Generating a random number on the Blockchain — II

M-Zohaib Nasir
3 min readMar 9, 2022

As we discussed in our previous article Generating a random number on the Blockchain, Chainlink-VRF is a preferred way to get a provably random number into your smart contract. So, let’s dive in to see how it actually works.

Request and Receive cycle?

Using chainlink data feeds, to asset pricing data like the ETH / USD feed, which consist of reference data posted on-chain by oracles, means that data is just stored at a single point and we just access it by reference. This data is stored in a contract until the oracle updates the data again.

Randomness, on the other hand, cannot be reference data. If the result of randomness is stored on-chain, any actor could retrieve the value and predict the outcome. Instead, randomness must be requested from an oracle, which generates a number and a cryptographic proof. Then, the oracle returns that result to the contract that requested it. This sequence is known as the Request and Receive cycle.

Notice:

The contract should have enough LINK to pay the specified fee. This example contract is not funded!

Procedure

To consume randomness, your contract should inherit from VRFConsumerBase and define two required functions:

  • requestRandomness, which makes the initial request for randomness.
  • fulfillRandomness, which is the function that receives and does something with verified randomness.

Example code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";

contract RandomNumberConsumer is VRFConsumerBase {

bytes32 internal keyHash;
uint256 internal fee;

uint256 public randomResult;


constructor() VRFConsumerBase(
0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9, // VRF Coordinator
0xa36085F69e2889c224210F603D836748e7dC0088 // LINK Token
) public
{
keyHash = 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4;
fee = 0.1 * 10 ** 18; // 0.1 LINK (Varies by network)
}


function getRandomNumber() public returns (bytes32 requestId) {
require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");
return requestRandomness(keyHash, fee);
}

function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
randomResult = randomness;
}

}

What is happening?

You’ll see, you can see our contract RandomNumberConsumer is inheriting VRFConsumerBase.

contract RandomNumberConsumer is VRFConsumerBase

and we look at our constructor, it does some weird stuff. Looks like we have two constructors here,

constructor() VRFConsumerBase(
_vrfCoordinatorAddress,
_link
) public
{
keyHash = <key_hash>;
fee = <fee>;
}
//_vrfCoordinator is the on-chain contract that makes sure numbers are random.
//_link address of LINK token contract
//keyHash uniquely identifies chainlink node we are going to use
//fee is how many link we are going to pay to link node.

Why is that? Let’s look into VRFConsumerBase.sol at https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/VRFConsumerBase.sol

As we can see that VRFConsumerBase we are importing has its own constructor.

//VRFConsumerBase's contructorconstructor(address _vrfCoordinator, address _link) {    vrfCoordinator = _vrfCoordinator;    
LINK = LinkTokenInterface(_link);
}

We are using both constructors in our RandomNumberConsumer contract.

function getRandomNumber() public returns (bytes32 requestId) {
require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");
return requestRandomness(keyHash, fee);
}

Now in this contract, RandomNumberConsumer, we have a function getRandomNumber() which is going to return bytes32, and what it’s going to do is ,it is going to call requestRandomness() which is inherited from VRFConsumerBase. In other words, we are supposed to call requestRandomness(keyHash, fee).

Once the VRFCoordinator has received and validated the oracle’s response to your request, it will call your contract’s fulfillRandomness method.

function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
randomResult = randomness;
}
//The requestId argument is generated from the keyHash and the seed by makeRequestId(keyHash, seed).

The randomness argument to fulfillRandomness() is the actual random value generated.

Summarizing:

So, to generate a random number, we actually had two transactions
occur. One made by us and other by VRFCoordinator. We called requestRandomness() and VRFCoordinator called fulfillRandomness(). Generated random number was initialized to randomness variable — an argument of fulfillRandomness().

--

--