本文将通过上、中、下三篇文章带领大家一步步开发实现一个自己中心化的Oracle服务,并通过抽奖合约演示如何使用
本文将通过上、中、下三篇文章带领大家一步步开发实现一个中心化的Oracle服务,并通过一个抽奖合约演示如何使用我们的Oracle服务。文章内容安排如下:
Oracle(预言机)是链接链上与链下的桥梁,能够将链下数据推送给链上。正是由于Oracle的存在,使得区块链从封闭走向开放,充满无限可能。
如需了解Oracle基础知识,这里推荐阅读孙孝虎的《什么是区块链预言机(BlockChain Oracle)》
Oracle服务分为中心化和去中心化,其核心区别是对数据的获取和审核上。去中心化的Oracle服务会有一套机制能够保障推送给用户合约的数据是可信的。而无论是中心化还是去中心化,用户合约调用Oracle合约和Oracle服务将获取到的结果数据推送给用户合约的底层逻辑都是一样的。
一个完整的中心化Oracle服务请求流程为:
整体流程如下图所示。
图片来源于文章《Chainlink预言机基本原理》:https://learnblockchain.cn/article/587
通过上面对Oracle服务流程的分析,总结到一个Oracle合约至少需要包含两个方法和一个事件:
接下来,我将实现一个通用的Oracle合约。
/**
* @dev 接收客户端请求
* @param queryId 请求id,回调时原值返回
* @param callbackAddr 回调的合约地址
* @param callbackFUN 回调合约的方法及参数,如getResponse(bytes32,uint64,uint256/bytes),
* 其中getResponse表示回调方法名,可自定义;
* bytes32类型参数指请求id,回调时会原值返回;
* uint64类型参数表示oracle服务状态码,1表示成功,0表示失败;
* 第三个参数表示Oracle服务回调支持uint256/bytes两种类型的参数
* @param queryData 请求数据,json格式,如{"url":"https://ethgasstation.info/api/ethgasAPI.json","responseParams":["fast"]}
* @return bool true请求成功,false请求失败
*/
function query(bytes32 queryId, address callbackAddr, string calldata callbackFUN, bytes calldata queryData) external payable returns(bool) {
require(msg.value >= MIN_FEE, "Insufficient handling fee!");
require(bytes(callbackFUN).length > 0, "Invalid callbackFUN!");
require(queryData.length > 0, "Invalid queryData!");
// 记录日志
emit QueryInfo(queryId, msg.sender, msg.value, callbackAddr, callbackFUN, queryData);
return true;
}
需要说明的地方:
queryId
请求ID参数可以让用户合约对请求做标识。callbackAddr
回调地址参数,而不是直接通过msg.sender
获取调用者地址,是考虑到调用Oracle合约(付费方)和接收数据方有可能不是一个地址。query
方法并没有更改任何状态变量,用户请求数据直接写入到日志中。/**
* @dev 将查询得到的结果(bytes类型)发送给客户端
* @param queryId 查询请求id
* @param callbackAddr 回调的合约地址
* @param callbackFUN 回调合约的方法及参数
* @param stateCode 查询结果状态码,1表示查询成功,0表示失败
* @param respData 查询结果
* @return bool true请求成功,false请求失败
*/
function responseBytes(bytes32 queryId, address callbackAddr, string calldata callbackFUN, uint64 stateCode, bytes calldata respData) payable external isOwner returns(bool) {
require(address(this).balance > CALLBACK_GAS, "Insufficient balance!");
(bool success,) = callbackAddr.call.gas(CALLBACK_GAS)(abi.encodeWithSignature(callbackFUN, queryId, stateCode, respData));
require(success,"call back failed!");
}
/**
* @dev 将查询得到的结果(uint256类型)发送给客户端
*/
function responseUint256(bytes32 queryId, address callbackAddr, string calldata callbackFUN, uint64 stateCode, uint256 respData) payable external isOwner returns(bool) {
require(address(this).balance > CALLBACK_GAS, "Insufficient balance!");
(bool success,) = callbackAddr.call.gas(CALLBACK_GAS)(abi.encodeWithSignature(callbackFUN, queryId, stateCode, respData));
require(success);
}
事件将用户请求的相关参数都记录下来,Oracle服务通过订阅该事件,一旦有用户请求时,Oracle服务就能够获取到用户的请求数据。
// 查询事件,oracle后端服务会订阅该事件
event 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语言给出。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!