撸一个预言机(Oracle)服务,真香
一、文章结构
本文将通过上、中、下三篇文章带领大家一步步开发实现一个中心化的 Oracle 服务,并通过一个抽奖合约演示如何使用我们的 Oracle 服务。文章内容安排如下:
-
上篇:Oracle 简介及合约实现
-
中篇:使用 go 语言开发 Oracle 服务
-
下篇:抽奖合约调用 Oracle 服务示例
一、Oracle 简介
Oracle (预言机)是链接链上与链下的桥梁,能够将链下数据推送给链上。正是由于 Oracle 的存在,使得区块链从封闭走向开放,充满无限可能。
如需了解 Oracle 基础知识,这里推荐阅读孙孝虎的《什么是区块链预言机(BlockChain Oracle)》
Oracle 服务分为中心化和去中心化,其核心区别是对数据的获取和审核上。去中心化的 Oracle 服务会有一套机制能够保障推送给用户合约的数据是可信的。而无论是中心化还是去中心化,用户合约调用 Oracle 合约和 Oracle 服务将获取到的结果数据推送给用户合约的底层逻辑都是一样的。
一个完整的中心化 Oracle 服务请求流程为:
-
用户合约调用 Oracle 合约的查询方法
-
Oracle 合约接收到用户查询请求后将相关数据写入 Event 事件中
-
Oracle 服务(后台服务)通过订阅 Oracle 合约的 Event 事件,获取到用户的请求
-
Oracle 服务根据用户请求获取外部数据
-
Oracle 服务调用 Oracle 合约响应方法,传入获取的外部数据
-
Oracle 合约响应方法调用用户合约的回调方法,将数据传递给用户合约
-
用户合约收到 Oracle 合约传递的数据,继续自己的业务。
整体流程如下图所示。
Chainlinkfundamental2.png
图片来源于文章《Chainlink 预言机基本原理》:
三、Oracle 合约
通过上面对 Oracle 服务流程的分析,总结到一个 Oracle 合约至少需要包含两个方法和一个事件:
-
能够接收用户合约请求的方法
-
能够回调用户合约的方法
-
能够供 Oracle 服务订阅的用户请求事件
接下来,我将实现一个通用的 Oracle 合约。
1. 能够接收用户合约请求的方法
1 /** 2 * @dev 接收客户端请求 3 * @param queryId 请求 id,回调时原值返回 4 * @param callbackAddr 回调的合约地址 5 * @param callbackFUN 回调合约的方法及参数,如 getResponse(bytes32,uint64,uint256/bytes), 6 * 其中 getResponse 表示回调方法名,可自定义; 7 * bytes32 类型参数指请求 id,回调时会原值返回; 8 * uint64 类型参数表示 oracle 服务状态码,1 表示成功,0 表示失败; 9 * 第三个参数表示 Oracle 服务回调支持 uint256/bytes 两种类型的参数 10 * @param queryData 请求数据,json 格式,如{"url":"https://ethgasstation.info/api/ethgasAPI.json","responseParams":["fast"]} 11 * @return bool true 请求成功,false 请求失败 12 */ 13function query(bytes32 queryId, address callbackAddr, string calldata callbackFUN, bytes calldata queryData) external payable returns(bool) { 14 require(msg.value >= MIN_FEE, "Insufficient handling fee!"); 15 require(bytes(callbackFUN).length > 0, "Invalid callbackFUN!"); 16 require(queryData.length > 0, "Invalid queryData!"); 17 // 记录日志 18 emit QueryInfo(queryId, msg.sender, msg.value, callbackAddr, callbackFUN, queryData); 19 return true; 20}
需要说明的地方:
-
用户合约会多次请求 Oracle 服务,获取数据,
queryId请求 ID 参数可以让用户合约对请求做标识。 -
让用户传
callbackAddr回调地址参数,而不是直接通过msg.sender获取调用者地址,是考虑到调用 Oracle 合约 (付费方) 和接收数据方有可能不是一个地址。 -
对于用户请求的数据类型,本文目前实现了 uint256 和 bytes 两种类型的回调。
-
考虑到通用性,用户请求的数据来源由用户自定义。如果是一个专类的 Oracle 服务(如只提供随机数服务),可以不需要请求数据字段。
-
考虑到节省用户的请求费用,加之本身就是一个中心化的 Oracle 服务,不存在作弊问题,因此
query方法并没有更改任何状态变量,用户请求数据直接写入到日志中。
2. 能够回调用户合约的方法
1/** 2 * @dev 将查询得到的结果(bytes 类型)发送给客户端 3 * @param queryId 查询请求 id 4 * @param callbackAddr 回调的合约地址 5 * @param callbackFUN 回调合约的方法及参数 6 * @param stateCode 查询结果状态码,1 表示查询成功,0 表示失败 7 * @param respData 查询结果 8 * @return bool true 请求成功,false 请求失败 9 */ 10function responseBytes(bytes32 queryId, address callbackAddr, string calldata callbackFUN, uint64 stateCode, bytes calldata respData) payable external isOwner returns(bool) { 11 require(address(this).balance > CALLBACK_GAS, "Insufficient balance!"); 12 (bool success,) = callbackAddr.call.gas(CALLBACK_GAS)(abi.encodeWithSignature(callbackFUN, queryId, stateCode, respData)); 13 require(success,"call back failed!"); 14} 15 16/** 17 * @dev 将查询得到的结果(uint256 类型)发送给客户端 18 */ 19function responseUint256(bytes32 queryId, address callbackAddr, string calldata callbackFUN, uint64 stateCode, uint256 respData) payable external isOwner returns(bool) { 20 require(address(this).balance > CALLBACK_GAS, "Insufficient balance!"); 21 (bool success,) = callbackAddr.call.gas(CALLBACK_GAS)(abi.encodeWithSignature(callbackFUN, queryId, stateCode, respData)); 22 require(success); 23}
3. 能够供 Oracle 服务订阅的用户请求事件
事件将用户请求的相关参数都记录下来,Oracle 服务通过订阅该事件,一旦有用户请求时,Oracle 服务就能够获取到用户的请求数据。
1// 查询事件,oracle 后端服务会订阅该事件 2event QueryInfo(bytes32 queryId, address requester, uint fee, address callbackAddr, string callbackFUN, bytes queryData);
完整代码地址:
https://github.com/six-days/ethereum-contracts/blob/master/oracle/Oracle.sol
下篇我们将介绍 Oracle 服务(后端服务)如何订阅查询事件以及将获取到的数据返回给合约,具体实现代码将以 golang 语言给出。
本文作者:六天
来源链接:mp.weixin.qq.com
- 免责声明
- 世链财经作为开放的信息发布平台,所有资讯仅代表作者个人观点,与世链财经无关。如文章、图片、音频或视频出现侵权、违规及其他不当言论,请提供相关材料,发送到:2785592653@qq.com。
- 风险提示:本站所提供的资讯不代表任何投资暗示。投资有风险,入市须谨慎。
- 世链粉丝群:提供最新热点新闻,空投糖果、红包等福利,微信:juu3644。

币小葱



