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

上一章节我们已经,部署了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 在这里插入图片描述

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
  • 阅读 ( 1970 )
  • 学分 ( 36 )
  • 分类:FISCO BCOS

0 条评论

请先 登录 后评论
向彪
向彪

区块链应用架构师

19 篇文章, 2283 学分