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并接收结果。
我们将引入 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 代币合约的地址。这些在每个网络上合约地址是固定的(稍后会详细介绍)。
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
来保存接收变量的值。
正如在前面 第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网络,如下图所示:
0x20fE562d797A42Dcb3399062AE9546cd06f63280
. 剩下的信息将自动填充,提交之后可以看到账号下有 100 个 LINK,下图是 70 个 LINK 的账号截图:回到 Remix, 复制部署合约地址,如下图:
现在我们将向合约发送一些 LINK。回到Metamask,然后单击100 LINK旁边的3个点。粘贴合约地址并发送10 LINK。确认交易后,再继续下一步。
在Remix中,我们现在可以请求随机数了。在同一选项卡中,向下滚动会发现更多代表合约公有(public)函数的橙色按钮,如下图所示。单击requestRandomness
右侧的箭头以展开参数。
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
登链社区赞助翻译。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!