(三)基于区块链的自动抽奖系统从0到1实现
上一章节我们已经,部署了Truora,并且访问正常,这一章节我们开始编写我们的核心的合约,并部署连接上Truora服务。
# 前言 上一章节我们已经,部署了Truora,并且访问正常,这一章节我们开始编写我们的核心的合约,并部署连接上Truora服务。 # 编写预言机合约 这里我们可以参照官方的步骤:[https://truora.readthedocs.io/zh_CN/latest/docs/develop/quick-start.html#id1](https://truora.readthedocs.io/zh_CN/latest/docs/develop/quick-start.html#id1) ## 1.创建用户 打开一键部署的 WeBASE-Front 页面,默认:http://{IP}:5002/WeBASE-Front/,使用部署主机的 IP 地址替换 {IP}。 点击左边 合约管理 –> 测试用户,创建一个调试用户 larry ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205143447930.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) ## 2.上传核心预言机合约 点击左边 合约管理 –> 合约 IDE,选择 solidity 版本,上传模板合约,包括以下五个合约: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205143629486.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) FiscoOracleClient.sol ```javascript pragma solidity ^0.6.0; import "./SafeMath.sol"; import "./OracleCoreInterface.sol"; abstract contract FiscoOracleClient { using SafeMath for uint256; OracleCoreInterface private oracle; uint256 private requestCount = 1; mapping(bytes32 => address) private pendingRequests; mapping (address => uint) private reqc; uint256 constant public EXPIRY_TIME = 10 * 60 * 1000; event Requested(bytes32 indexed id); event Fulfilled(bytes32 indexed id); function __callback(bytes32 requestId, int256 result) public virtual; // __callback with proof // function __callback(bytes32 requestId, int256 result, bytes calldata proof) public virtual; function oracleQuery(address _oracle, string memory url, uint256 timesAmount) internal returns (bytes32 requestId) { return oracleQuery(EXPIRY_TIME,"url", _oracle, url, timesAmount, false); } function oracleQuery(uint expiryTime, string memory datasource, address _oracle, string memory url, uint256 timesAmount, bool needProof) internal returns (bytes32 requestId) { // calculate the id; oracle = OracleCoreInterface(_oracle); int256 chainId; int256 groupId; ( chainId, groupId) = oracle.getChainIdAndGroupId(); requestId = keccak256(abi.encodePacked(chainId, groupId, this, requestCount)); pendingRequests[requestId] = _oracle; emit Requested(requestId); require(oracle.query(address(this),requestCount, url,timesAmount, expiryTime,needProof),"oracle-core invoke failed!"); requestCount++; reqc[msg.sender]++; return requestId; } /** * @notice Sets the stored oracle core address * @param _oracle The address of the oracle core contract */ function setOracleCoreAddress(address _oracle) internal { oracle = OracleCoreInterface(_oracle); } /** * @notice Retrieves the stored address of the oracle contract * @return The address of the oracle contract */ function getOracleCoreAddress() internal view returns (address) { return address(oracle); } /** * @dev Reverts if the sender is not the oracle of the request. * @param _requestId The request ID for fulfillment */ modifier onlyOracleCoreInvoke(bytes32 _requestId) { require(msg.sender == pendingRequests[_requestId], "Source must be the oracle of the request"); delete pendingRequests[_requestId]; emit Fulfilled(_requestId); _; } } ``` OracleCore.sol ```javascript pragma solidity ^0.6.0; import "./Ownable.sol"; import "./SafeMath.sol"; /** * @title The contract for oracle service listening */ contract OracleCore is Ownable { using SafeMath for uint256; mapping(bytes32 => bytes32) private commitments; mapping(bytes32 => uint256) timeoutMap; int256 private chainId; int256 private groupId; bytes4 private callbackFunctionId = bytes4(keccak256("__callback(bytes32,int256)")); event OracleRequest( address callbackAddr, bytes32 requestId, string url, uint256 expiration, uint256 timesAmount, bool needProof ); constructor(int256 _chainId, int256 _groupId) public Ownable() { chainId = _chainId; groupId = _groupId; } function query( address _callbackAddress, uint256 _nonce, string calldata _url, uint256 _timesAmount, uint256 _expiryTime, bool _needProof ) external returns(bool) { bytes32 requestId = keccak256(abi.encodePacked(chainId, groupId, _callbackAddress, _nonce)); require(commitments[requestId] == 0, "Must use a unique ID"); uint256 expiration = now.add(_expiryTime); timeoutMap[requestId] = expiration; commitments[requestId] = keccak256( abi.encodePacked( _callbackAddress, expiration ) ); emit OracleRequest( _callbackAddress, requestId, _url, expiration, _timesAmount, _needProof); return true; } function fulfillRequest( bytes32 _requestId, address _callbackAddress, uint256 _expiration, uint256 _result, bytes calldata proof ) public onlyOwner isValidRequest(_requestId) returns (bool) { bytes32 paramsHash = keccak256( abi.encodePacked( _callbackAddress, _expiration ) ); require(commitments[_requestId] == paramsHash, "Params do not match request ID"); delete commitments[_requestId]; delete timeoutMap[_requestId]; (bool success, ) = _callbackAddress.call(abi.encodeWithSelector(callbackFunctionId, _requestId, _result)); // solhint-disable-line avoid-low-level-calls return success; } function getChainIdAndGroupId() public view returns(int256,int256){ return (chainId, groupId); } /** * @dev Reverts if request ID does not exist or time out. * @param _requestId The given request ID to check in stored `commitments` */ modifier isValidRequest(bytes32 _requestId) { require(commitments[_requestId] != 0, "Must have a valid requestId"); require(timeoutMap[_requestId] > now, "fulfill request time out"); _; } } ``` OracleCoreInterface.sol ```javascript pragma solidity ^0.6.0; interface OracleCoreInterface { function query( address _callbackAddress, uint256 _nonce, string calldata _url, uint256 _timesAmount, uint256 _expiryTime, bool needProof ) external returns(bool) ; function getChainIdAndGroupId() external view returns(int256,int256) ; } ``` Ownable.sol ```javascript pragma solidity ^0.6.0; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be aplied to your functions to restrict their use to * the owner. * * This contract has been modified to remove the revokeOwnership function */ contract Ownable { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor () internal { _owner = msg.sender; emit OwnershipTransferred(address(0), _owner); } /** * @dev Returns the address of the current owner. */ function owner() public view returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(isOwner(), "Ownable: caller is not the owner"); _; } /** * @dev Returns true if the caller is the current owner. */ function isOwner() public view returns (bool) { return msg.sender == _owner; } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public onlyOwner { _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). */ function _transferOwnership(address newOwner) internal { require(newOwner != address(0), "Ownable: new owner is the zero address"); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } } ``` SafeMath.sol ```javascript pragma solidity ^0.6.0; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a, "SafeMath: subtraction overflow"); uint256 c = a - b; return c; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, "SafeMath: division by zero"); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0, "SafeMath: modulo by zero"); return a % b; } } ``` 确认后,选择上传目录,此处选择根目录 / ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205143653151.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205143922301.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) ## 3.编写自己业务的合约 这里我们就把官方的demo稍微改造一下即可,代码如下: APISampleOracle.sol ```javascript pragma solidity ^0.6.0; import "./FiscoOracleClient.sol"; contract APISampleOracle is FiscoOracleClient { //指定处理的oracle address private oracleCoreAddress; // Multiply the result by 1000000000000000000 to remove decimals uint256 private timesAmount = 10**18; mapping(bytes32=>int256) private resultMap; mapping(bytes32=>bool) private validIds; int256 public result; string private url = "plain(http://blog.zhihuixiangxi.com:9999/lottery)"; constructor(address oracleAddress) public { oracleCoreAddress = oracleAddress; } function request() public returns (bytes32) { bytes32 requestId = oracleQuery(oracleCoreAddress, url, timesAmount); validIds[requestId] = true; return requestId; } /** * Receive the response in the form of int256 */ function __callback(bytes32 _requestId, int256 _result) public override onlyOracleCoreInvoke(_requestId) { require(validIds[_requestId], "id must be not used!") ; resultMap[_requestId]= _result; delete validIds[_requestId]; result = _result ; } function get() public view returns(int256){ return result; } function getById(bytes32 id) public view returns(int256){ return resultMap[id]; } function checkIdFulfilled(bytes32 id) public view returns(bool){ return validIds[id]; } function setUrl(string memory _url) public { url = _url; } function getUrl() public view returns(string memory){ return url; } } ``` 核心就是 string private url = "plain(http://blog.zhihuixiangxi.com:9999/lottery)"; 和链下的API去交互。API的逻辑我们下一章节详聊。 ## 4.Truora控制台刷新合约获取合约地址 我们这这里就进入ruora控制台,http://192.168.119.133:5020/#/contractSearch,获取oraclecore合约地址如下图所示: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205144251592.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) ## 5.部署APISampleOracle.sol 编译APISampleOracle.sol,点击部署按钮进行部署,填入上一步我们获取的合约地址即可。如下图所示: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205144428871.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205144504904.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205144646974.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205144712677.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) ## 6.测试合约 我们点击“合约调用”,选择request方法,点击确认如下图所示: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205144840322.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205144912703.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) 到这里,已经执行了合约并且和链下的API进行了交互。我们也可以再执行get方法进入结果的获取如下图所示: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205144941889.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205145330478.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) ![这里为了防止](https://img-blog.csdnimg.cn/2021020514543129.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) 实际我们这里可以确定我们的链下的API是不会产生小数的,可以合理的去调整合约的逻辑,但是我是选择在java项目代码里面去处理这块逻辑了,如下: ```java /** * 执行合约 * * @param param 合约参数 * @return */ public String handle(String param) { String url = "http://192.168.119.133:5002/WeBASE-Front/trans/handle"; String body = "{\n" + " \"user\":\"0xf0d04e0cc9b16528207027f1d5020e402096b44e\",\n" + " \"contractName\":\"APISampleOracle\",\n" + " \"contractAddress\":\"0x5ffbf18cfbe8c5b8fee09ccde4f5165007a6043e\",\n" + " \"funcName\":\"" + param + "\",\n" + " \"contractAbi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"oracleAddress\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"Fulfilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"Requested\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"EXPIRY_TIME\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_requestId\",\"type\":\"bytes32\"},{\"internalType\":\"int256\",\"name\":\"_result\",\"type\":\"int256\"}],\"name\":\"__callback\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"checkIdFulfilled\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"getById\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getUrl\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"request\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"result\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_url\",\"type\":\"string\"}],\"name\":\"setUrl\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}] ,\n" + " \"groupId\" :\"1\",\n" + " \"useCns\": false\n" + "}"; String res = HttpUtil.post(url, body); if (param.equals("get")) { // 处理格式 res = res.replace("[", "").replace("]", "").replace("000000000000000000", ""); } return res; } ``` 结果也可以在truora控制台的“历史查询”里面去查看,如下图所示: ![在这里插入图片描述](https://img-blog.csdnimg.cn/2021020514510256.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205145114718.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) # 闭坑指南 **问题**:独立mysql整合到truora如果连接不上,页面不会有任何提示 **现象**:truora和链下API交互异常,返回0 **解决方案**:查truora后台的log,路径truora/deploy/log/server/Oracle-Service.log 会包含java.sql.SQLException: Access denied for user 'truora'@'localhost' (using password: YES) 的异常提示 ,在/truora/deploy/docker-compose.yml修正mysql连接参数,重启服务即可。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205151543272.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) **问题**:一键部署后WeBASE-Front创建truora的合约只要重启一下服务就会消失了。 **解决方案**: 在webase/webase-front.yml配置文件中添加以下配置: ```yaml spring: datasource: url: jdbc:h2:file:/dist/h2/webasefront;DB_CLOSE_ON_EXIT=FALSE ``` ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210205151911907.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzMzI3NDQzNzUy,size_16,color_FFFFFF,t_70) 然后重启一下服务即可解决。 PS:上述问题官方会在下一个版本进行修复 # 总结 Truora第三篇系列文章我们重点讲解《合约的开发与部署》,注意闭坑指南。其他的按照教程一步一步来成功率99%以上。
前言
上一章节我们已经,部署了Truora,并且访问正常,这一章节我们开始编写我们的核心的合约,并部署连接上Truora服务。
编写预言机合约
这里我们可以参照官方的步骤:https://truora.readthedocs.io/zh_CN/latest/docs/develop/quick-start.html#id1
1.创建用户
打开一键部署的 WeBASE-Front 页面,默认:http://{IP}:5002/WeBASE-Front/,使用部署主机的 IP 地址替换 {IP}。 点击左边 合约管理 –> 测试用户,创建一个调试用户 larry
2.上传核心预言机合约
点击左边 合约管理 –> 合约 IDE,选择 solidity 版本,上传模板合约,包括以下五个合约:
FiscoOracleClient.sol
pragma solidity ^0.6.0;
import "./SafeMath.sol";
import "./OracleCoreInterface.sol";
abstract contract FiscoOracleClient {
using SafeMath for uint256;
OracleCoreInterface private oracle;
uint256 private requestCount = 1;
mapping(bytes32 => address) private pendingRequests;
mapping (address => uint) private reqc;
uint256 constant public EXPIRY_TIME = 10 * 60 * 1000;
event Requested(bytes32 indexed id);
event Fulfilled(bytes32 indexed id);
function __callback(bytes32 requestId, int256 result) public virtual;
// __callback with proof
// function __callback(bytes32 requestId, int256 result, bytes calldata proof) public virtual;
function oracleQuery(address _oracle, string memory url, uint256 timesAmount)
internal
returns (bytes32 requestId)
{
return oracleQuery(EXPIRY_TIME,"url", _oracle, url, timesAmount, false);
}
function oracleQuery(uint expiryTime, string memory datasource, address _oracle, string memory url, uint256 timesAmount, bool needProof) internal
returns (bytes32 requestId) {
// calculate the id;
oracle = OracleCoreInterface(_oracle);
int256 chainId;
int256 groupId;
( chainId, groupId) = oracle.getChainIdAndGroupId();
requestId = keccak256(abi.encodePacked(chainId, groupId, this, requestCount));
pendingRequests[requestId] = _oracle;
emit Requested(requestId);
require(oracle.query(address(this),requestCount, url,timesAmount, expiryTime,needProof),"oracle-core invoke failed!");
requestCount++;
reqc[msg.sender]++;
return requestId;
}
/**
* @notice Sets the stored oracle core address
* @param _oracle The address of the oracle core contract
*/
function setOracleCoreAddress(address _oracle) internal {
oracle = OracleCoreInterface(_oracle);
}
/**
* @notice Retrieves the stored address of the oracle contract
* @return The address of the oracle contract
*/
function getOracleCoreAddress()
internal
view
returns (address)
{
return address(oracle);
}
/**
* @dev Reverts if the sender is not the oracle of the request.
* @param _requestId The request ID for fulfillment
*/
modifier onlyOracleCoreInvoke(bytes32 _requestId) {
require(msg.sender == pendingRequests[_requestId],
"Source must be the oracle of the request");
delete pendingRequests[_requestId];
emit Fulfilled(_requestId);
_;
}
}
OracleCore.sol
pragma solidity ^0.6.0;
import "./Ownable.sol";
import "./SafeMath.sol";
/**
* @title The contract for oracle service listening
*/
contract OracleCore is Ownable {
using SafeMath for uint256;
mapping(bytes32 => bytes32) private commitments;
mapping(bytes32 => uint256) timeoutMap;
int256 private chainId;
int256 private groupId;
bytes4 private callbackFunctionId = bytes4(keccak256("__callback(bytes32,int256)"));
event OracleRequest(
address callbackAddr,
bytes32 requestId,
string url,
uint256 expiration,
uint256 timesAmount,
bool needProof
);
constructor(int256 _chainId, int256 _groupId) public Ownable()
{
chainId = _chainId;
groupId = _groupId;
}
function query(
address _callbackAddress,
uint256 _nonce,
string calldata _url,
uint256 _timesAmount,
uint256 _expiryTime,
bool _needProof
)
external
returns(bool)
{
bytes32 requestId = keccak256(abi.encodePacked(chainId, groupId, _callbackAddress, _nonce));
require(commitments[requestId] == 0, "Must use a unique ID");
uint256 expiration = now.add(_expiryTime);
timeoutMap[requestId] = expiration;
commitments[requestId] = keccak256(
abi.encodePacked(
_callbackAddress,
expiration
)
);
emit OracleRequest(
_callbackAddress,
requestId,
_url,
expiration,
_timesAmount,
_needProof);
return true;
}
function fulfillRequest(
bytes32 _requestId,
address _callbackAddress,
uint256 _expiration,
uint256 _result,
bytes calldata proof
)
public
onlyOwner
isValidRequest(_requestId)
returns (bool)
{
bytes32 paramsHash = keccak256(
abi.encodePacked(
_callbackAddress,
_expiration
)
);
require(commitments[_requestId] == paramsHash, "Params do not match request ID");
delete commitments[_requestId];
delete timeoutMap[_requestId];
(bool success, ) = _callbackAddress.call(abi.encodeWithSelector(callbackFunctionId, _requestId, _result)); // solhint-disable-line avoid-low-level-calls
return success;
}
function getChainIdAndGroupId() public view returns(int256,int256){
return (chainId, groupId);
}
/**
* @dev Reverts if request ID does not exist or time out.
* @param _requestId The given request ID to check in stored `commitments`
*/
modifier isValidRequest(bytes32 _requestId) {
require(commitments[_requestId] != 0, "Must have a valid requestId");
require(timeoutMap[_requestId] > now, "fulfill request time out");
_;
}
}
OracleCoreInterface.sol
pragma solidity ^0.6.0;
interface OracleCoreInterface {
function query(
address _callbackAddress,
uint256 _nonce,
string calldata _url,
uint256 _timesAmount,
uint256 _expiryTime,
bool needProof
) external
returns(bool) ;
function getChainIdAndGroupId() external view returns(int256,int256) ;
}
Ownable.sol
pragma solidity ^0.6.0;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be aplied to your functions to restrict their use to
* the owner.
*
* This contract has been modified to remove the revokeOwnership function
*/
contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Returns true if the caller is the current owner.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
SafeMath.sol
pragma solidity ^0.6.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, "SafeMath: division by zero");
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "SafeMath: modulo by zero");
return a % b;
}
}
确认后,选择上传目录,此处选择根目录 /
3.编写自己业务的合约
这里我们就把官方的demo稍微改造一下即可,代码如下: APISampleOracle.sol
pragma solidity ^0.6.0;
import "./FiscoOracleClient.sol";
contract APISampleOracle is FiscoOracleClient {
//指定处理的oracle
address private oracleCoreAddress;
// Multiply the result by 1000000000000000000 to remove decimals
uint256 private timesAmount = 10**18;
mapping(bytes32=>int256) private resultMap;
mapping(bytes32=>bool) private validIds;
int256 public result;
string private url = "plain(http://blog.zhihuixiangxi.com:9999/lottery)";
constructor(address oracleAddress) public {
oracleCoreAddress = oracleAddress;
}
function request() public returns (bytes32)
{
bytes32 requestId = oracleQuery(oracleCoreAddress, url, timesAmount);
validIds[requestId] = true;
return requestId;
}
/**
* Receive the response in the form of int256
*/
function __callback(bytes32 _requestId, int256 _result) public override onlyOracleCoreInvoke(_requestId)
{
require(validIds[_requestId], "id must be not used!") ;
resultMap[_requestId]= _result;
delete validIds[_requestId];
result = _result ;
}
function get() public view returns(int256){
return result;
}
function getById(bytes32 id) public view returns(int256){
return resultMap[id];
}
function checkIdFulfilled(bytes32 id) public view returns(bool){
return validIds[id];
}
function setUrl(string memory _url) public {
url = _url;
}
function getUrl() public view returns(string memory){
return url;
}
}
核心就是 string private url = "plain(http://blog.zhihuixiangxi.com:9999/lottery)"; 和链下的API去交互。API的逻辑我们下一章节详聊。
4.Truora控制台刷新合约获取合约地址
我们这这里就进入ruora控制台,http://192.168.119.133:5020/#/contractSearch,获取oraclecore合约地址如下图所示:
5.部署APISampleOracle.sol
编译APISampleOracle.sol,点击部署按钮进行部署,填入上一步我们获取的合约地址即可。如下图所示:
6.测试合约
我们点击“合约调用”,选择request方法,点击确认如下图所示:
到这里,已经执行了合约并且和链下的API进行了交互。我们也可以再执行get方法进入结果的获取如下图所示: 实际我们这里可以确定我们的链下的API是不会产生小数的,可以合理的去调整合约的逻辑,但是我是选择在java项目代码里面去处理这块逻辑了,如下:
/**
* 执行合约
*
* @param param 合约参数
* @return
*/
public String handle(String param) {
String url = "http://192.168.119.133:5002/WeBASE-Front/trans/handle";
String body = "{\n" +
" \"user\":\"0xf0d04e0cc9b16528207027f1d5020e402096b44e\",\n" +
" \"contractName\":\"APISampleOracle\",\n" +
" \"contractAddress\":\"0x5ffbf18cfbe8c5b8fee09ccde4f5165007a6043e\",\n" +
" \"funcName\":\"" + param + "\",\n" +
" \"contractAbi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"oracleAddress\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"Fulfilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"Requested\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"EXPIRY_TIME\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_requestId\",\"type\":\"bytes32\"},{\"internalType\":\"int256\",\"name\":\"_result\",\"type\":\"int256\"}],\"name\":\"__callback\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"checkIdFulfilled\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"getById\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getUrl\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"request\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"result\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_url\",\"type\":\"string\"}],\"name\":\"setUrl\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}] ,\n" +
" \"groupId\" :\"1\",\n" +
" \"useCns\": false\n" +
"}";
String res = HttpUtil.post(url, body);
if (param.equals("get")) {
// 处理格式
res = res.replace("[", "").replace("]", "").replace("000000000000000000", "");
}
return res;
}
结果也可以在truora控制台的“历史查询”里面去查看,如下图所示:
闭坑指南
问题:独立mysql整合到truora如果连接不上,页面不会有任何提示 现象:truora和链下API交互异常,返回0 解决方案:查truora后台的log,路径truora/deploy/log/server/Oracle-Service.log 会包含java.sql.SQLException: Access denied for user 'truora'@'localhost' (using password: YES) 的异常提示 ,在/truora/deploy/docker-compose.yml修正mysql连接参数,重启服务即可。
问题:一键部署后WeBASE-Front创建truora的合约只要重启一下服务就会消失了。
解决方案: 在webase/webase-front.yml配置文件中添加以下配置:
spring:
datasource:
url: jdbc:h2:file:/dist/h2/webasefront;DB_CLOSE_ON_EXIT=FALSE
然后重启一下服务即可解决。
PS:上述问题官方会在下一个版本进行修复
总结
Truora第三篇系列文章我们重点讲解《合约的开发与部署》,注意闭坑指南。其他的按照教程一步一步来成功率99%以上。
区块链技术网。
- 发表于 2021-03-15 08:47
- 阅读 ( 1406 )
- 学分 ( 34 )
- 分类:FISCO BCOS
评论