预言机chainlink的使用——喂价、VRF

  • 1_bit
  • 更新于 2022-10-10 13:53
  • 阅读 4030

在智能合约中,无法获取区块链意外的数据,或外部数据不可靠,从而产生了预言机。预言机可使外部数据可信并保持唯一性。

本节内容:

  • 预言机是什么
  • 获取 Goerli 测试币
  • chainlink获取交易对价钱
  • VRF准备步骤
  • VRF使用

一、chainlink 使用准备

1.1 什么是预言机

在智能合约中,无法获取区块链意外的数据,或外部数据不可靠,从而产生了预言机。预言机可使外部数据可信并保持唯一性。

在使用 chainlink 之前要注意,(2022年9月29日) 之前很多资料所述在测试网需要使用 Rinkeby 网络,但由于 Ethereum的协议更改:Rinkeby、Ropsten和Kovan测试网络可能无法可靠地工作,很快就会被弃用。所以在此我们需要使用 Goerli 测试网络(当然chainlink 也支持其他网络): 在这里插入图片描述

1.2 获取预言机测试币

在 Goerli 测试网络下,需要获取 Link 以及对应的 eth 测试币 ,获取方式如下。

首先导入 Link 的代币 ,导入步骤为:

  • 1.点击导入代币

在这里插入图片描述

  • 2.点击后在如下页面中输入合约地 0x326C977E6efc84E512bB9C30f76E30c160eD06FB 以及 LINK

在这里插入图片描述

  • 3.最后进行添加即可,添加之后转到 https://faucets.chain.link/,随后在页面上点击连接钱包,出现如下界面:

在这里插入图片描述 在此需要有一个推特账号并且有发布一条推特,最后点击验证后即可获取测试币 。

二、 获取交易对价格

使用 chainlink 预言机在 solidity 中需要引入 chainlink 的 sol 文件:

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

随后需要在 solidity 中创建一个 AggregatorV3Interface 对象:

AggregatorV3Interface internal priceFeed;

可以创建一个构造函数用来对这个 reserveFeed 初始化:

constructor() {
    reserveFeed = AggregatorV3Interface(0x779877A7B0D9E8603169DdbD7836e478b4624789);
}

其中 AggregatorV3Interface 中所填入的地址为你需要对应数据的地址,这个地址可以在 chainlink文档中查询不同价格的地址。接下来创建一个方法,通过 priceFeed 调取最新 BTC / ETH 价格的价格:

priceFeed.latestRoundData();

由于这个 latestRoundData 方法返回多个参数,在此我们仅获取价格即可,所有函数写成:

function getLatestPrice() public view returns (int) {
    (
        /*uint80 roundID*/,
        int price,
        /*uint startedAt*/,
        /*uint timeStamp*/,
        /*uint80 answeredInRound*/
    ) = priceFeed.latestRoundData();
    return price;
}

调取后即可得到最新价格: 在这里插入图片描述

以上返回的数值是一个 18位的小数,也就是 14.615977000000000000 完整合约代码如下(参考chainlink 官方示例):

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

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

contract PriceConsumerV3 {

    AggregatorV3Interface internal priceFeed;

    /**
     * Network: Goerli
     * Aggregator: ETH/USD
     * Address: 0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e
     */
    constructor() {
        priceFeed = AggregatorV3Interface(0x779877A7B0D9E8603169DdbD7836e478b4624789);
    }

    /**
     * Returns the latest price
     */
    function getLatestPrice() public view returns (int) {
        (
            /*uint80 roundID*/,
            int price,
            /*uint startedAt*/,
            /*uint timeStamp*/,
            /*uint80 answeredInRound*/
        ) = priceFeed.latestRoundData();
        return price;
    }
}

对于以上所述的地址就等于一个接口,这个接口可以通过 chainlink 的 doc 进行查看,链接为:https://docs.chain.link/docs/data-feeds/price-feeds/addresses/

打开链接后,page 拉到下面,可以找到对应的测试网地址: 在这里插入图片描述

点击地址进入,选择合约,可以查看当前合约: 在这里插入图片描述 接下来还可以直接点击 read Contract 查看相关读取的方法,找到我们所使用的的 latestRoundData 为我们所调用的方法: 在这里插入图片描述 点击 query 就等于我们调用了该方法: 在这里插入图片描述 之后将会出现最新的返回结果: 在这里插入图片描述 其中参数:

  • roundid 表示第几轮价格(价格是一轮轮更新的)
  • answer 表示当前价格
  • startedAt 什么时候开始更新的价格
  • updatedAt 什么时候开始更新结束的
  • answeredlnRound 是第几轮更新的当前价格

三、VRF 确定性的随机数使用准备

3.1 创建订阅

VRF 是 chainlink 提供的可验证随机数,在以往的随机数中,每个节点生成的随机数是不一样的,此时所有节点的某个结果不一致,是在区块链中是不允许的,chainlink 的 VRF 即可解决这个问题。

在此我们只探讨 VRF 的使用方法,并不验证 VRF 的可验证性。

使用 VRF 分为以下几个步骤(之后将会详细讲解):

  1. 创建一个订阅,有了订阅后才可以使用VRF (相当于注册个账号,方便接下来的使用)
  2. 创建订阅后将会有一个 订阅ID,
  3. 获取 订阅ID 获取到ID后,部署合约通过 订阅ID 初始化(构造函数完成)VRF的接口
  4. 部署合约后将合约地址通过网页指定调用随机数的合约

由于我们使用搞得是 goerli 测试网络,在 chainlink 文档中找到 goerli 网络的 VRF 订阅地址,doc 链接为: https://docs.chain.link/docs/vrf/v2/subscription/supported-networks/ 在这里插入图片描述

找到 goerli 网络的 VRF 订阅链接 https://vrf.chain.link/goerli 通过地址进行链接订阅。

接着打开 goerli 的订阅链接,在 web 页 中点击 Create Subscription 对 开始创建订阅:

在这里插入图片描述

点击创建订阅后需要通过钱包账户进行链接,本文中使用小狐狸 metamask,点击按钮连接钱包: 在这里插入图片描述

接着会获取到你钱包地址,点击 Create subscription 创建订阅:

在这里插入图片描述

此时会发生一个交易,要注意你的钱包提示(有些同学可能会不直接弹出钱包,在此需要点击钱包后会弹出交易窗),点击确认交易(我点太快了): 在这里插入图片描述 随后稍等一会,将会出现已订阅提示,并且会出现一个 transaction 交易,可以点击查看: 在这里插入图片描述 点击查看交易后可以看到对应的交易信息(当然你可以不看,可以稍等片刻后交易完成): 在这里插入图片描述

接着会出现一个窗口,叫你往里面存一点 LINK,这个 LINK 是用于生成随机数的花费,你可以选择10个就可以了,做个测试嘛,不需要太多,10个够十来次了(LINK 之前在前几节有说怎么获取): 在这里插入图片描述 随后小狐狸会弹出交易窗,交易完毕后即设置随机数生成花费成功: 在这里插入图片描述

接下来就需要添加你的合约账户了,在这里你还可以看到这个ID,这个ID很重要,注意:

在这里插入图片描述

此时我们只需要记录下这个订阅 ID 后就可以去写代码了,这个页面先保留,别急着叉掉。

3.2 VRF 获取真随机数合约编写

在合约中使用 VRF 需要 import 引入对应的合约,合约所在位置可以通过 chainlink 的 GitHub 进行查看,地址为: https://github.com/smartcontractkit/chainlink/blob/develop/contracts/

在这里插入图片描述

在此处我们一共要 import 两个合约:

  • chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol
  • chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol

这些合约可以在这个项目的 src 查看,由于我们是 0.8 版本,所以之后查看将会在 v0.8 目录下查看,以下是对应不同版本的 合约:

在这里插入图片描述 我们选择 v0.8 版本: 在这里插入图片描述

我们往下拉,找到 VRF 相关的合约内容,在此 就是所需引入的 VRF 第一个合约,这个合约是需要咱们继承后重写接收预言机下所产生的随机数接口(之后将会详细讲解): 在这里插入图片描述

接着还有一个合约在 interface 目录下: 在这里插入图片描述 此时需要进入到 interface 才能查看此合约代码: 在这里插入图片描述

接下来所实现 VRF 的步骤为:

  1. 创建一个类继承 VRFConsumerBaseV2 合约,重写方法
  2. 引入 VRF 的 interface ,在继承的 VRFConsumerBaseV2 合约下使用 VRFCoordinatorV2Interface 申请随机数(对随机数进行获取)

接着我们创建一个合约后,在合约中使用 import 引入这两个合约(示例引用 chainlink 官方示例):

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.8.7;

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";

注意,引入合约路径须在前面加一个“@”。

接着创建一个合约名为 ChainLinkVRFDemo 继承自 VRFConsumerBaseV2:

contract ChainLinkVRFDemo is VRFConsumerBaseV2{

}

接着编写一个构造方法,这个构造方法需要调用 父合约 构造函数,传入 VRF 的调用地址,在此我们可以查看 goerli 测试网络下的地址,查看链接为:https://docs.chain.link/docs/vrf/v2/subscription/supported-networks/#avalanche-mainnet

在这里插入图片描述 此时构造函数代码如下:

address vrfCoordinatorAddr=0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D;//调用地址
constructor()VRFConsumerBaseV2(vrfCoordinatorAddr){
}

由于接下来我们还需要使用对应的订阅ID,通过这个订阅ID 对预言机发送请求获取随机数,所以此时我们得需要设置这个订阅号,那么在构造函数时传入参数,记录订阅号ID。此时新建一个变量 subId,修改构造函数接收订阅 ID:

uint64 subId;//订阅号
constructor(uint64 _subId)VRFConsumerBaseV2(vrfCoordinatorAddr){
    subId=_subId;
}

由于我们在创建另一个 import 的合约 VRFCoordinatorV2Interface 获取随机数时也需要对应的知道 VRF 的调用地址,所以此时咱们直接在构造函数中对其对象进行创建,并设定地址:

VRFCoordinatorV2Interface VRFInterface;//当然要声明一个 VRFCoordinatorV2Interface 类型的变量,接着在构造函数中实例化即可
constructor(uint64 _subId)VRFConsumerBaseV2(vrfCoordinatorAddr){
    VRFInterface=VRFCoordinatorV2Interface(vrfCoordinatorAddr);
    subId=_subId;
}

此时你若编译肯定会报错,那是因为你还未重写一个接收方法,这个方法叫做 fulfillRandomWords,咱们在 GitHub 的 VRFConsumerBaseV2.sol 源码中应该可以看得到: 在这里插入图片描述 这个方法需要重写,作用是当我们请求了随机数后,将会返回对应的随机数(也就是参数 randomWords),我们需要在这个方法中编写代码将这个随机数进行存储。可能看到这里你会疑惑,直接调用请求不就可以获取了?为什么还要这个方法?而且这个方法是 internal 修饰的,是内部的方法呀。

在此我们并不讨论 chainlink VRF 随机数生成后返回的原力,咱们只需要知道在调用随机数请求后,chainlink 的节点开始生成随机数,并经过一些列的操作(投票随机数)最后得出最终结果,这个结果将通过调用咱们这个合约中的一个接口进行随机数的“返回”。

可能你还会说,这个接口不是 internal ?外部怎么调用? 其实最开始我也并不理解 VRF 这个接口的实现逻辑,在我查看他代码时发现,fulfillRandomWords 这个方法在 rawFulfillRandomWords 中进行了调用,你看下图: 在这里插入图片描述 此时 rawFulfillRandomWords 方法是 external 进行修饰的,并且接收两个参数,其中我肯定的告诉你 randomWords 就是我们请求后的随机数。

那么此时你应该明白了大体逻辑:在我们继承了 VRFConsumerBaseV2 合约后,并不需要重写 rawFulfillRandomWords 方法,只需要重写 fulfillRandomWords 方法,在其内部编写将 randomWords 存储到何处(哪个变量接收)即可,这样就得到了随机数。

现在你应该明白了吧。那么接下来我们直接从 GitHub 中复制这个需要重写的 fulfillRandomWords,用一个变量接收 randomWords 即可:

//存储随机数
uint256[] public s_randomWords;
//接收方法
function fulfillRandomWords(uint256 requestID,uint256[] memory randomWords) internal override{
    s_randomWords=randomWords;
} 

记得,一定要 把 virtual 改成了 override。

接下来咱们开始编写请求方法吧,请求方法也超击简单,通过 另外一个 import 的合约 VRFCoordinatorV2Interface 的对象调用 requestRandomWords 方法,并且传入参数即可。

我们可以在 Github 上的合约代码内看到这个接口: 在这里插入图片描述 这个方法接收 5 个参数,分别是:

  • keyHash:一个hash值,不同的 hash 值表示你这个“生成随机数的快慢”
  • subId:订阅ID(已获得)
  • requestConfirmations:多少个交易后表示成功,越小值越快
  • callbackGasLimit:gas limit上限官方示例给出的值是 100000
  • numWords:请求多少个随机数,上限是500

这样一看是不是就觉得很简单了?

其中的 keyHash 是有官方给出的,查看文档: https://docs.chain.link/docs/vrf/v2/subscription/supported-networks/#avalanche-mainnet

在这里插入图片描述

上图中的 30 gwei key hash 就是这个 hash 值了,在这里由于是测试网只有一个,这个 hash 的作用就是给得越高,当网络繁忙时,你的“交易”会比其他的快,低一点慢 高一点快。

你可以看到其他网络中的 gwei key hash 的值有不同的选择: 在这里插入图片描述

剩下的参数我们就知道怎么创建了,一个是 requestConfirmations 就给个 3,表示 3个确认后我们就认为交易success了、callbackGasLimit 默认给官方文档的 100000、numWords 请求随机数就请求 3 个即可;那么此时我们先创建一个方法叫做 requestRandomWords(必须这个名,至于为啥还在了解),并且使用在构造函数中所创建的 VRFInterface 对象对接口 requestRandomWords 进行调用,并且传入对应的参数:

//选择不同的 gwei  可以快和慢,低一点慢 高一点快
bytes32 keyHash=0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15;
//认为多少个交易以后交易就成功了,一般是12
uint16 requestConfirmations=3;
//gas limit上限
uint32 callbackGasLimit=100000;
//请求多少个随机数,上限是500
uint32 numWords=3;

function requestRandomWords() external{
    VRFInterface.requestRandomWords(
        keyHash,
        subId,
        requestConfirmations,
        callbackGasLimit,
        numWords
    );
}

是不是这样就ok了?不过我们需要,这个请求随机数的方法是一个交易,会存在花费,我们需要对应的为其设置只能合约者可以调用,那么添加 require:

在这里插入图片描述

在这里还要提一嘴,这个 requestRandomWords 方法会返回一个值 requestID,表示你这个随机数是第几轮产生的,你也可以进行接收: 在这里插入图片描述

整理一下后最终整个合约代码如下:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.8.7;

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";

contract ChainLinkVRFDemo is VRFConsumerBaseV2{
    VRFCoordinatorV2Interface VRFInterface;
    uint64 subId;//订阅号
    address owner;
    address vrfCoordinatorAddr=0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D;
    //选择不同的 gwei  可以快和慢,低一点慢 高一点快
    bytes32 keyHash=0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15;
    //认为多少个交易以后交易就成功了,一般是12
    uint16 requestConfirmations=3;
    //gas limit上限
    uint32 callbackGasLimit=100000;
    //请求多少个随机数,上限是500
    uint32 numWords=3;
    //存储随机数
    uint256[] public s_randomWords;
    //第几次请求的数据
    uint256 public requestID;

    constructor(uint64 _subId)VRFConsumerBaseV2(vrfCoordinatorAddr){
        VRFInterface=VRFCoordinatorV2Interface(vrfCoordinatorAddr);
        subId=_subId;
    }

    //接收方法
    function fulfillRandomWords(uint256 requestID,uint256[] memory randomWords) internal override{
        s_randomWords=randomWords;
    }

    function requestRandomWords() external{
        require(msg.sender==owner);
        requestID=VRFInterface.requestRandomWords(
            keyHash,
            subId,
            requestConfirmations,
            callbackGasLimit,
            numWords
        );
    }
}

我们开始部署合约,选择 Metamask 的网络,传入之前我生成的那个订阅ID号(请忽略一下地址不同,因为之前敲错代码了后又重新贴图了一次): 在这里插入图片描述

点击部署后将会有一个交易产生: 在这里插入图片描述 确定支付后可以对其查看: 在这里插入图片描述

回到之前订阅成功的页面,点击 添加合约地址: 在这里插入图片描述 复制自己的合约地址: 在这里插入图片描述

添加合约: 在这里插入图片描述

接着确认小狐狸的交易: 在这里插入图片描述 在这里插入图片描述

接着进行等待交易完成(你的可能和我的不一样)。 在这里插入图片描述

接着点击请求随机数方法: 在这里插入图片描述

接着会弹出交易提示,我们点击确认: 在这里插入图片描述

接着我们可以看到一个随机数的请求: 在这里插入图片描述 等 Pending 编程 success 即可完成随机数获取。 在这里插入图片描述

success 之后,点击合约中的存储状态变量,输入索引获取随机数,由于我们设置的是3个随机数,所以索引为0、1、2: 在这里插入图片描述

点赞 4
收藏 4
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
1_bit
1_bit
转区块链中(求职)... InfoQ签约作者 蓝桥签约作者 CSDN、51、InfoQ专家、 2020年博客之星TOP5 CSDN第二季新星评委 CSDN新星导师 2021年博客新星评委 自媒体程序员 2021Infoq社区年度社区荣誉共建奖