如何使用链路VRF和Brownie生成随机性

152 阅读7分钟

使用Chainlink VRF和Brownie生成随机性

以太坊区块链是一个确定性的系统。这意味着,给定相同的输入,输出将总是相同的。当涉及到生成随机数时,这个系统带来了一些挑战。你不希望以确定性的方式生成随机数。

黑客可以操纵输入来生成他们想要的输出。Chainlink VRF通过使用分散的神谕来解决这个问题。

神谕器将链外数据(在我们的例子中,随机数)连接起来,并将其与区块链连接起来。随机性有各种各样的用例。

它们可能包括。

  • 创建和分发NFT。
  • 去中心化的金融。
  • 营销活动和忠诚度奖励。
  • 订购流程。
  • 区块链游戏。
  • 安全和认证。

目标

在这篇文章中,我们将了解Chainlink VRF的功能,以及如何使用brownie在智能合约中部署它。

前提条件

要跟上进度,读者需要。

  • 对Python和Solidity编程语言的基本了解。
  • 对Brownie API的基本了解。
  • 安装Brownie Python虚拟环境。
  • 最好是安装了Python3。
  • 一个 IDE,如Visual Studio 代码编辑器。

开始工作

第一步是在终端使用下面的命令初始化一个brownie 项目。

brownie init

合同文件夹中,创建一个名为RandomNumberGen.sol的新文件并初始化合同

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

contract RandomNumberGen {}

添加以下导入。

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

注意,上述导入可能会显示一些错误。我们将暂时忽略这些IDE错误。

Brownie 不能自动理解这个导入是来自哪里的。因此,你应该提供Chainlink的GitHub上的信息。 将从Github上提取Chainlink的代码。Brownie

文件夹中创建brownie-config.yaml 文件,并添加以下依赖关系。

dependencies:
  - "smartcontractkit/chainlink@1.1.0"

现在,我们需要让brownie 知道,@chainlink 实际上是指我们上面刚刚声明的依赖关系。

在同一个brownie-config.yaml 文件中,添加以下内容。

compiler:
  solc:
    remappings:
      - "@chainlink=smartcontractkit/chainlink@1.1.0"

然后运行下面的命令。

brownie compile

与Chainlink VRF交互

有了依赖关系,你现在可以与Chainlink VRF交互了。

首先,你需要继承自VRFConsumerBase合约。这是一个具有随机性的合约,是必需的。

contract RandomNumberGen is VRFConsumerBase{

}

你需要声明三个关键属性。

bytes32 internal keyHash;
uint256 internal fee;
uint256 public randomNumber;
  • keyHash 是唯一的键,用于识别要执行的任务。
  • randomNumber 将持有你打算生成的随机数。
  • fee 是执行这项交易所需的资金数额(LINK)。

Chainlink对一个人使用他们的神谕收费LINK 。然而,对于测试环境,你可以使用Chainlink Faucets免费获得LINK

你也可以使用这个龙头来为你的测试网络获得免费的ETH。在这种情况下,确保你有一些Rinkeby ETH

你的合约的构造器将是一个VRFConsumerBase构造器,它需要两个参数。

  • _vrfCoordinator - 这是智能合约的地址,用于检查生成的随机数是否真正随机。
  • _link - 这是链接令牌的地址。它根据你的网络而变化。

RandomNumberGen 也需要以下构造函数。

constructor(
    address _vrfCoordinator,
    address _link,
    bytes32 _keyHash,
    uint256 _fee
) VRFConsumerBase(_vrfCoordinator, _link) {
    keyHash = _keyHash;
    fee = _fee;
}

在这一点上,你需要获得vrfCoordinatorlink token ,和keyHash 的各自地址。

存储这些地址的一个有效方法是在你先前创建的brownie-config.yaml 文件中。

networks:
  rinkeby:
    vrf_coordinator: "0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B"
    link: "0x01BE23585060835E02B77ef475b0Cc51aA1e0709"
    key_hash: "0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311"

由于这些地址是在rinkeby网络上,你需要附上你的以太坊地址。

获得私钥,并将其存储在.env 文件中。你还需要获得一个Infura项目ID

创建一个.env 文件并把它放在文件夹中。

export PRIVATE_KEY = your_private_key
export WEB3_INFURA_PROJECT_ID = your_infura_project_id

接下来,我们需要提醒brownie 私钥的存储位置。在你的brownie-config.yaml 文件中,添加以下设置。

dotenv: .env
wallets:
    from_key: ${PRIVATE_KEY}

RandomNumberGen.sol 中创建一个getRandomness() 函数。它检查你是否有足够的链接来资助交易。

它还要求使用keyhash得到一个可验证的随机数。

    function getRandomness() public returns (bytes32) {
        require(
            LINK.balanceOf(address(this)) >= fee,
            "Inadequate Link to fund this transaction"
        );
        return requestRandomness(keyHash, fee);
    }

现在,VRF协调器产生一个可验证的随机数。这是通过重写RandomNumberGen.sol 中的fulfillRandomness 函数。

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

requestId 是唯一的ID,用于识别你在区块链上的随机数。

添加一个rollDice函数,在1到6之间随机选择一个数字。

function rollDice() public view returns (uint256) {
        require(randomNumber > 0, "Random number has not yet been obtained");
        return randomNumber % 6;
    }

最后的合约应该是这样的。

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

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

contract RandomNumberGen is VRFConsumerBase {
    bytes32 internal keyHash;
    uint256 internal fee;
    uint256 public randomNumber;

    constructor(
        address _vrfCoordinator,
        address _link,
        bytes32 _keyHash,
        uint256 _fee
    ) VRFConsumerBase(_vrfCoordinator, _link) {
        keyHash = _keyHash;
        fee = _fee;
    }

    function getRandomness() public returns (bytes32) {
        require(
            LINK.balanceOf(address(this)) >= fee,
            "Inadequate Link to fund this transaction"
        );
        return requestRandomness(keyHash, fee);
    }

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

    function rollDice() public view returns (uint256) {
        require(randomNumber >= 0, "Random number has not yet been obtained");
        return (randomNumber % 6) + 1;
    }
}

运行下面的命令来编译该程序。

brownie compile

最终的brownie-config.yaml 文件应该如下图所示。

dotenv: .env
dependencies:
  - "smartcontractkit/chainlink@1.1.0"
compiler:
  solc:
    remappings:
      - "@chainlink=smartcontractkit/chainlink@1.1.0"
networks:
  rinkeby:
    vrf_coordinator: "0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B"
    link: "0x01BE23585060835E02B77ef475b0Cc51aA1e0709"
    key_hash: "0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311"

wallets:
  from_key: "${PRIVATE_KEY}"

使用Brownie进行部署

第一步是确保你的brownie虚拟环境正常运行。

接下来,你需要使用brownie将智能合约部署到区块链上。在scripts文件夹下,创建一个名为helpful_scripts.py 的Python文件。

在这个文件中,创建一个辅助函数,并将其命名为get_account() 。这个函数将帮助我们获得一个部署智能合约的账户。

from brownie import accounts, config
def get_account():
    account = accounts.add(config["wallets"]["from_key"])
    return account
  • accounts 是提供账户的brownie库。它可以使用 ,创建假的虚拟账户。它也可以使用你的私钥访问你的真实账户。ganache
  • config 是访问你之前创建的 文件的brownie库。brownie-config.yaml

scripts文件夹中创建另一个deploy_contract.py 文件。这个文件将把你的智能合约部署到区块链上。

from scripts.helpful_scripts import get_account
from brownie import RandomNumberGen, network, config

def get_contract():
    account = get_account()
    fee = 0.1 * 10**18
    contract = RandomNumberGen.deploy(
        config["networks"][network.show_active()]["vrf_coordinator"],
        config["networks"][network.show_active()]["link"],
        config["networks"][network.show_active()]["key_hash"],
        fee,
        {"from": account},
    )

def main():
    get_contract()

10**18将费用从Eth转换为Wei。Wei是Ethereum交易中最小的面额。

RandomNumberGen 是你在Solidity中创建的智能合约。

函数network.show_active() ,当它被调用时将返回rinkeby

brownie run deploy_contract --network rinkeby

RandomNumberGen智能合约现在应该成功部署在Rinkeby Ethereum区块链上。

用LINK资助合同

由于智能合约已经被部署,我们需要获得一个随机数。要做到这一点,首先需要用Link ,为部署的合约提供资金。

Link 是Chainlink的代币。他们用它来收取使用其神谕的费用。在这种情况下,你需要付费来获得随机数。

你将不得不把这个link ,从你的账户转移到智能合约上。要做到这一点,你将需要来自Chainlink的Link Token合约。

合约文件夹中创建一个LinkToken.sol 。接下来,复制并粘贴整个代码。你唯一需要改变的部分是导入

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

import "@chainlink/contracts/src/v0.4/ERC677Token.sol";
import {StandardToken as linkStandardToken} from "@chainlink/contracts/src/v0.4/vendor/StandardToken.sol";

contract LinkToken is linkStandardToken, ERC677Token {
    ....
}

运行brownie编译以确保一切工作正常。

brownie compile

现在在helpful_scripts.py 中创建另一个辅助函数fund_with_link(contract_address) 。这个函数将在参数中为contract address 输入的链接提供资金。

from brownie import Contract, accounts, network, config, LinkToken

def fund_with_link(contract_address):
    account = get_account()
    link = Contract.from_abi(
        LinkToken._name,
        config["networks"][network.show_active()]["link"],
        LinkToken.abi,
    )
    fee = 0.1 * 10 ** 18
    tx = link.transfer(contract_address, fee, {"from": account})
    tx.wait(1)
    print("Contract funded with link successfully")
    return tx

链接代币已经存在于区块链中。因此,你使用Contract.from_abi() ,它接收合同名称合同在区块链中的地址,以及abi

abi 代表应用二进制接口。这是与Ethereum区块链上的智能合约进行交互的标准化方式。

tx.wait(1) 等待一个交易完成后再继续。

我们需要在deploy_contract.py 文件中调用这个函数。

from brownie import RandomNumberGen, accounts, config, network

from scripts.helpful_scripts import fund_with_link, get_account

def deploy_contract():
    ...
    fund_with_link(contract.address)

生成随机数

由于合约现在已经有了资金链接,它已经准备好生成随机数了。

def deploy_contract():
    ...
    tx = contract.getRandomness({"from": account})

最后的deploy_contract.py 应该是这样的。

import time
from brownie import RandomNumberGen, accounts, config, network
from scripts.helpful_scripts import fund_with_link, get_account

def main():
    deploy_contract()

def deploy_contract():
    account = get_account()
    fee = 0.1 * 10 ** 18
    contract = RandomNumberGen.deploy(
        config["networks"][network.show_active()]["vrf_coordinator"],
        config["networks"][network.show_active()]["link"],
        config["networks"][network.show_active()]["key_hash"],
        fee,
        {"from": account},
    )
    fund_with_link(contract.address)
    tx = contract.getRandomness({"from": account})
    tx.wait(1)
    time.sleep(200)
    print(f"The random number is {contract.randomNumber()}")
    print(f"The dice rolled is {contract.rollDice()}")

time.sleep(200) 是必不可少的,因为随机数需要一些时间来反映在智能合约上。

运行下面的命令来部署该合约。

brownie run deploy_contract --network rinkeby

结论

创建不可预测的随机数给计算机科学家带来了挑战。使用计算机生成随机数很困难。

然而,Chainlink使用chainlink vrfs解决了这个问题。他们的随机数还没有被黑客攻击或被恶意的人利用。

本教程希望能阐明如何利用链式信标来获得真正可验证的随机数。