使用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;
}
在这一点上,你需要获得vrfCoordinator ,link 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库。它可以使用 ,创建假的虚拟账户。它也可以使用你的私钥访问你的真实账户。ganacheconfig是访问你之前创建的 文件的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解决了这个问题。他们的随机数还没有被黑客攻击或被恶意的人利用。
本教程希望能阐明如何利用链式信标来获得真正可验证的随机数。