上一章节我们已经,部署了Truora,并且访问正常,这一章节我们开始编写我们的核心的合约,并部署连接上Truora服务。

前言

上一章节我们已经,部署了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 (三)基于区块链的自动抽奖系统从0到1实现

2.上传核心预言机合约

点击左边 合约管理 –> 合约 IDE,选择 solidity 版本,上传模板合约,包括以下五个合约: (三)基于区块链的自动抽奖系统从0到1实现

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;   } } 

确认后,选择上传目录,此处选择根目录 / (三)基于区块链的自动抽奖系统从0到1实现

(三)基于区块链的自动抽奖系统从0到1实现

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合约地址如下图所示:

(三)基于区块链的自动抽奖系统从0到1实现

5.部署APISampleOracle.sol

编译APISampleOracle.sol,点击部署按钮进行部署,填入上一步我们获取的合约地址即可。如下图所示: (三)基于区块链的自动抽奖系统从0到1实现 (三)基于区块链的自动抽奖系统从0到1实现 (三)基于区块链的自动抽奖系统从0到1实现 (三)基于区块链的自动抽奖系统从0到1实现

6.测试合约

我们点击“合约调用”,选择request方法,点击确认如下图所示:

(三)基于区块链的自动抽奖系统从0到1实现

(三)基于区块链的自动抽奖系统从0到1实现 到这里,已经执行了合约并且和链下的API进行了交互。我们也可以再执行get方法进入结果的获取如下图所示: (三)基于区块链的自动抽奖系统从0到1实现 (三)基于区块链的自动抽奖系统从0到1实现 (三)基于区块链的自动抽奖系统从0到1实现 实际我们这里可以确定我们的链下的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": falsen" +                 "}";         String res = HttpUtil.post(url, body);         if (param.equals("get")) {             // 处理格式             res = res.replace("[", "").replace("]", "").replace("000000000000000000", "");         }         return res;     } 

结果也可以在truora控制台的“历史查询”里面去查看,如下图所示: (三)基于区块链的自动抽奖系统从0到1实现 (三)基于区块链的自动抽奖系统从0到1实现

闭坑指南

问题:独立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连接参数,重启服务即可。 (三)基于区块链的自动抽奖系统从0到1实现

问题:一键部署后WeBASE-Front创建truora的合约只要重启一下服务就会消失了。
解决方案: 在webase/webase-front.yml配置文件中添加以下配置:

spring:   datasource:     url: jdbc:h2:file:/dist/h2/webasefront;DB_CLOSE_ON_EXIT=FALSE

(三)基于区块链的自动抽奖系统从0到1实现 然后重启一下服务即可解决。

PS:上述问题官方会在下一个版本进行修复

总结

Truora第三篇系列文章我们重点讲解《合约的开发与部署》,注意闭坑指南。其他的按照教程一步一步来成功率99%以上。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注