Chainlink生成随机数的方法一

概述

        随机数在软件设计中有很广泛的应用场景,尤其是在游戏中、菠菜、彩票等业务。但在区块链产生一个真正随机的数字确不容易,主要原因是区块链的共识机制需要所有节点达成一致,这使得智能合约中不可能存在真正随机的数字,否则各节点无法达成一致。本文主要介绍预言机生成随机数的原理,以及对比Chainlink生成随机数的两种方法。

预言机的原理

       众所周知 智能合约的自动执行、智能合约外部数据的获取都需要预言机的支持。预言机一般分为链上和链下两部分,其中链上部分相当于一个桥接器。它接收智能合约的请求,同时将请求发送到预言机的链下部分,链下部分根据请求收集数据相应数据,收集到数据后通过方法调用的形式将数据响应给预言机的链上部分,链上部分再将数据反馈到智能合约。

        在Chainlink中智能合约向预言机发送请求是通过转移Link来触发的,Link是一个 ERC677 token,它继承了ERC20 token标准的功能,与ERC20 token不同的是,转移Link时可以携带额外的数据,Link的接收方可以定制逻辑,每当收到Link则解析数据并自动触发相关逻辑。下面通过获取随机数的例子来说明具体工作流程。

预言机组成部分       

注:从上图我们可以看到,我们首先需要向智能合约中转入Link,以备触发预言机的逻辑。

VRF Wrapper(链上)

        Wrapper定义接收Link的接口,并实现相关逻辑——将智能合约的请求转发给Coordinator

VRF Coordinator(链上)

        Coodinator主要做两件事:1. 接收到Wrapper的请求后发射日志事件; 2. 收到Service的响应数据后验证,再将验证通过的数据回调给Wrapper。

VRF Service(链下)

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

工作流程

        与预言机交互时需要遵守一定的规范,一般我们通过继承预言机的接口来实现,比如“生成随机数”时我们需要继承VRFV2WrapperConsumerBase,该抽象合约中封装了相关方法。智能合约需要实现该抽象合约的fullfill方法来接收数据。以下是获取数据的流程:

  1. 调用Wrapper的calculateRequestPrice方法,用于计算此刻调用时需要转移的Link数量 ;
  2. 调用LinkToken的transferAndCall方法向Wrapper转入Link,转入Link时携带请求数据;
  3. 收到Link后Wrapper的onTokenTransfer会被执行,onTokenTransfer方法解析收到的数据,并向Coordinator的requestRandomWords方法发起调用;
  4. Coordinator的requestRandomWords方法收到调用后会发射日志事件;
  5. Service收到Coordinator的事件后,解析日志,并根据需要收集相关数据;
  6. Service将收集到的数据返回给Coordinator,后者收到数据后将数据响应给Wrapper;
  7. Wrapper调用智能合约的fullfill方法,将数据返回给智能合约;

代码示例

// SPDX-License-Identifier: MIT
// An example of a consumer contract that directly pays for each request.
pragma solidity ^0.8.19;

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


contract VRFv2DirectFundingConsumer is
    VRFV2WrapperConsumerBase,
    ConfirmedOwner
{
    event RequestSent(uint256 requestId, uint32 numWords);
    event RequestFulfilled(
        uint256 requestId,
        uint256[] randomWords,
        uint256 payment
    );

    struct RequestStatus {
        uint256 paid; // amount paid in link
        bool fulfilled; // whether the request has been successfully fulfilled
        uint256[] randomWords;
    }
    mapping(uint256 => RequestStatus) public s_requests; /* requestId --> requestStatus */


    uint256[] public requestIds;
    uint256 public lastRequestId;

    uint32 callbackGasLimit = 100000;

    uint16 requestConfirmations = 3;

    uint32 numWords = 2;

    address linkAddress;


    constructor(address _linkAddress, address _wrapperAddress)
        ConfirmedOwner(msg.sender)
        VRFV2WrapperConsumerBase(_linkAddress, _wrapperAddress)
    {
        linkAddress = _linkAddress;
    }

    function requestRandomWords()
        external
        onlyOwner
        returns (uint256 requestId)
    {
        requestId = requestRandomness(
            callbackGasLimit,
            requestConfirmations,
            numWords
        );
        s_requests[requestId] = RequestStatus({
            paid: VRF_V2_WRAPPER.calculateRequestPrice(callbackGasLimit),
            randomWords: new uint256[](0),
            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].paid > 0, "request not found");
        s_requests[_requestId].fulfilled = true;
        s_requests[_requestId].randomWords = _randomWords;
        emit RequestFulfilled(
            _requestId,
            _randomWords,
            s_requests[_requestId].paid
        );
    }

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

    /**
     * Allow withdraw of Link tokens from the contract
     */
    function withdrawLink() public onlyOwner {
        LinkTokenInterface link = LinkTokenInterface(linkAddress);
        require(
            link.transfer(msg.sender, link.balanceOf(address(this))),
            "Unable to transfer"
        );
    }
}

部署

         部署合约时需要指定Link和Wrapper的地址。Sepolia网络为例,Link地址为:0x779877A7B0D9E8603169DdbD7836e478b4624789,Wrapper地址为:0xab18414CD93297B0d12ac29E63Ca20f515b3DB46。Chainlink支持的网络从这里可以看到。

  1. 在Remix中打开上述合约;
  2. 将钱包切换到目标网络,如Sepolia;
  3. 输入指定网络Link及Wrapper地址,部署合约;

执行

  1. 向智能合约中转入LINK,可以从这里获取测试LINK;
  2. 发起请求;

  • 29
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值