本文是按照以下教程编写的,在BNB测试网上运行失败,发现了ChainLink帮助文档中的参数错误,修正后才运行成功。TESTNET Binance (BNB) Blockchain Explorer

------------------------------------------------------------------------------------------------------------------

原文:3939. 链上随机数 | WTF学院39

很多以太坊上的应用都需要用到随机数,例如NFT随机抽取tokenId、抽盲盒、gamefi战斗中随机分胜负等等。但是由于以太坊上所有数据都是公开透明(public)且确定性(deterministic)的,它没法像其他编程语言一样给开发者提供生成随机数的方法。这一讲我们将介绍链上(哈希函数)和链下(chainlink预言机)随机数生成的两种方法,并利用它们做一款tokenId随机铸造的NFT

链上随机数生成​

我们可以将一些链上的全局变量作为种子,利用keccak256()哈希函数来获取伪随机数。这是因为哈希函数具有灵敏性和均一性,可以得到“看似”随机的结果。下面的getRandomOnchain()函数利用全局变量block.numbermsg.senderblockhash(block.timestamp-1)作为种子来获取随机数:

    /** * 链上伪随机数生成* 利用keccak256()打包一些链上的全局变量/自定义变量* 返回时转换成uint256类型*/function getRandomOnchain() public view returns(uint256){// remix运行blockhash会报错bytes32 randomBytes = keccak256(abi.encodePacked(block.number, msg.sender, blockhash(block.timestamp-1)));return uint256(randomBytes);}

注意:,这个方法并不安全:

  • 首先,block.numbermsg.senderblockhash(block.timestamp-1)这些变量都是公开的,使用者可以预测出用这些种子生成出的随机数,并挑出他们想要的随机数执行合约。
  • 其次,矿工可以操纵blockhashblock.timestamp,使得生成的随机数符合他的利益。

尽管如此,由于这种方法是最便捷的链上随机数生成方法,大量项目方依靠它来生成不安全的随机数,包括知名的项目meebitsloots等。当然,这些项目也无一例外的被攻击了:攻击者可以铸造任何他们想要的稀有NFT,而非随机抽取。

链下随机数生成​

我们可以在链下生成随机数,然后通过预言机把随机数上传到链上。Chainlink提供VRF(可验证随机函数)服务,链上开发者可以支付LINK代币来获取随机数。 Chainlink VRF有两个版本,因为第二个版本需要官网注册并预付费,且用法类似,这里只介绍第一个版本VRF v1

Chainlink VRF使用步骤​

我们将用一个简单的合约介绍使用Chainlink VRF的步骤。RandomNumberConsumer合约可以向VRF请求一个随机数,并存储在状态变量randomResult中。

1. 用户合约继承VRFConsumerBase并转入LINK代币

为了使用VRF获取随机数,合约需要继承VRFConsumerBase合约,并在构造函数中初始化VRF Coordinator地址,LINK代币地址,唯一标识符Key Hash,和使用费用fee

注意: 不同链对应不同的参数,在这里查询。

教程中我们使用Rinkeby测试网。部署好合约后,用户需要向合约转一些LINK代币,测试网的LINK代币可以从LINK水龙头领取。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";contract RandomNumberConsumer is VRFConsumerBase {bytes32 internal keyHash; // VRF唯一标识符uint256 internal fee; // VRF使用手续费uint256 public randomResult; // 存储随机数/*** 使用chainlink VRF,构造函数需要继承 VRFConsumerBase * 不同链参数填的不一样* 网络: Rinkeby测试网* Chainlink VRF Coordinator 地址: 0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B* LINK 代币地址: 0x01BE23585060835E02B77ef475b0Cc51aA1e0709* Key Hash: 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311*/constructor() VRFConsumerBase(0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B, // VRF Coordinator0x01BE23585060835E02B77ef475b0Cc51aA1e0709  // LINK Token){keyHash = 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311;fee = 0.1 * 10 ** 18; // 0.1 LINK (VRF使用费,Rinkeby测试网)}

2. 用户合约申请随机数

用户可以调用从VRFConsumerBase合约继承来的requestRandomness()申请随机数,并返回申请标识符requestId。这个申请会传递给VRF合约。

    /** * 向VRF合约申请随机数 */function getRandomNumber() public returns (bytes32 requestId) {// 合约中需要有足够的LINKrequire(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");return requestRandomness(keyHash, fee);}

3. Chainlink节点链下生成随机数和数字签名,并发送给VRF合约

4. VRF合约验证签名有效性

5. 用户合约接收并使用随机数

VRF合约验证签名有效之后,会自动调用用户合约的回退函数fulfillRandomness(),将链下生成的随机数发送过来。用户要把消耗随机数的逻辑写在这里。

注意: 用户申请随机数时调用的requestRandomness()VRF合约返回随机数时调用的回退函数fulfillRandomness()是两笔交易,调用者分别是用户合约和VRF合约,后者比前者晚几分钟(不同链延迟不一样)。

    /*** VRF合约的回调函数,验证随机数有效之后会自动被调用* 消耗随机数的逻辑写在这里*/function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {randomResult = randomness;}

tokenId随机铸造的NFT

这一节,我们将利用链上和链下随机数来做一款tokenId随机铸造的NFTRandom合约继承ERC721VRFConsumerBase合约。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;import "https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/ERC721.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";contract Random is ERC721, VRFConsumerBase{

状态变量​

  • NFT相关

    • totalSupplyNFT总供给。
    • ids:数组,用于计算可供minttokenId,见pickRandomUniqueId()函数。
    • mintCount:已经mint的数量。
  • Chainlink VRF相关
    • keyHash:VRF唯一标识符。
    • feeVRF手续费。
    • requestToSender:记录申请VRF用于铸造的用户地址。
    // NFT相关uint256 public totalSupply = 100; // 总供给uint256[100] public ids; // 用于计算可供mint的tokenIduint256 public mintCount; // 已mint数量// chainlink VRF相关bytes32 internal keyHash;uint256 internal fee;// 记录VRF申请标识对应的mint地址mapping(bytes32 => address) public requestToSender;

构造函数​

初始化继承的VRFConsumerBaseERC721合约的相关变量。

    /*** 使用chainlink VRF,构造函数需要继承 VRFConsumerBase * 不同链参数填的不一样* 网络: BNB测试网* Chainlink VRF Coordinator 地址: 0xa555fC018435bef5A13C6c6870a9d4C11DEC329C* LINK 代币地址: 0x84b9B910527Ad5C03A9Ca831909E21e236EA7b06* Key Hash: 0xcaf3c3727e033261d383b315559476f48034c13b18f8cafed4d871abe5049186*/constructor() VRFConsumerBase(0xa555fC018435bef5A13C6c6870a9d4C11DEC329C, // VRF Coordinator0x84b9B910527Ad5C03A9Ca831909E21e236EA7b06// LINK Token)ERC721("WTF Random", "WTF"){keyHash = 0xcaf3c3727e033261d383b315559476f48034c13b18f8cafed4d871abe5049186;fee = 0.1 * 10 ** 18; // 0.1 LINK (VRF使用费,BNB测试网)}

其他函数​

除了构造函数以外,合约里还定义了5个函数。

  • pickRandomUniqueId():输入随机数,获取可供minttokenId

  • getRandomOnchain():获取链上随机数(不安全)。

  • mintRandomOnchain():利用链上随机数铸造NFT,调用了getRandomOnchain()pickRandomUniqueId()

  • mintRandomVRF():申请Chainlink VRF用于铸造随机数。由于使用随机数铸造的逻辑在回调函数fulfillRandomness(),而回调函数的调用者是VRF合约,而非铸造NFT的用户,这里必须利用requestToSender状态变量记录VRF申请标识符对应的用户地址。

  • fulfillRandomness()VRF的回调函数,由VRF合约在验证随机数真实性后自动调用,用返回的链下随机数铸造NFT

    /** * 输入uint256数字,返回一个可以mint的tokenId*/function pickRandomUniqueId(uint256 random) private returns (uint256 tokenId) {uint256 len = totalSupply - mintCount++; // 可mint数量require(len > 0, "mint close"); // 所有tokenId被mint完了uint256 randomIndex = random % len; // 获取链上随机数//随机数取模,得到tokenId,作为数组下标,同时记录value为len-1,如果取模得到的值已存在,则tokenId取该数组下标的valuetokenId = ids[randomIndex] != 0 ? ids[randomIndex] : randomIndex; // 获取tokenIdids[randomIndex] = ids[len - 1] == 0 ? len - 1 : ids[len - 1]; // 更新ids 列表ids[len - 1] = 0; // 删除最后一个元素,能返还gas}/** * 链上伪随机数生成* keccak256(abi.encodePacked()中填上一些链上的全局变量/自定义变量* 返回时转换成uint256类型*/function getRandomOnchain() public view returns(uint256){// remix跑blockhash会报错bytes32 randomBytes = keccak256(abi.encodePacked(block.number, msg.sender, blockhash(block.timestamp-1)));return uint256(randomBytes);}// 利用链上伪随机数铸造NFTfunction mintRandomOnchain() public {uint256 _tokenId = pickRandomUniqueId(getRandomOnchain()); // 利用链上随机数生成tokenId_mint(msg.sender, _tokenId);}/** * 调用VRF获取随机数,并mintNFT* 要调用requestRandomness()函数获取,消耗随机数的逻辑写在VRF的回调函数fulfillRandomness()中* 调用前,把LINK代币转到本合约里*/function mintRandomVRF() public returns (bytes32 requestId) {// 检查合约中LINK余额require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");// 调用requestRandomness获取随机数requestId = requestRandomness(keyHash, fee);requestToSender[requestId] = msg.sender;return requestId;}/*** VRF的回调函数,由VRF Coordinator调用* 消耗随机数的逻辑写在本函数中*/function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {address sender = requestToSender[requestId]; // 从requestToSender中获取minter用户地址uint256 _tokenId = pickRandomUniqueId(randomness); // 利用VRF返回的随机数生成tokenId_mint(sender, _tokenId);}

remix验证​

1. 在BNB测试网部署Random合约

2. 利用Chainlink水龙头获取测试网的LINKETH

3. 将LINK代币转入Random合约​

合约部署后,拷贝合约地址,像普通转账一样,通过小狐狸钱包转账LINK到合约地址

4. 利用链上随机数铸造NFT

remix界面中,点击左侧橙色函数mintRandomOnchain,在弹出的小狐狸钱包中点击确认,利用链上随机数铸造交易就开始了。

这里看到使用伪随机数生成的tokenId是55,铸造给了用户。

5. 利用Chainlink VRF链下随机数铸造NFT

同理,在remix界面中,点击左侧橙色函数mintRandomVRF,在弹出的小狐狸钱包中点击确认,利用Chainlink VRF链下随机数铸造交易就开始了。

注意: 采用VRF铸造NFT时,发起交易和铸造成功不在同一个区块。 注意观察这笔交易给ChainLink的合约发送了0.1Link作为手续费。如果合约账户余额不足,是会出现错误的。

6. 验证NFT已被铸造​

等待一会儿,在查看合约的“Internal Txns”,看到出现了回调交易:

查看这笔交易,tokenId=30NFT被回调函数铸造出来。

总结​

Solidity中生成随机数没有其他编程语言那么容易。这一讲我们将介绍链上(哈希函数)和链下(chainlink预言机)随机数生成的两种方法,并利用它们做一款tokenId随机铸造的NFT。这两种方法各有利弊:使用链上随机数高效,但是不安全;而链下随机数生成依赖于第三方提供的预言机服务,比较安全,但是没那么简单经济。项目方要根据业务场景来选择适合自己的方案。

除此以外,还有一些组织在尝试RNG(Random Number Generation)的新鲜方式,如randao就提出以DAO的模式来提供一个on-chaintrue randomness的服务

------------------------------------------------------------------------------------------------------------

【错误修正】原文使用的ChainLink参数是Rinkeby测试网,现在已经停用,我改用币安智能链测试网。根据ChainLink的配置文档:Configuration | Chainlink Documentation ,参数如下:

代码中填写这组参数是有问题的,一旦调用mintRandomVRF()函数,就报告错误:

这个错误莫名其妙,在网上也有人遇到并提问:调用chainlinkVRF获取随机数报错revert:0x8129bbcd 。

解决方法:

我查找了BNB测试网上的一个开源合约,观察里面如何调用ChainLink函数,发现使用的参数与文档中的不一样,使用这组参数合约调用就正常了。

参考合约:LotteryContract | Address 0x8773ee7555916a669c0472b67adfb5a8170f505b | BscScan

WTF Solidity极简入门: 39链上随机数相关推荐

  1. WTF Solidity极简入门: 35. 荷兰拍卖

    这一讲,我将介绍荷兰拍卖,并通过简化版Azuki荷兰拍卖代码,讲解如何通过荷兰拍卖发售ERC721标准的NFT. 荷兰拍卖​ 荷兰拍卖(Dutch Auction)是一种特殊的拍卖形式. 亦称&quo ...

  2. [转载]芋道 Soul 极简入门(国产微服务网关)

    摘要: 原创出处 http://www.iocoder.cn/Soul/install/ 「芋道源码」欢迎转载,保留摘要,谢谢! 由于原著写作时间时间有点久了:有部分类容需要更新,后去个人会再发布文章 ...

  3. .Net Core in Docker极简入门(下篇)

    点击上方蓝字"小黑在哪里"关注我吧 Docker-Compose 代码修改 yml file up & down 镜像仓库 前言 上一篇[.Net Core in Dock ...

  4. Nginx 极简入门教程

    Nginx 极简入门教程 基本介绍 Nginx 是一个高性能的 HTTP 和反向代理 web 服务器,同时也提供了 IMAP/POP3/SMTP服务. Nginx 是由伊戈尔·赛索耶夫为俄罗斯访问量第 ...

  5. SkyWalking 极简入门

    1. 概述 1.1 概念 SkyWalking 是什么? FROM http://skywalking.apache.org/ 分布式系统的应用程序性能监视工具,专为微服务.云原生架构和基于容器(Do ...

  6. Seata 极简入门

    1. 概述 Seata 是阿里开源的一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务. 1.1 四种事务模式 Seata 目标打造一站式的分布事务的解决方案,最终会提供四种事务 ...

  7. RL极简入门:从MDP、DP MC TC到Q函数、策略学习、PPO

    前言 22年底/23年初ChatGPT大火,在写ChatGPT通俗笔记的过程中,发现ChatGPT背后技术涉及到了RL/RLHF,于是又深入研究RL,研究RL的过程中又发现里面的数学公式相比ML/DL ...

  8. APISIX 极简入门(国产微服务网关)

    1. 概述 APISIX 是基于 OpenResty + etcd 实现的云原生.高性能.可扩展的微服务 API 网关.它是国人开源,目前已经进入 Apache 进行孵化,牛逼!!! OpenRest ...

  9. 机器学习极简入门课程

    开篇词 | 入门机器学习,已迫在眉睫 大家好,我是李烨.现就职于微软(Microsoft),曾在易安信(EMC)和太阳微系统(Sun Microsystems)任软件工程师.先后参与过聊天机器人.大数 ...

最新文章

  1. python英语字典程序-Pyqt5实现英文学习词典
  2. yum 下载RPM包而不进行安装
  3. Spring Boot freemarker导出word下载
  4. 程序员想进大公司?学会这门编程知识,决定你能进什么样的企业!
  5. 小程序字符串拼接_小程序突袭预约!Yeezy 350quot;氧化满天星quot;拼接配色本月发售!...
  6. BCS冬奥主题活动日:奥运网络安全成全球关注焦点
  7. Docker 架构(二)【转】
  8. 全新2021款 Jlink隔离器,ARM仿真器隔离,Jlink,Nu-link,ULINK的隔离,Cortex-M系列隔离仿真
  9. 使用网络协议分析仪Wireshark
  10. Python 计算平方数
  11. 初中计算机 课题研究,初中信息技术的教研课题题目
  12. ChucK初步(1)
  13. DVWA靶场——下载与安装(全)
  14. 大型互联网公司如何防止黑客入侵?(下)
  15. 设计LDO电路需考虑因素
  16. 计算机技能培训心得,计算机技能培训心得感想.doc
  17. 一文了解新型AMM方案Caspian,解决L2导致的流动性碎片化问题
  18. uboot 或者 linux 下限制 sata speed
  19. cdma2000解析_LTE和CDMA2000互操作方案分析
  20. 原创| Python中“等于“到底用 == 还是 is ?

热门文章

  1. H265码流分析详解
  2. k8s中部署traefik并开启https支持
  3. 驰为v10刷linux,驰为Vi10刷机包-驰为Vi10刷机工具下载v6.0.43 官方最新版-西西软件下载...
  4. html中水平时间轴,CSS3 水平时间轴/步骤引导环节
  5. python计算极限,Python的极限是什么?
  6. 《解析极限编程》读书笔记
  7. MADDPG论文理解
  8. Crontab 简单实现树莓派语音闹钟
  9. h5学习笔记之canvas绘图(1)
  10. 脑科学读物阅读笔记系列 - 拉马钱德兰《脑中魅影》- 3.追踪幻影