Chainlink 如何解决以太坊“随机数问题”.
随机数和区块链一直很难达到“一致”(译者注:区块链要求确定性,而随机数正相反)。到目前为止,区块链上还没有可验证的随机函数。
原因是:交易被旷工出块后,需要网络上的多个节点来确认才算真实有效。就要求每个节点验证时都必须得出相同的结果。如果函数是随机的(每次运行的结果不一样),则每个节点将得出不同的结果,从而导致交易得不到确认。
有一些解决(变通)方法可以生成一些 伪随机生成,但到目前为止,已有的方法都不算是真正的随机,或存在操控的可能。
登链社区之前也有一篇译文:区块链上生成随机数 大家可以读一读。
Chainlink 网络可以为任何区块链上的复杂智能合约提供可靠的防篡改输入和输出。 —来自 chain.link 官网的介绍
区块链和智能合约针对一组不可变的规则执行计算是个很棒的平台。问题是规则只能应用于系统内部的数据。而如果要从系统外部获取可验证的数据则非常困难。
Chainlink 想要通过提供去中心化的预言机来解决这个问题,使区块链能够通过 Chainlink 访问生态系统之外的数据。预言机(Oracles)实质上是区块链和外部世界之间的桥梁。
在最近的一篇文章中,Chainlink 宣布发布了其新的可验证随机函数(VRF)。开发者现在可以使用该功能将其集成到多个测试网上的 DApp 中,从而使智能合约能够获得可在链上验证的随机数。
如果你想在 JavaScript 中生成一个随机数,代码非常简单:
Math.random();
每执行一次,生成一个随机数。然而这不是 VRF 的工作方式。与 JavaScript 不同,VRF 是在一些交易实现的。
以下是 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 测试网上,合约地址如下:
0xf720CF1B963e0e7bE9F58fd471EFa67e7bF00cfb
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 插件。
RandomGenerator
,把第 2 步中的代码复制过来。然后单击下面的按钮,并在下拉列表中选择“Injected web3”,如下图所示。
0x20fE562d797A42Dcb3399062AE9546cd06f63280
. 剩下的信息将自动填充,提交之后可以看到账号下有 100 个 LINK,下图是 70 个 LINK 的账号截图: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
欢迎大家在知乎,微博,GitHub 关注我
[登链社区](https://learnblockchain.cn/)赞助翻译。