如何使用VRF(可验证随机函数)在以太坊上生成随机数
随机数和区块链一直达到“一致”(译者注:区块链要求确定性,而随机数正相反)。到目前为止,区块链上还没有可验证的随机函数。
原因是:交易被旷工出块后,需要网络上的多个节点来确认才算真实有效。就要求每个节点验证时都必须得出相同的结果。如果函数是随机的(每次运行的结果不一样),则每个节点将得出不同的结果,从而导致交易得不到确认。
有一些解决(变通)方法可以生成一些 伪随机生成 ,但到目前为止,已有的方法都不算是真正的随机,或存在操控的可能。
登链社区之前也有一篇译文:区块链上生成随机数 大家可以读一读。
关于 chainlink
Chainlink网络可以为任何区块链上的复杂智能合约提供可靠的防篡改输入和输出。 —来自 chain.link 官网的介绍
区块链和智能合约针对一组不可变的规则执行计算是个很棒的平台。问题是规则只能应用于系统内部的数据。而如果要从系统外部获取可验证的数据则非常困难。
Chainlink想要通过提供去中心化的预言机来解决这个问题,使区块链能够通过Chainlink访问生态系统之外的数据。预言机(Oracles)实质上是区块链和外部世界之间的桥梁。
真正的随机
在最近的一篇文章中, Chainlink宣布发布了其新的可验证随机函数(VRF) 。开发者现在可以使用该功能将其集成到多个测试网上的DApp中,从而使智能合约能够获得可在链上验证的随机数。
可验证随机函数是怎么实现的?
如果你想在Javascript中生成一个随机数,代码非常简单:
Math.random();
每执行一次,生成一个随机数。然后这不是 VRF的工作方式 。与Javascript不同,VRF是在一些交易实现的。
以下是 VRF 事件发生的顺序:
-
你的智能合约 通过交易 向VRF请求一个随机数。
-
VRF会生成该随机数字并进行验证。
-
VRF准备响应1 的请求。
-
VRF通过 另一笔交易 将随机数字发送回你的智能合约。
为了使第4步成功,你的合约需要实现一个确定的函数,以便VRF调用以返回结果。如何在项目中实现呢?
如何实现随机性
让我们创建一个名为 RandomGenerator
的新合约,在合约里我们将调用VRF并接收结果。
第 1 步: 创建消费者合约
我们将引入 Chainlink提供的 VRFConsumerBase
的合约,这是一个抽象合约,它定义了一个获取和消耗VRF的最少实现(后面也会列出 VRFConsumerBase
的代码),我们定义“ RandomGenerator.sol”文件开头像这样:
pragma solidity ^0.6.2; import "https://raw.githubusercontent.com/smartcontractkit/chainlink/develop/evm-contracts/src/v0.6/VRFConsumerBase.sol"; contract RandomGenerator is VRFConsumerBase { constructor(address _vrfCoordinator, address _link) VRFConsumerBase(_vrfCoordinator, _link) public { } }
VRFConsumerBase
的合约的源码如下:
pragma solidity 0.6.2; import "./vendor/SafeMath.sol"; import "./interfaces/LinkTokenInterface.sol"; import "./VRFRequestIDBase.sol"; abstract contract VRFConsumerBase is VRFRequestIDBase { using SafeMath for uint256; function fulfillRandomness(bytes32 requestId, uint256 randomness) external virtual; function requestRandomness(bytes32 _keyHash, uint256 _fee, uint256 _seed) public returns (bytes32 requestId) { LINK.transferAndCall(vrfCoordinator, _fee, abi.encode(_keyHash, _seed)); // This is the seed actually passed to the VRF in VRFCoordinator uint256 vRFSeed = makeVRFInputSeed(_keyHash, _seed, address(this), nonces[_keyHash]); // nonces[_keyHash] must stay in sync with // VRFCoordinator.nonces[_keyHash][this], which was incremented by the above // successful LINK.transferAndCall (in VRFCoordinator.randomnessRequest) nonces[_keyHash] = nonces[_keyHash].add(1); return makeRequestId(_keyHash, vRFSeed); } LinkTokenInterface internal LINK; address internal vrfCoordinator; // Nonces for each VRF key from which randomness has been requested. // // Must stay in sync with VRFCoordinator[_keyHash][this] mapping(bytes32 /* keyHash */ => uint256 /* nonce */) public nonces; constructor(address _vrfCoordinator, address _link) public { vrfCoordinator = _vrfCoordinator; LINK = LinkTokenInterface(_link); } }
VRFConsumerBase
仍在后期测试中,因此还没有产品软件包对外提供。这就是为什么使用Github的HTTP URL进行导入的原因。
VRFConsumerBase
抽象合约有两个参数,分别代表协调器(coordinator)和LINK ERC20 代币合约的地址。这些在每个网络上合约地址是固定的(稍后会详细介绍)。
第 2 步: 重载函数
VRFConsumerBase
中有两个对VRF流程至关重要的函数。
第一个是 requestRandomness
,这个函数已经实现了,我们不需要重载。这个函数是用来对VRF进行 初始请求调用 。
另一个是 fulfillRandomness
, 这是VRF在生成数字后,用来回调的函数。我们需要重载它,以便在获取随机数后执行相应的操作。
在我们合约的实现里,仅仅是把随机数存储在一个名为 randomNumber
的状态变量中,以便我们可以在结束时查询它。代码像这样:
pragma solidity ^0.6.2; import "https://raw.githubusercontent.com/smartcontractkit/chainlink/develop/evm-contracts/src/v0.6/VRFConsumerBase.sol"; contract RandomGenerator is VRFConsumerBase { bytes32 public reqId; uint256 public randomNumber; constructor(address _vrfCoordinator, address _link) VRFConsumerBase(_vrfCoordinator, _link) public { } function fulfillRandomness(bytes32 requestId, uint256 randomness) external override { reqId = requestId; randomNumber = randomness; } }
我们在 fulfillRandomness
函数上添加了override 修饰符以实现重载,在实现中,使用 reqId
和 randomNumber
来保存接收变量的值。
第 3 步: 生成随机数
正如在前面 第1步提到的,函数调用需要传递一些地址和其他值作为参数。在部署智能合约并调用构造函数时,它需要VRF协调器(coordinator)合约地址和网络上LINK 代币合约地址。在Ropsten测试网上,合约地址如下:
-
VRF coordinator:
0xf720CF1B963e0e7bE9F58fd471EFa67e7bF00cfb
-
LINK 代币:
0x20fE562d797A42Dcb3399062AE9546cd06f63280
当调用 requestRandomness
函数时,我们需要传递几个参数:生成随机数的key hash,生成随机数的费用fee(使用LINK代币)和生成随机性的种子seed(最后一个由我们提供)。requestRandomness函数签名如下:
function requestRandomness(bytes32 _keyHash, uint256 _fee, uint256 _seed) public returns (bytes32 requestId)
在 Ropsten 网络上,参数值如下:
0xced103054e349b8dfb51352f0f8fa9b5d20dde3d06f9f43cb2b85bc64b238205 1000000000000000000
因此我们的调用代码如下:
// 设置ropsten key hash bytes32 keyHash = "0xced103054e349b8dfb51352f0f8fa9b5d20dde3d06f9f43cb2b85bc64b238205";// // 设置 ropsten LINK 费用 fee = 1000000000000000000; // 设置种子 seed = 123456789; // 请求随机数 bytes32 reqId = rand.requestRandomness(keyHash, fee, seed);
当结果返回时,随机值将存储并且可以通过以下方法获取:
rand.randomNumber;
自己尝试一下
现在我们将逐步实践如何使用 Remix IDE
和Metamask插件从VRF获取随机数。在继续之前,请确保已在浏览器上安装了Metamask 插件。
-
打开 Remix IDE
-
如果还没用过 Remix,需要向下图一样选择 Solidity 语言。
-
创建一个文件:
RandomGenerator
,把第 2 步中的代码复制过来。 -
使用左侧菜单,单击Solidity图标,然后选择0.6.2编译器版本,如下图所示。
然后单击下面的按钮,并在下拉列表中选择“Injected web3”,如下图所示。
-
这时Metamask会提示一个连接请求,我们点击接受请求。
-
确保MetaMask 连接的是 Ropsten网络,如下图所示:
- 确保Metamask帐户中有一些Ropsten 网络的 以太币,如果没有,可以从 这里 获取。
- 回到Remix,在同一选项卡上,应该看到橙色的“ Deploy”按钮,单击“ Deploy”按钮进行部署,注意要接受Metamask弹出的合约部署请求。
- 在部署后,我们需要确保合约中存有一些LINK 代币,以便它可以为请求随机数支付费用。打开 Ropsten LINK的“水龙头”,粘贴Metamask地址,就可以在Metamask中收到100 LINK。
- Metamask不知道LINK 代币在Ropsten网络上的地址,因此我们需要添加它。在“ Metamask”中,在帐户名称左侧,单击“菜单”符号,然后单击底部的 “Add Token”。
- 在 “Custom Token(自定义代币)”下, 添加地址:
0x20fE562d797A42Dcb3399062AE9546cd06f63280
. 剩下的信息将自动填充,提交之后可以看到账号下有 100 个 LINK,下图是 70 个 LINK 的账号截图:
-
回到 Remix, 复制部署合约地址,如下图:
-
现在我们将向合约发送一些 LINK。回到Metamask,然后单击100 LINK旁边的3个点。粘贴合约地址并发送10 LINK。确认交易后,再继续下一步。
-
在Remix中,我们现在可以请求随机数了。在同一选项卡中,向下滚动会发现更多代表合约公有(public)函数的橙色按钮,如下图所示。单击
requestRandomness
右侧的箭头以展开参数。
- 按顺序复制这 3 个数:
0xced103054e349b8dfb51352f0f8fa9b5d20dde3d06f9f43cb2b85bc64b238205
,1000000000000000000
,123456789
(或者其他你想要的值)作为参数提交交易。
交易可能需要一些时间才能确定,因此需要关注一下终端输出在Etherscan中的交易信息。
交易完成后,我们需要等待VRF生成随机数并将其发送回我们的合约。几分钟后,单击我们在Remix中发送交易的橙色按钮下方的蓝色“ randomNumber”按钮,检查合约是否收到了随机数,如下图所示。
如果一切顺利,应该有一个像我这样的随机数,它是 30207470459964961279215818016791723193587102244018403859363363849439350753829
.
现在就大功告成了。
结论
使用 Chainlink 可以在智能合约中可以使用可验证的随机数。在文章中阐述了该机制的工作原理,以及演示了如何将代码集成到智能合约中获取随机数
作者:Alex Roan
原文: https://medium.com/coinmonks/how-to-generate-random-numbers-on-ethereum-using-vrf-8250839dd9e2
登链社区赞助翻译。