预言机
预言机是将以太坊连接到链外、真实世界信息的数据源,以便您可以在智能合约中查询数据。 例如,预测市场的去中心化应用程序使用预言机来根据事件进行结算。 预测市场可能要求您使用以太币来打赌下一任美国总统是谁。 他们将使用一个预言机来确认结果并向获胜者付款。
前提条件
什么是预言机
预言机是区块链与真实世界之间的桥梁。 他们充当在链上的应用程序接口,你可以查询以获取信息到你的智能合约。 可以是任何信息,从价格信息到天气预报。 预言机可以是双向的,用于向真实世界“发送”数据。
观看 Patrick 解释预言机:
为什么需要预言机?
使用以太坊这样的区块链,您需要确保:网络中每个节点都能够重放每个交易,并最终产生同样的结果。 应用程序接口可能会引入可变数据。 如果使用价格应用程序接口根据商定的 $USD 值发送以太币,查询将在不同日期返回不同的结果。 更不用说,应用程序接口可能会被破解或弃用。 一旦发生这种情况,网络中的节点将不会接受以太坊的当前状态,从而打破共识。
预言机可通过发布数据到区块链来解决这个问题。 以便任何重放交易的节点使用所有人都能看到的相同且不变的数据。 要做到这一点,预言机通常由一个智能合约和某些可以查询应用程序接口的链下组件组成,然后定期发送交易以更新智能合约的数据。
Oracle 问题
如前所述,以太坊交易无法直接获取链下数据。 同时,依靠单一的事实来源提供数据并不安全,会使智能合约的去中心化失效。 这便是预言机的问题。
我们可以使用去中心化预言机拉取多个数据来源,以避免预言机的问题;如果某个数据源被破解或失效,智能合约仍将按预期运行。
安全性
预言机的安全性等同于其数据源。 如果一个去中心化应用程序使用 Uniswap 作为其以太币/DAI 价格的预言机,攻击者就可以在 Uniswap 上篡改价格,以操纵该去中心化应用程序对当前价格的理解。 如何对付这个隐患的示例包括一种推送系统,如 MakerDAO 所使用的推送系统。它会将来自若干外部数据源的价格数据进行比对,而不是仅仅依靠单一来源。
架构
这是一个简单预言机架构的示例,但还有更多方法可用来触发链下计算。
- 通过您的智能合约事件发出一份日志
- 一个链下服务已经订阅了(通常使用类似 JSON-RPC
eth_subscribe
的命令)这些特定的日志。 - 该链下服务会继续做一些日志所定义的任务。
- 链下服务使用智能合约二级交易中要求的数据作出回应。
如此可以 1 对 1 的方式获取数据,但是为了提高安全性,您可能希望对链下数据的收集方式进行去中心化。
下一步可能是建立这些节点的网络,将这些节点调用到不同的应用程序接口和源,并在链上汇总数据。
Chainlink 链下报告 (Chainlink OCR) 让链下预言机网络相互通信、为它们的回应附加加密签名、汇总它们的链下回应,并且仅随结果发送一个交易,以改进这种方法。 这样可以减少消耗的燃料,但您仍然可以得到去中心化数据的保证,因为每个节点都对其负责的交易进行了签名,使得发送交易的节点无法更改该交易。 如果节点不进行交易处理,而由下一个节点发送交易,上报规则将会介入。
使用方法
使用诸如 Chainlink 等服务,您可以引用链上已经从现实世界中提取并汇总的去中心化数据。 这有点像公共空间,但仅用于去中心化数据。 您也可以构建自己的模块化预言机网络来获取您想要的任意自定义数据。 此外,您可以执行链下运算并向真实世界发送信息。 Chainlink 已具备相关基础设施,以:
以下为如何从智能合约中获取最新以太币价格的示例:
Chainlink 数据源
1pragma solidity ^0.6.7;23import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";45contract PriceConsumerV3 {67 AggregatorV3Interface internal priceFeed;89 /**10 * Network: Kovan11 * Aggregator: ETH/USD12 * Address: 0x9326BFA02ADD2366b30bacB125260Af64103133113 */14 constructor() public {15 priceFeed = AggregatorV3Interface(0x9326BFA02ADD2366b30bacB125260Af641031331);16 }1718 /**19 * Returns the latest price20 */21 function getLatestPrice() public view returns (int) {22 (23 uint80 roundID,24 int price,25 uint startedAt,26 uint timeStamp,27 uint80 answeredInRound28 ) = priceFeed.latestRoundData();29 return price;30 }31}32显示全部复制
Chainlink VRF
Chainlink VRF(可验证随机函数)是为智能合约设计的可证明公平性和可验证的随机性来源。 智能合约开发者可以使用 Chainlink VRF 作为防篡改随机数生成 (RNG),为依赖不可预测结果的任何应用程序建立可靠的智能合约:
- 区块链游戏和非同质化代币
- 职责和资源的随机分配(例如随机分配审判者审理案件)
- 为共识机制选择一个有代表性的示例
随机数很难,因为区块链是决定性的。
在数据源之外使用 Chainlink 预言机需要遵循使用 Chainlink 的请求和接收周期。 它们使用 LINK 代币向预言机供应商发送预言机燃料,以获得响应。 LINK 代币专门设计用于与预言机协同作用,基于升级后的 ERC-677 代币,此代币向后兼容 ERC-20。 以下代码如果部署到 Kovan 测试网,将获得一个经加密验证的随机数。 要提出请求,请使用您可以从 Kovan LINK Faucet 获得的测试网 LINK 代币,为合约提供资金。
12pragma solidity 0.6.6;34import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";56contract RandomNumberConsumer is VRFConsumerBase {78 bytes32 internal keyHash;9 uint256 internal fee;1011 uint256 public randomResult;1213 /**14 * Constructor inherits VRFConsumerBase15 *16 * Network: Kovan17 * Chainlink VRF Coordinator address: 0xdD3782915140c8f3b190B5D67eAc6dc5760C46E918 * LINK token address: 0xa36085F69e2889c224210F603D836748e7dC008819 * Key Hash: 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f420 */21 constructor()22 VRFConsumerBase(23 0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9, // VRF Coordinator24 0xa36085F69e2889c224210F603D836748e7dC0088 // LINK Token25 ) public26 {27 keyHash = 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4;28 fee = 0.1 * 10 ** 18; // 0.1 LINK (varies by network)29 }3031 /**32 * Requests randomness from a user-provided seed33 */34 function getRandomNumber(uint256 userProvidedSeed) public returns (bytes32 requestId) {35 require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");36 return requestRandomness(keyHash, fee, userProvidedSeed);37 }3839 /**40 * Callback function used by VRF Coordinator41 */42 function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {43 randomResult = randomness;44 }45}46显示全部
Chainlink Keeper
智能合约无法在任意时间或任意条件下自动触发或执行。 只有当另一个帐户(如用户、预言机或合约)发起交易时,才会发生状态变化。 Chainlink Keeper 网络为智能合约提供了多种选择,以信任最小化和去中心化的方式将常规维护任务外包出去。
要使用 Chainlink Keeper,智能合约必须执行 KeeperCompatibleInterface,它由两个函数组成:
checkUpkeep
- 检查合约是否需要完成工作。performUpkeep
- 如果 checkUpkeep 有指示,随即执行合约内容。
以下示例是一个简单的 Counter 合约。 counter
变量通过每次调用 performUpkeep
来递增。 您可以使用 Remix 查看以下代码
1// SPDX-License-Identifier: MIT2pragma solidity ^0.7.0;34// KeeperCompatible.sol imports the functions from both ./KeeperBase.sol and5// ./interfaces/KeeperCompatibleInterface.sol6import "@chainlink/contracts/src/v0.7/KeeperCompatible.sol";78contract Counter is KeeperCompatibleInterface {9 /**10 * Public counter variable11 */12 uint public counter;1314 /**15 * Use an interval in seconds and a timestamp to slow execution of Upkeep16 */17 uint public immutable interval;18 uint public lastTimeStamp;1920 constructor(uint updateInterval) {21 interval = updateInterval;22 lastTimeStamp = block.timestamp;2324 counter = 0;25 }2627 function checkUpkeep(bytes calldata /* checkData */) external override returns (bool upkeepNeeded, bytes memory /* performData */) {28 upkeepNeeded = (block.timestamp - lastTimeStamp) > interval;29 // We don't use the checkData in this example. Upkeep 注册时会定义 checkData。30 }3132 function performUpkeep(bytes calldata /* performData */) external override {33 lastTimeStamp = block.timestamp;34 counter = counter + 1;35 // We don't use the performData in this example. Keeper 调用 checkUpkeep 函数36 }37} 时,会生成 performData38显示全部
部署了兼容 Keeper 的合约后,您必须注册 Upkeep 的合约,并用 LINK 为其添加资金。这样才能通知 Keeper 网络有关您的合约事宜,工作才可以持续进行。
Keeper 项目
Chainlink API 调用
Chainlink 应用程序接口调用是以网 络调用的传统方式(应用程序接口调用)从链下获取数据的最简单方式。 做一个这样的实例,并且只有一个预言机,使得它在本质上是集中的。 要保持它真正去中心化,智能合约平台需要使用在外部数据市场中找到的许多节点。
这也遵循了预言机的请求和接收周期,并且需要从 Kovan LINK(预言机燃料)获得资金才能工作。
1pragma solidity ^0.6.0;23import "@chainlink/contracts/src/v0.6/ChainlinkClient.sol";45contract APIConsumer is ChainlinkClient {67 uint256 public volume;89 address private oracle;10 bytes32 private jobId;11 uint256 private fee;1213 /**14 * Network: Kovan15 * Oracle: 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e16 * Job ID: 29fa9aa13bf1468788b7cc4a500a45b817 * Fee: 0.1 LINK18 */19 constructor() public {20 setPublicChainlinkToken();21 oracle = 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e;22 jobId = "29fa9aa13bf1468788b7cc4a500a45b8";23 fee = 0.1 * 10 ** 18; // 0.1 LINK24 }2526 /**27 * Create a Chainlink request to retrieve API response, find the target28 * data, then multiply by 1000000000000000000 (to remove decimal places from data).29 */30 function requestVolumeData() public returns (bytes32 requestId)31 {32 Chainlink.Request memory request = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);3334 // Set the URL to perform the GET request on35 request.add("get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD");3637 // Set the path to find the desired data in the API response, where the response format is:38 // {"RAW":39 // {"ETH":40 // {"USD":41 // {42 // "VOLUME24HOUR": xxx.xxx,43 // }44 // }45 // }46 // }47 request.add("path", "RAW.ETH.USD.VOLUME24HOUR");4849 // Multiply the result by 1000000000000000000 to remove decimals50 int timesAmount = 10**18;51 request.addInt("times", timesAmount);5253 // Sends the request54 return sendChainlinkRequestTo(oracle, request, fee);55 }5657 /**58 * Receive the response in the form of uint25659 */60 function fulfill(bytes32 _requestId, uint256 _volume) public recordChainlinkFulfillment(_requestId)61 {62 volume = _volume;63 }64}65显示全部
您可以阅读 Chainlink 开发者博客,进一步了解 Chainlink 的应用程序。
预言机服务
构建一个预言机智能合约
此为 Pedro Costa 编写的预言机合约示例。 您可以在他的以下文章中找到更多注解:在以太网部署区块链预言机。
1pragma solidity >=0.4.21 <0.6.0;23contract Oracle {4 Request[] requests; //list of requests made to the contract5 uint currentId = 0; //increasing request id6 uint minQuorum = 2; //minimum number of responses to receive before declaring final result7 uint totalOracleCount = 3; // Hardcoded oracle count89 // defines a general api request10 struct Request {11 uint id; //request id12 string urlToQuery; //API url13 string attributeToFetch; //json attribute (key) to retrieve in the response14 string agreedValue; //value from key15 mapping(uint => string) anwers; //answers provided by the oracles16 mapping(address => uint) quorum; //oracles which will query the answer (1=oracle hasn't voted, 2=oracle has voted)17 }1819 //event that triggers oracle outside of the blockchain20 event NewRequest (21 uint id,22 string urlToQuery,23 string attributeToFetch24 );2526 //triggered when there's a consensus on the final result27 event UpdatedRequest (28 uint id,29 string urlToQuery,30 string attributeToFetch,31 string agreedValue32 );3334 function createRequest (35 string memory _urlToQuery,36 string memory _attributeToFetch37 )38 public39 {40 uint lenght = requests.push(Request(currentId, _urlToQuery, _attributeToFetch, ""));41 Request storage r = requests[lenght-1];4243 // Hardcoded oracles address44 r.quorum[address(0x6c2339b46F41a06f09CA0051ddAD54D1e582bA77)] = 1;45 r.quorum[address(0xb5346CF224c02186606e5f89EACC21eC25398077)] = 1;46 r.quorum[address(0xa2997F1CA363D11a0a35bB1Ac0Ff7849bc13e914)] = 1;4748 // launch an event to be detected by oracle outside of blockchain49 emit NewRequest (50 currentId,51 _urlToQuery,52 _attributeToFetch53 );5455 // increase request id56 currentId++;57 }5859 //called by the oracle to record its answer60 function updateRequest (61 uint _id,62 string memory _valueRetrieved63 ) public {6465 Request storage currRequest = requests[_id];6667 //check if oracle is in the list of trusted oracles68 //and if the oracle hasn't voted yet69 if(currRequest.quorum[address(msg.sender)] == 1){7071 //marking that this address has voted72 currRequest.quorum[msg.sender] = 2;7374 //iterate through "array" of answers until a position if free and save the retrieved value75 uint tmpI = 0;76 bool found = false;77 while(!found) {78 //find first empty slot79 if(bytes(currRequest.anwers[tmpI]).length == 0){80 found = true;81 currRequest.anwers[tmpI] = _valueRetrieved;82 }83 tmpI++;84 }8586 uint currentQuorum = 0;8788 //iterate through oracle list and check if enough oracles(minimum quorum)89 //have voted the same answer has the current one90 for(uint i = 0; i < totalOracleCount; i++){91 bytes memory a = bytes(currRequest.anwers[i]);92 bytes memory b = bytes(_valueRetrieved);9394 if(keccak256(a) == keccak256(b)){95 currentQuorum++;96 if(currentQuorum >= minQuorum){97 currRequest.agreedValue = _valueRetrieved;98 emit UpdatedRequest (99 currRequest.id,100 currRequest.urlToQuery,101 currRequest.attributeToFetch,102 currRequest.agreedValue103 );104 }105 }106 }107 }108 }109}110显示全部复制
我们希望有更多关于构建预言机智能合约的文档。 如果您能帮助我们,就创建一个拉取请求吧!
延伸阅读
文章
- 什么是区块链 Oracle?- Chainlink
- 预言机 – EthHub
- 什么是区块链预言机?- Patrick Collins
- 去中心化预言机:综述– Julien Thevenard
- 在以太坊部署区块链预言机– Pedro Costa
- 为什么智能合约无法调用应用程序接口?- StackExchange
- 我们为什么需要去中心化预言机- Bankless
- 因此您想要使用价格预言机- samczsun
视频
- 预言机和区块链实用程序拓展- Real Vision Finance
教程
- 如何在 Solidity 中获取以太坊的当前价格- Chainlink
示例项目
帮助我们处理此页面
如果您是这方面的专家,并想发表意见,那么编辑此页分享您的智慧。
您将获得褒奖并会为以太坊社区提供帮助!
自由发挥 文档模板
有任何疑问? 请在我们的频道中进行询问 Discord 服务器
编辑页面