Search code examples
chainlink

Chainlink VRF not calling my fulfillRandomWords


I'm testing out a Chainlink VRF implementation on Arbitrum Sepolia and after calling requestRandomWords, I don't seem to ever get back a response in the form of a call to fulfillRandomWords.

I'm able to see that the request goes through from a RandomWordsRequested event that gets logged - here's one example of a transaction with this log (which also contains other pertinent info like the subscription id, the requesting contract address, etc.)...

https://sepolia.arbiscan.io/tx/0xa53cb8c1fc9086bcb5e7a0fa1e0f47d253f09e0d4b7b9389c271abad7bd6d85f#eventlog#2

As shown in the link above, the address of my consumer contract on Arbitrum Sepolia is 0x8C34303C98cf94b2A332293E0888DFaF9C8ddAec. This contract both calls requestRandomWords on the coordinator and implements fulfillRandomWords as follows...

contract SomeContract is VRFConsumerBaseV2Plus {

    function requestRandomWords(uint32 _numWords) internal {
        IVRFCoordinatorV2Plus coordinator = IVRFCoordinatorV2Plus(0x5CE8D5A2BC84beb22a398CCA51996F7930313D61);

        uint256 requestId = coordinator.requestRandomWords(
            VRFV2PlusClient.RandomWordsRequest({
                keyHash: 0x1770bdc7eec7771f7ba4ffd640f34260d7f095b79c92d34a5b2551d6f6cfd2be,
                subId: 115678517865845314128708521410416585367502008983375723740453418397630079470798, 
                requestConfirmations: 1, 
                callbackGasLimit: 2500000, 
                numWords: _numWords,
                extraArgs: VRFV2PlusClient._argsToBytes(VRFV2PlusClient.ExtraArgsV1({ nativePayment: false }))
            })
        );
    }

    function fulfillRandomWords(
        uint256 _requestId,
        uint256[] calldata _randomWords
    ) internal override {
        // Do some stuff
    }

}

I have confirmed that the consumer is registered under the appropriate subscription id.


Solution

  • The fulfillRandomWords function is executed successfully in response to your requestIds, and the corresponding RandomWordsFulfilled events are emitted, as you can see in these transactions:

    1. For request id 70478683367982197649124627064126082699697643183791205784294629728598288438554: 0x1c06f3489bd9835f2aa39d8d03447b7e1b6a53adc0368f314ff5ce6734f4b578

    2. For request id 88364870351260394125084081620819479699342045779156455279879578975759625912927: 0x2e00db5126602b7b503f5e8dcf6e0ea65f5ee9ce5eb59eb4df93335ad036df74

    As Andrej mentioned in the comments, you can filter the logs of the RandomWordsFulfilled event by topic1 (i.e., your request id).

    Since the block explorer only allows filtering by topic0 (i.e., the keccak256 hash of the event signature), I’ve created this script (using ethers.js) to filter based on both topic0 and topic1 combined:

    import { ethers } from "ethers";
    
    const provider = new ethers.JsonRpcProvider("https://arbitrum-sepolia-rpc.publicnode.com");
    
    // VRFCoordinatorV2_5 contract address
    const contractAddress = "0x5CE8D5A2BC84beb22a398CCA51996F7930313D61";
    
    // ABI of the RandomWordsFulfilled event (only needed to decode logs later)
    const abi = [
        "event RandomWordsFulfilled(uint256 indexed requestId, uint256 outputSeed, uint256 indexed subId, uint96 payment, bool nativePayment, bool success, bool onlyPremium)"
    ];
    
    // Compute topic0 (Keccak-256 of event signature)
    const eventSignature = "RandomWordsFulfilled(uint256,uint256,uint256,uint96,bool,bool,bool)";
    const topic0 = ethers.id(eventSignature); // Hashes event signature
    
    // Convert uint256 to 32-byte hex
    const requestId = 88364870351260394125084081620819479699342045779156455279879578975759625912927n; 
    const topic1 = ethers.zeroPadValue(ethers.toBeHex(requestId), 32); // Convert to 32-byte format
    
    const fromBlock = 128387786; // Block number of the transaction corresponding to your placeBet function call which is calling the requestRandomWords function (https://sepolia.arbiscan.io/tx/0x122f49c3c11fb6626afdf017ccc94ede946f656a1c913a50525070daec67a776)
    const toBlock = fromBlock + 50;
    
    // Define filter
    const filter = {
        address: contractAddress,
        fromBlock: fromBlock,
        toBlock: toBlock,
        topics: [topic0, topic1] // Filtering by topic0 (event type) and topic1 (requestId)
    };
    
    (async () => {
        try {
            // Fetch logs
            const logs = await provider.getLogs(filter);
    
            // Decode logs
            const iface = new ethers.Interface(abi);
            logs.forEach(log => {
                const parsedLog = iface.parseLog(log);
                console.log("RandomWordsFulfilled Event:", parsedLog.args);
                console.log("Transaction Hash:", log.transactionHash);
            });
        } catch (error) {
            console.error("Error fetching logs:", error);
        }
    })();
    

    You can create a file named vrf_FilterRandomWordsFulfilledEventByRequestId.js (or any other name you prefer) and paste this script into it. Then, execute the script using the following command:

    node vrf_FilterRandomWordsFulfilledEventByRequestId.js
    

    OUTPUT:

    Screenshot


    NOTE: To receive the random words and reflect them in your contract, you need to store them in a state variable/array or emit them via your contract's event (or maybe do both) in your overridden fulfillRandomWords function, like this:

    event RequestFulfilled(uint256 requestId, uint256[] randomWords);
    
    uint256[] public s_randomWords;
    
    function fulfillRandomWords(
        uint256 _requestId,
        uint256[] calldata _randomWords
    ) internal override {
        s_randomWords = _randomWords;
        emit RequestFulfilled(_requestId, _randomWords);
    }
    

    As you can see, the five random words/numbers are shown in this RequestFulfilled event log, which I emitted in the overridden fulfillRandomWords function of my VRFTest contract.

    RequestFulfilled event showing random words