Chainlink生成随机数的方法二

概述

        在之前的文章《Chainlink生成随机数的方法一》中我们介绍了在Chainlink中产生随机数的一般方法。本文我们介绍Chainlink生成随机数的另一种方法。读者可能会有疑惑——简单的产生一个随机数为什么还有不同的方法?这里我先给出结论——总体来讲两种方法底层逻辑相同,区别仅在于使用场景。比如:在方法一中我们需要为自己写的智能合约中预先转入Link,通过转移Link来触发随机数的请求。方法二(即我们本文将要介绍)中,其将Link统一管理,然后授权智能合约来转移Link。这样可以集中管理应用而不用为每个应用单独设置管理地址及账户。

框架

        框架图

Chainlink生成随机数的方法二_第1张图片

   组件

        相比于方法一,此处少了Wrapper组件,这是因为Link的转移发生在向智能合约返回结果时。

VRF Coordinator(链上)

        Coodinator主要做三件事:1. 接收到智能合约的请求后发射日志事件; 2. 收到Service的响应数据后计算费用并扣除Link;3. 验证数据,并将验证通过的数据回调给调用方。

VRF Service(链下)

        Service订阅Coordinator的日志事件,并从日志中解析出请求参数,然后向外部发送请求并收集数据,收集到数据后通过RPC方式将数据发送给Coordinator。

流程

        与预言机交互时需要遵守一定的规范,一般我们通过引入、继承预言机的相关接口来实现,比如在下面代码示例中我们引入了VRFCoordinatorV2Interface接口,通过调用该接口中的方法来发起请求。另外智能合约需要实现“fullfill”方法来接收数据,我们通过继承VRFConsumerBaseV2抽象合约,并实现其fulfillRandomWords方法。

  1. 先在Chainlink创建一个subscription,并向subscription中转入一定数量的Link;
  2. 创建一个发起随机数请求的合约,(在下面示例代码中我们调用VRFCoordinatorV2Interface接口的requestRandomWords方法来发起调用),并将合约添加到subscription;
  3. 调用合约方法发起随机数请求到VRF Coordinator;
  4. VRF Coordinator收到请求后发射日志事件;
  5. VRF Service收到VRF Coordinator发射的日志事件后,解析日志数据并生成响应数据;
  6. VRF Service以RPC调用的形式向以太坊发起交易,用于调用VRF Coordinator的方法,并将响应数据以参数的形式传递到VRF Coordinator;
  7. VRF Coordinator计算GAS费用将将其转换为Link予以扣除;
  8. VRF Coordinator调用智能合约的fulfillRandomWords方法向其返回数据;

代码示例

// SPDX-License-Identifier: MIT
// An example of a consumer contract that relies on a subscription for funding.
pragma solidity ^0.8.19;

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";

/**
 * Request testnet LINK and ETH here: https://faucets.chain.link/
 * Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
 */


contract VRFv2Consumer is VRFConsumerBaseV2, ConfirmedOwner {
    event RequestSent(uint256 requestId, uint32 numWords);
    event RequestFulfilled(uint256 requestId, uint256[] randomWords);

    struct RequestStatus {
        bool fulfilled; // whether the request has been successfully fulfilled
        bool exists; // whether a requestId exists
        uint256[] randomWords;
    }
    mapping(uint256 => RequestStatus)
        public s_requests; /* requestId --> requestStatus */
    VRFCoordinatorV2Interface COORDINATOR;

    uint64 s_subscriptionId;

    // past requests Id.
    uint256[] public requestIds;
    uint256 public lastRequestId;

    // The gas lane to use, which specifies the maximum gas price to bump to.
    // For a list of available gas lanes on each network,
    // see https://docs.chain.link/docs/vrf/v2/subscription/supported-networks/#configurations
    bytes32 keyHash =
        0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c;

    // Depends on the number of requested values that you want sent to the
    // fulfillRandomWords() function. Storing each word costs about 20,000 gas,
    // so 100,000 is a safe default for this example contract. Test and adjust
    // this limit based on the network that you select, the size of the request,
    // and the processing of the callback request in the fulfillRandomWords()
    // function.
    uint32 callbackGasLimit = 100000;

    // The default is 3, but you can set this higher.
    uint16 requestConfirmations = 3;

    // For this example, retrieve 2 random values in one request.
    // Cannot exceed VRFCoordinatorV2.MAX_NUM_WORDS.
    uint32 numWords = 2;

    /**
     * COORDINATOR FOR SEPOLIA: 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625
     */
     address coordinator;
    constructor(
        uint64 _subscriptionId,
        address _coordinator
    )
        VRFConsumerBaseV2(_coordinator)
        ConfirmedOwner(msg.sender)
    {
        COORDINATOR = VRFCoordinatorV2Interface(_coordinator);
        s_subscriptionId = _subscriptionId;
    }

    // Assumes the subscription is funded sufficiently.
    function requestRandomWords()
        external
        onlyOwner
        returns (uint256 requestId)
    {
        // Will revert if subscription is not set and funded.
        requestId = COORDINATOR.requestRandomWords(
            keyHash,
            s_subscriptionId,
            requestConfirmations,
            callbackGasLimit,
            numWords
        );
        s_requests[requestId] = RequestStatus({
            randomWords: new uint256[](0),
            exists: true,
            fulfilled: false
        });
        requestIds.push(requestId);
        lastRequestId = requestId;
        emit RequestSent(requestId, numWords);
        return requestId;
    }

    function fulfillRandomWords(
        uint256 _requestId,
        uint256[] memory _randomWords
    ) internal override {
        require(s_requests[_requestId].exists, "request not found");
        s_requests[_requestId].fulfilled = true;
        s_requests[_requestId].randomWords = _randomWords;
        emit RequestFulfilled(_requestId, _randomWords);
    }

    function getRequestStatus(
        uint256 _requestId
    ) external view returns (bool fulfilled, uint256[] memory randomWords) {
        require(s_requests[_requestId].exists, "request not found");
        RequestStatus memory request = s_requests[_requestId];
        return (request.fulfilled, request.randomWords);
    }
}

部署

        部署代码时需要提供subscriptionId(每个注册完成的subscription都会生成唯一ID),Coordinator地址,不同网络该地址不同,Chainlink支持的网络看这里。比如:我使用的是Sepolia网络,其对应的Coordinator地址是0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625

        智能合约部署完成后,将其添加到自己注册的subscription中(进入自己subscription详情页,点击“Add consumer”后填入智能合约的地址)。

Chainlink生成随机数的方法二_第2张图片

一切就绪后,就可以执行合约的requestRandomWords方法来发起请求,稍等片刻后就可以看到结果。

Chainlink生成随机数的方法二_第3张图片

你可能感兴趣的:(区块链,区块链,智能合约,去中心化,分布式账本)