开发文档

更新时间:

蚂蚁区块链跨链服务是面向智能合约提供的链上数据服务,本服务在客户区块链环境中部署跨链服务合约/链码,并且提供 API 接口供用户合约/链码进行调用。跨链接服务目前提供 账本数据访问合约消息推送 两类服务及其对应的 API 接口。账本数据访问服务可以帮助用户智能合约获取其他区块链账本上的数据,包括但不限于区块头,完整区块、交易等。合约消息推送服务可以帮助部署了跨链数据服务的不同区块链上的智能合约进行消息通信,满足跨链业务关联处理等场景。

账本数据访问服务

目前,账本数据访问服务仅支持蚂蚁区块链合约访问蚂蚁区块链账本数据,同时支持 Solidity 和 C++ 语言开发的智能合约。

Mychain/Solidity 合约

Solidity 合约开发流程

用户智能合约使用账本数据访问 API 接口,开发流程如下:

  1. 在 BaaS 平台上获取账本数据合约名称。

  2. 获取账本数据访问 API 接口定义(ChainDataInterface.sol,见API 接口定义章节)。

  3. 在用户合约中引入账本数据访问 API 定义。

  4. 用户合约实现回调接口,用于异步接收账本数据回调使用。

  5. 用户合约构建账本数据访问请求(ChainDataCmdHelper.sol)。

  6. 用户合约向账本数据合约发送请求,具体参考API 使用示例

  7. 账本数据服务返回数据为 JSON 格式,具体格式参考账本数据结构部分。

API 使用流程

  1. 用户合约调用账本数据合约的账本数据访问接口发起查询请求,账本数据合约同步返回查询请求句柄,即请求单据号(request_id)。

  2. 账本数据合约获取到查询结果数据后,异步回调用户合约的回调接口。

API 接口定义

ChainDataInterface.sol 中定义了账本数据合约提供的账本数据访问接口,用户通过调用账本数据合约 ChainDataRequest 接口发送请求。用户合约需要实现 ChainDataCallbackResponse 接口,用于接收账本数据合约的查询账本数据结果。

pragma solidity ^0.4.22;

interface ChainDataInterface {

    /**
     * function: chainDataRequest
     * usage: 用户合约调用账本数据合约发送跨链账本数据访问请求
     * parameters:
     *         _biz_id            :用户自定义的业务请求 ID
     *         _chain_data_cmd    :账本数据访问命令,参考账本数据访问命令说明
     *         _if_callback       :是否需要账本数据合约将请求结果回调用户合约
     *         _callback_identity :账本数据合约请求结果回调的合约 ID,可以是发送请求的合约,也可以是其他合约
     *         _delay_time        :该特性未激活,填 0 即可
     * return value               :查询请求 ID,是账本数据合约为本次请求生成的唯一请求 ID
     */
    function chainDataRequest(bytes32 _biz_id, string _chain_data_cmd, bool _if_callback, identity _callback_identity, uint256 _delay_time) external returns (bytes32);

    /**
     * function: chainDataCallbackResponse
     * usage: 用户合约需要实现的接口,供账本数据合约回调
     * parameters:
     *         _request_id       :账本数据访问请求 ID(在发送账本数据请求时账本数据合约会返回此 ID)
     *         _biz_id           :用户合约的业务请求 ID
     *         _error_code       :请求结果码,如果值是 0,则表示预言机请求处理成功;如果是其他值,则为请求处理失败,详见合约错误码表
     *        _resp_body         :账本数据body
     *        _call_identity     :发起该请求的合约 ID
     * return value              :调用结果
     */
    function chainDataCallbackResponse (bytes32 _request_id, bytes32 _biz_id, uint32 _error_code, bytes _resp_body, identity _call_identity) external returns (bool);
}

API 参数说明

账本数据访问接口需要用户指定要访问的区块链域名、资源类型和资源 ID,以及对于资源数据的加工处理。详细命令语法格式如下。

NAME
    udag

VERSION
    1.0.0_BETA

SYNOPSIS
    udag udag://udag-domain/CID[/path] [options]
    其中 udag-domain 为目标区块链域名,CID 为区块链资源描述 ID,建议使用提供 ChainDataCmdHelper 进行拼装。path为内容提取路径,不建议使用,建议使用--json-path替代。

DESCRIPTION    
    udag 命令是跨链数据服务的账本数据访问服务提供的接口必要传入参数之一。udag命令描述了指定访问目的区块链资源的URI,以及其他可选配置项。

OPTIONS
  --json-path <value> 
        指定了对 http 原始响应 body 进行 jsonpath 处理,仅限于对 json 数据进行加工。
        jsonpath 语法说明:https://goessner.net/articles/JsonPath/
        目前支持:$.[]*
        e.g.
            --json-path '$.obj'  取子对象
            --json-path '$[0]'   从数组取下标
            --json-path "$['obj']" 取子对象
            --json-path '$[0,1].obj' 取多个对象

为方便用户使用账本数据访问服务,本服务提供了便于用户拼装命令的辅助库 ChainDataCmdHelper.sol 协助用户快速开发。

说明

单击下载 Solidity 合约代码示例,可以查看完整版的 Solidity 合约示例。

pragma solidity ^0.4.22;

import "strings.sol";

library ChainDataCmdHelper {

    enum Base {BASE16, BASE58, BASE64}
    enum Hash {SHA2_256, SHA3_256, KECCAK256}
    enum Content {MYCHAIN010_BLOCK, MYCHAIN010_TX, MYCHAIN010_HEADER}

    bytes1 constant udag_cid_version = hex'01';

    string constant base16_codec = "f";
    string constant base58_codec = "b";
    string constant base64_codec = "m";

    bytes1 constant sha2_256_codec = hex'32';
    bytes1 constant sha3_256_codec = hex'35';
    bytes1 constant keccak256_codec = hex'39';  

    bytes1 constant MYCHAIN010_block_codec = hex'a5';
    bytes1 constant MYCHAIN010_tx_codec = hex'a6';
    bytes1 constant MYCHAIN010_header_codec = hex'a8';    

    /**
     * @dev build mychain010 block content id
     * @param _hash bytes
     */  
    function buildMychain010BlockCID(bytes _hash) internal pure returns (string){    
        uint8 hash_length = uint8(_hash.length);
        bytes memory multihash = concat(concat(varintEncoding(uint8(sha2_256_codec)), varintEncoding(hash_length)), _hash);
        bytes memory multicontent = concat(concat(varintEncoding(uint8(udag_cid_version)), varintEncoding(uint8(MYCHAIN010_block_codec))), multihash);
        string memory multibase = bytesToHexString(multicontent);
        string memory content_id = strJoin(base16_codec, multibase);
        return content_id;        
    }

    /**
     * @dev build mychain010 tx content id
     * @param _hash bytes
     */  
    function buildMychain010TxCID(bytes _hash) internal pure returns (string){
        uint8 hash_length = uint8(_hash.length);
        bytes memory multihash = concat(concat(varintEncoding(uint8(sha2_256_codec)), varintEncoding(hash_length)), _hash);
        bytes memory multicontent = concat(concat(varintEncoding(uint8(udag_cid_version)), varintEncoding(uint8(MYCHAIN010_tx_codec))), multihash);
        string memory multibase = bytesToHexString(multicontent);
        string memory content_id = strJoin(base16_codec, multibase);
        return content_id;        
    }

    /**
     * @dev build mychain010 blockheader content id
     * @param _hash bytes
     */  
    function buildMychain010HeaderCID(bytes _hash) internal pure returns (string){
        uint8 hash_length = uint8(_hash.length);
        bytes memory multihash = concat(concat(varintEncoding(uint8(sha2_256_codec)), varintEncoding(hash_length)), _hash);
        bytes memory multicontent = concat(concat(varintEncoding(uint8(udag_cid_version)), varintEncoding(uint8(MYCHAIN010_header_codec))), multihash);
        string memory multibase = bytesToHexString(multicontent);
        string memory content_id = strJoin(base16_codec, multibase);
        return content_id;        
    }

    /**
     * @dev build udag command
     * @param _domain string
     * @param _cid string
     * @param _path string
     * @param _parser_cmd string
     * 
     */
    function buildCommand(string _domain, string _cid, string _path, string _parser_cmd) internal pure returns(string){

        string memory udag_cmd;
        string memory udag_url;
        string memory udag_url_path;

        var path_slice = strings.toSlice(_path);
        var parser_slice = strings.toSlice(_parser_cmd);
        var slash_slice = strings.toSlice("/");

        if(strings.empty(path_slice) && strings.empty(parser_slice)){
            udag_cmd = strJoin("udag://", _domain, "/", _cid);
        }
        else if(strings.empty(path_slice)){
            udag_url = strJoin("udag://", _domain, "/", _cid);
            udag_cmd = strJoin(udag_url," --json-path ", _parser_cmd);
        }
        else if(strings.empty(parser_slice)){
            udag_url = strJoin("udag://", _domain, "/", _cid);

            if(strings.startsWith(path_slice, slash_slice)){
                udag_cmd = strJoin(udag_url, _path);
            }else{
                udag_cmd = strJoin(udag_url,"/", _path);
            }

        }
        else{
            udag_url = strJoin("udag://", _domain, "/", _cid);
            if(strings.startsWith(path_slice, slash_slice)){
                udag_url_path = strJoin(udag_url, _path);
            }else{
                udag_url_path = strJoin(udag_url,"/", _path);
            }
            udag_cmd = strJoin(udag_url_path," --json-path ", _parser_cmd);
        }

        return udag_cmd;
    }

    /**
     * @dev generate udag content id
     * @param _hash bytes
     * @param _hash_codec UDAGHashList
     * @param _content_codec UDAGContentList
     * @param _base_codec UDAGBaseList
     */  
    function generateCID(bytes _hash,
                        Hash _hash_codec,
                        Content _content_codec,
                        Base _base_codec) internal pure returns (string){

        string memory content_id;
        uint8 hash_length = uint8(_hash.length);
        bytes1 hash_codec;
        bytes1 content_codec;
        string memory base_codec;

        if (_hash_codec == Hash.SHA2_256){
            hash_codec = sha2_256_codec;
        }
        else if (_hash_codec == Hash.SHA3_256){
            hash_codec = sha3_256_codec;
        }
        else if(_hash_codec == Hash.KECCAK256){
            hash_codec = keccak256_codec;
        }

        if (_base_codec == Base.BASE16){
            base_codec = base16_codec;
        }
        else if (_base_codec == Base.BASE58){
            base_codec = base58_codec;
        }
        else if(_base_codec == Base.BASE64){
            base_codec = base64_codec;
        }

        if (_content_codec == Content.MYCHAIN010_BLOCK){
            content_codec = MYCHAIN010_block_codec;
        }
        else if(_content_codec == Content.MYCHAIN010_TX){
            content_codec = MYCHAIN010_tx_codec;
        }
        else if (_content_codec == Content.MYCHAIN010_HEADER){
            content_codec = MYCHAIN010_header_codec;
        }

        bytes memory multihash = concat(concat(varintEncoding(uint8(hash_codec)), varintEncoding(hash_length)), _hash);
        bytes memory multicontent = concat(concat(varintEncoding(uint8(udag_cid_version)), varintEncoding(uint8(content_codec))), multihash);
        string memory multibase;

        if(_base_codec == Base.BASE16){
            multibase = bytesToHexString(multicontent);

        }
        else if(_base_codec == Base.BASE64){
            bool ret_code;
            (ret_code, multibase) = base64_encode(bytesToHexString(multicontent));

            if(!ret_code){
                revert("BASE_ENCODE_ERROR: Registering oracle node failed. encoding base64 AVR failed");                    
            }
        }

        content_id = strJoin(base_codec, multibase);
        return content_id;
    }

    /**
     * @dev encode unsigned varint 
     * @param _n uint8
     */  
    function varintEncoding(uint8 _n) internal pure returns (bytes){

        bytes memory varint1;
        bytes memory varint2;

        for (uint32 i = 0; i < 2; i++) {
            uint8 a = (_n & 0x7F);
            _n = _n >> 7;
            if (_n == 0) {
                varint1 = uint8ToBytes(a);

                if(i == 0){
                    return varint1;
                }
                else{
                    return concat(varint2, varint1);
                }

            } else {
                varint2 = uint8ToBytes(a | 0x80);
            }
        }
    }

    function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes) {
        bytes memory tempBytes;

        assembly {

            tempBytes := mload(0x40)
            let length := mload(_preBytes)
            mstore(tempBytes, length)
            let mc := add(tempBytes, 0x20)
            let end := add(mc, length)

            for {

                let cc := add(_preBytes, 0x20)
            } lt(mc, end) {
                mc := add(mc, 0x20)
                cc := add(cc, 0x20)
            } {

                mstore(mc, mload(cc))
            }

            length := mload(_postBytes)
            mstore(tempBytes, add(length, mload(tempBytes)))

            mc := end
            end := add(mc, length)

            for {
                let cc := add(_postBytes, 0x20)
            } lt(mc, end) {
                mc := add(mc, 0x20)
                cc := add(cc, 0x20)
            } {
                mstore(mc, mload(cc))
            }

            mstore(0x40, and(
              add(add(end, iszero(add(length, mload(_preBytes)))), 31),
              not(31) // Round down to the nearest 32 bytes.
            ))
        }

        return tempBytes;
    }

    function uint8ToBytes(uint8 input) internal pure returns (bytes) {
        bytes memory b = new bytes(1);
        byte temp = byte(input);
        b[0] = temp;
        return b;
    }

    function bytesToHexString(bytes bs) internal pure returns(string) {
        bytes memory tempBytes = new bytes(bs.length * 2);
        uint len = bs.length;
        for (uint i = 0; i < len; i++) {
            byte b = bs[i];
            byte nb = (b & 0xf0) >> 4;
            tempBytes[2 * i] = nb > 0x09 ? byte((uint8(nb) + 0x37)) : (nb | 0x30);
            nb = (b & 0x0f);
            tempBytes[2 * i + 1] = nb > 0x09 ? byte((uint8(nb) + 0x37)) : (nb | 0x30);
        }
        return string(tempBytes);
    }

    function strJoin(string _a, string _b, string _c, string _d, string _e) internal pure returns (string){
        bytes memory _ba = bytes(_a);
        bytes memory _bb = bytes(_b);
        bytes memory _bc = bytes(_c);
        bytes memory _bd = bytes(_d);
        bytes memory _be = bytes(_e);
        string memory abcde = new string(_ba.length + _bb.length + _bc.length + _bd.length + _be.length);
        bytes memory babcde = bytes(abcde);
        uint k = 0;
        for (uint i = 0; i < _ba.length; i++) babcde[k++] = _ba[i];
        for (i = 0; i < _bb.length; i++) babcde[k++] = _bb[i];
        for (i = 0; i < _bc.length; i++) babcde[k++] = _bc[i];
        for (i = 0; i < _bd.length; i++) babcde[k++] = _bd[i];
        for (i = 0; i < _be.length; i++) babcde[k++] = _be[i];
        return string(babcde);
    }

    function strJoin(string _a, string _b, string _c, string _d) internal pure returns (string) {
        return strJoin(_a, _b, _c, _d, "");
    }

    function strJoin(string _a, string _b, string _c) internal pure returns (string) {
        return strJoin(_a, _b, _c, "", "");
    }

    function strJoin(string _a, string _b) internal pure returns (string) {
        return strJoin(_a, _b, "", "", "");
    }
}

API 使用示例

pragma solidity ^0.4.0;
pragma experimental ABIEncoderV2;

import './ChainDataInterface.sol';
import './ChainDataCmdHelper.sol';

// 实现一个 demo 合约
contract BizContract {
    // 账本数据合约
    identity ibc_base_address;
    // 业务 ID 与账本数据访问请求 ID 的关联关系
    mapping(bytes32 => bytes32) requests;

    // 权限控制
    modifier onlyIbcBase() {
      require(msg.sender == ibc_base_address, "PERMISSION_ERROR: Permission denied!");
      _;
    }

    // 配置账本数据合约 ID
    function setIbcBaseAddress(identity _addr) public  {
        ibc_base_address = _addr;
    }

    // 调用账本数据合约的账本数据访问接口访问目标区块链指定一笔交易
    function chainDataRequest(bytes32 biz_id, string domain, bytes block_hash, string path, string parser) public {

         // 1. 跨合约调用,需要通过合约接口定义及账本数据合约 ID 生成一个合约对象
         ChainDataInterface ibc = ChainDataInterface(ibc_base_address);

         // 2. 拼装账本数据访问命令,拼装区块链头查询命令
         string cid = ChainDataCmdHelper.buildMychain010HeaderCID(block_hash);
         string cmd = ChainDataCmdHelper.buildCommand(domain, cid, path, parser);

         // 3. 发送账本数据访问请求
         bytes32 request_id = ibc.chainDataRequest(biz_id, cmd, true, this, 0);

         // 4. 记录账本数据合约返回的 request id
         requests[biz_id] = request_id;

         // 5. 请求阶段结束,等待回调
         return;
    }

    // 业务合约用于接收账本数据合约的账本数据访问请求结果回调
    function chainDataCallbackResponse (bytes32 _request_id, bytes32 _biz_id, uint32 _error_code, bytes _resp_body, identity _call_identity) onlyIbcBase external returns (bool) {

        // 业务处理返回账本数据
        return true;
    }
}

Mychain/C++ 合约

C++ 合约开发流程

用户智能合约使用账本数据访问 API 接口,开发流程如下:

  1. 在 BaaS 平台上获取账本数据合约名称。

  2. 获取账本数据访问 API 接口定义(见API 接口定义章节)。

  3. 用户合约构建账本数据访问请求。

  4. 用户合约实现回调接口,用于异步接收账本数据进行回调。

  5. 用户合约向账本数据合约发送请求,具体参考API 使用示例

  6. 账本数据服务返回数据为 JSON 格式,具体格式参考账本数据结构章节。

API 使用流程

用户合约调用账本数据合约的账本数据访问接口发起查询请求,账本数据合约同步返回查询请求句柄,即请求单据号(request_id)。账本数据合约获取到查询结果数据后,异步回调用户合约的回调接口。

API 接口定义

账本数据合约实现 ChainDataRequest 接口;客户合约调用账本数据合约的 ChainDataRequest 接口发送请求。客户合约实现 ChainDataCallbackResponse 接口,用于接收账本数据合约的查询账本数据结果。账本数据合约回调客户合约 ChainDataCallbackResponse 接口,返回账本数据。

 /**
  * function: ChainDataRequest
  * usage: 用户合约调用账本数据合约发送跨链账本数据访问请求
  * parameters:
  *         _biz_id            :用户自定义的业务请求 ID
  *         _chain_data_cmd    :账本数据访问命令,参考参数说明部分参数改造说明
  *         _if_callback       :是否需要账本数据合约将请求结果回调用户合约
  *         _callback_identity :账本数据合约请求结果回调的合约 ID,可以是发送请求的合约,也可以是其他合约
  *         _delay_time        :该特性未激活,填 0 即可
  * return value               :查询请求 ID,是账本数据合约为本次请求生成的唯一请求 ID
  */
INTERFACE std::string ChainDataRequest(
                        const std::string& _biz_id,
                        const std::string& _chain_data_cmd,
                        const bool& _if_callback,
                        const std::string& _callback_identity,
                        const uint32_t& _delay_time);
 /**
  * function: ChainDataCallbackResponse
  * usage: 用户合约需要实现的接口,供账本数据合约回调
  * parameters:
  *         _request_id       :账本数据访问请求 ID(在发送账本数据请求时账本数据合约会返回此 ID)
  *         _biz_id           :用户合约的业务请求 ID
  *         _error_code       :请求结果码,如果值是 0,则表示预言机请求处理成功;如果是其他值,则为请求处理失败,详见合约错误码表
  *        _resp_body         :账本数据 body
  *        _call_identity     :发起该请求的合约 ID
  * return value              :无
  */
INTERFACE void ChainDataCallbackResponse (
                                              const std::string& _request_id, 
                        const std::string& _biz_id, 
                        const uint32_t& _error_code, 
                        const std::string& _resp_body, 
                        const std::string& _call_identity);

API 参数说明

下面是针对 chain_data_cmd 的构造说明。账本数据访问接口需要用户指定要访问的区块链域名、资源类型和资源 ID,以及对于资源数据的加工处理。详细命令语法格式如下。

语法:
    udag udag://udag-domain/CID [options]
    其中 udag-domain 为目标区块链域名,CID 为区块链资源描述 ID。options 是可选参数。

描述:    
    udag 命令是跨链数据服务的账本数据访问服务提供的接口必要传入参数之一。udag 命令描述了指定访问目的区块链资源的 URI,以及其他可选配置项。

选项:
  --json-path <value> 
        指定了对账本数据进行 jsonpath 处理。
        jsonpath 语法说明:https://goessner.net/articles/JsonPath/
        目前支持:$.[]*
        e.g.
            --json-path '$.obj'  取子对象
            --json-path '$[0]'   从数组取下标
            --json-path "$['obj']" 取子对象
            --json-path '$[0,1].obj' 取多个对象

API 使用示例

以下是账本数据访问服务的客户合约示例。

#include <mychainlib/contract.h>

using namespace mychain;

class ChainDataRequestDemo:public Contract {

public:
    // 跨链账本数据访问接口的 API 定义
    const std::string CHAIN_DATA_REQUEST_API = "ChainDataRequest";

    /**
     * 调用账本数据合约的账本数据访问接口访问目标区块链指定一笔交易
     **/
    INTERFACE void ChainDataRequest(const std::string& ibc_base_address, const std::string& biz_id, const std::string& chain_data_cmd){

        // 1. 调用跨链合约接口,发送请求
        Identity address(ibc_base_address);
        auto ret = CallContract<std::string>(address, CHAIN_DATA_REQUEST_API, 0, 0,
                        biz_id,
                        chain_data_cmd,
                        true,
                        GetSelf().to_hex(),
                        0);

        // 2. 调用结果校验
        Require(ret.code == 0, "call corss_chain_contract fail " + address.to_hex() + " : " + ret.msg);

        // 3. 处理请求信息
        // 一般保存请求 ID,即 ret.result 字段

        // 4. 请求阶段结束,等待回调
        return;
    }

    /**
     * 业务合约用于接收账本数据合约的账本数据访问请求结果回调
     **/
    INTERFACE void ChainDataCallbackResponse (const std::string& _request_id, 
                                                const std::string& _biz_id, 
                                                const uint32_t& _error_code, 
                                                const std::string& _resp_body, 
                                                const std::string& _call_identity){

        // 业务自定义的逻辑,处理返回账本数据
        // ...
        
    }
}

账本数据结构

  • Mychain 区块头结构

{
    "Hash": "750a1ae6f4053ff141bd243e48713130501eb19c00ad04e0befe9dcb0f69381c",
    "Number": 64259,
    "Version": 0,
    "ParentHash": "4ebbbb964fec85dfbe8fbb2e800944bba0532042b08fc5978da4e619cc101f1e",
    "TransactionRoot": "36b8d9e7834fb09cdc62fdd73dfa96ee447b5a346417cb9d913277a9b67639c0",
    "ReceiptRoot": "2ffb03de688b2163960aa86a408caf49dc8d0f984d9b22134c561371074d0e8f",
    "StateRoot": "faf6f0bad73aa4bf11e60552e3b53242b7d7983ba408ca485ae7bf3799fd17b0",
    "GasUsed": 20690,
    "Timestamp": 1539921657732
}
  • Mychain 交易结构

{
    "Hash": "36b8d9e7834fb09cdc62fdd73dfa96ee447b5a346417cb9d913277a9b67639c0",
    "Type": 0,
    "Timestamp": 1539921657722,
    "Nonce": 13717934618841259934,
    "Period": 1539921657722,
    "From": "cb84ac09120827b41e01de5494cd25bb06fd7b709879a34f72b8e44b0e6b276f",
    "To": "cb84ac09120827b41e01de5494cd25bb06fd7b709879a34f72b8e44b0e6b276f",
    "Value": 0,
    "Gas": 1000000,
    "Data": "f843b840fdbc52f9acdd26a8382b1384c831f88b99a0c19716e386b0899c9fee43befcf0b2eba0cdabc71506be74f1de93d9752023ebe999eac323f60d7c1717fed9128f64",
    "Signatures": ["9ddd1019e85b8857989503de5d4aa7c5520467265e3d0dda2b3d461a60dea2906c079264bc65b04e7ffe7c6c3f60ed96c313d9c2bff57cac36835c397712e46101"]
}
  • Mychain 区块结构

{
    "Hash": "750a1ae6f4053ff141bd243e48713130501eb19c00ad04e0befe9dcb0f69381c",
    "Number": 64259,
    "Version": 0,
    "ParentHash": "4ebbbb964fec85dfbe8fbb2e800944bba0532042b08fc5978da4e619cc101f1e",
    "TransactionRoot": "36b8d9e7834fb09cdc62fdd73dfa96ee447b5a346417cb9d913277a9b67639c0",
    "ReceiptRoot": "2ffb03de688b2163960aa86a408caf49dc8d0f984d9b22134c561371074d0e8f",
    "StateRoot": "faf6f0bad73aa4bf11e60552e3b53242b7d7983ba408ca485ae7bf3799fd17b0",
    "GasUsed": 20690,
    "Timestamp": 1539921657732,
    "ConsensusProof": "MNrjZ54NPsJzcYv3XPfqW7kR7WjRkF8udkTJ3qbZ2OcA=",
    "Txs": [{
        "Hash": "36b8d9e7834fb09cdc62fdd73dfa96ee447b5a346417cb9d913277a9b67639c0",
        "Type": 0,
        "Timestamp": 1539921657722,
        "Nonce": 13717934618841259934,
        "Period": 1539921657722,
        "From": "cb84ac09120827b41e01de5494cd25bb06fd7b709879a34f72b8e44b0e6b276f",
        "To": "cb84ac09120827b41e01de5494cd25bb06fd7b709879a34f72b8e44b0e6b276f",
        "Value": 0,
        "Gas": 1000000,
        "Data": "f843b840fdbc52f9acdd26a8382b1384c831f88b99a0c19716e386b0899c9fee43befcf0b2eba0cdabc71506be74f1de93d9752023ebe999eac323f60d7c1717fed9128f64",
        "Signatures": ["9ddd1019e85b8857989503de5d4aa7c5520467265e3d0dda2b3d461a60dea2906c079264bc65b04e7ffe7c6c3f60ed96c313d9c2bff57cac36835c397712e46101"]
    }],
    "Receipts": [{
        "Offset": "0",
        "Result": "SUCCESS",
        "GasUsed": "20690",
        "Output": "0",
        "LogEntries": [{
            "From": "cb84ac09120827b41e01de5494cd25bb06fd7b709879a34f72b8e44b0e6b276f",
            "To": "b84ac09120827b41e01de5494cd25bb06fd7b709879a34f72b8e44b0e6b276f",
            "Topics": ["7570646174655f617574685f6d6170"],
            "LogData": ""
        }]
    }]
}

错误码

下表汇总了使用账本数据访问服务过程中的账本数据合约相关错误码:

错误码

16 进制错误码

10 进制错误

描述

解决方法

OE_SUCCESS

0x0000

0

查询成功

OE_UNKNOWN_ERROR

0x0002

2

未知错误

联系管理员

OE_UDAG_QUERY_FAILED

0x3000

12288

查询失败

联系管理员

OE_UDAG_DOMAIN_ERROR

0x3010

12304

域名不存在

检查域名正确性

OE_UDAG_CID_ERROR

0x3012

12306

CID 错误

检查账本数据哈希是否正确

合约消息推送服务

目前,合约消息推送服务支持的客户智能合约语言包括 Mychain 平台的 Solidity 和 C++ 语言;Hyperledger Fabric 平台不限语言。

Mychain/Solidity 合约

Solidity 合约开发流程

用户智能合约使用合约消息推送服务 API 接口,开发流程如下:

  1. 在 BaaS 平台上获取消息合约名称。

  2. 获取合约消息推送 API 接口定义(见API 接口定义章节)。

  3. 在用户合约中引入合约消息推送 API 接口。

  4. 用户合约实现接收消息接口,供跨链消息收发合约调用。

  5. 用户合约调用跨链消息收发合约发送消息接口。

API 接口定义

InterContractMessageInterface.sol 中定义了跨链消息收发合约提供的合约消息推送 API 接口,用户合约调用跨链消息收发合约sendMessage 接口发送消息。用户合约实现 recvMessage接口,用于接收跨链消息。

pragma solidity ^0.4.22;

interface InterContractMessageInterface {

    /**
     * function: sendMessage
     * usage: 用户合约call 跨链消息收发合约发送跨链消息
     * parameters:
     *         _destination_domain   :目标区块链域名
     *         _receiver                      :接收消息的合约账号,根据合约名称或者链码名计算sha256获得
     *         _message                    :消息内容
     * return value                         :无
     */
    function sendMessage(string _destination_domain, identity _receiver, bytes _message) external;

    /**
     * function: recvMessage
     * usage: 用户合约需要实现的接口,供跨链消息收发合约调用,接收跨链消息
     * parameters:
     *         _from_domain     :消息来源区块链
     *         _sender               :消息来源的合约账号名称/链码名的sha256哈希值
     *         _message           :消息内容
     * return value                :无
     */
    function recvMessage(string _from_domain, identity _sender, bytes _message) external ;
}

API 使用示例

pragma solidity ^0.4.0;
pragma experimental ABIEncoderV2;

import './InterContractMessageInterface.sol';

// 实现一个 demo 合约
contract BizContract {
    // 消息合约
    identity ibc_msg_address;

    // 权限控制
    modifier onlyIbcMsg() {
      require(msg.sender == ibc_msg_address, "PERMISSION_ERROR: Permission denied!");
      _;
    }

    // 配置消息合约 ID
    function setIbcMsgAddress(identity _addr) public  {
        ibc_msg_address = _addr;
    }

    // 调用消息合约的发送消息接口,发送一笔消息给目标区块链上特定合约
    function sendRemoteMsg (string _domain, identity _receiver, bytes _msg) public {

         // 1. 跨合约调用,需要通过合约接口定义及消息合约 ID 生成一个合约对象
         InterContractMessageInterface ibc = InterContractMessageInterface(ibc_msg_address);

         // 2. 发送跨链消息
         ibc.sendMessage(_domain, _receiver, _msg);

         // 3. 发送消息结束
         return;
    }

    // 业务合约用于接收账本数据合约的账本数据访问请求结果回调
    function recvMessage ( string _from_domain, identity _sender, bytes _message ) onlyIbcMsg external {

        // 业务处理接收到的跨链消息
        
    }
}

Mychain/C++ 合约

C++ 合约开发流程

用户智能合约使用合约消息推送服务 API 接口,开发流程如下:

  1. 在 BaaS 平台上获取消息合约名称。

  2. 获取合约消息推送 API 接口定义(见API 接口定义章节)。

  3. 在用户合约中引入合约消息推送 API 接口。

  4. 用户合约实现接收消息接口,供跨链消息收发合约调用。

  5. 用户合约调用跨链消息收发合约发送消息接口。

API 接口定义

以下是合约消息推送服务的 C++ 合约 API 接口定义。用户合约调用跨链消息收发合约 SendMessage 接口发送消息。用户合约实现 RecvMessage 接口,用于接收跨链消息。

/**
 * function: SendMessage
 * usage: 用户合约调用跨链消息收发合约发送跨链消息
 * parameters:
 *         _destination_domain   :目标区块链域名
 *         _receiver                      :接收消息的合约账号,根据合约名称或者链码名计算 SHA256 的 hex 值
 *         _message                    :消息内容
 * return value                         :无
 */
INTERFACE void SendMessage(const string& _destination_domain, const string& _receiver,
                           const string& _message);

/**
 * function: RecvMessage
 * usage: 用户合约需要实现的接口,供跨链消息收发合约调用,接收跨链消息
 * parameters:
 *         _from_domain     :消息来源区块链
 *         _sender               :消息来源的合约账号名称/链码名的 SHA256 的 hex 值
 *         _message           :消息内容
 * return value                :无
 */
INTERFACE void RecvMessage(const string& _from_domain, const string& _sender,
                           const string& _message);

API 使用示例

以下是合约消息推送服务的客户合约示例。

#include <mychainlib/contract.h>

using namespace mychain;

class BizContract:public Contract {
public:
    // 调用消息合约的发送消息接口,发送一笔消息给目标区块链上特定合约
    INTERFACE void sendRemoteMsg(const std::string& domain, const std::string& receiver, const std::string& msg, const std::string& p2p_msg_address){

        // 1.1 声明跨链消息合约的合约地址
        Identity p2p(p2p_msg_address);

        // 1.2 调用消息合约
        auto ret = CallContract<void>(p2p, "SendMessage", 0, 0,
                              domain, // 目标区块链域名
                              receiver,  // 接收者 蚂蚁链合约名称/Fabric链码名称,经过sha256 计算后的地址
                              msg // 跨链消息内容
                    ); 

        // 2. 校验是否发送成功
        Require(ret.code == 0, "call p2p fail." + ret.msg);

        // 3. 发送消息结束
        return;
    }

    // 业务合约用于接收账本数据合约的账本数据访问请求结果回调
    INTERFACE void RecvMessage(const std::string& _from_domain, const std::string& _sender, const std::string& _message) {

        // 业务自定义逻辑,处理接收到的跨链消息
        return true;
    }
};

Fabric 合约(不限语言)

智能合约开发流程

用户智能合约使用合约消息推送服务 API 接口,开发流程如下:

  1. 在 BaaS 平台上获取跨链链码名称。

  2. 用户链码实现接收消息接口,供跨链链码调用,接收跨链消息。

  3. 用户链码调用跨链链码发送消息接口。

API 接口定义

# 跨链链码实现,用户链码调用
function:sendMessage
usage: 客户链码调用跨链链码发送消息
params: args []string
    args[0] 目的地区块链的域名(必选)
    args[1] 目的地合约账号(必选),十六进制字符串,长度 64
    args[2] 消息内容(必选)
    args[3] 消息nounce(可选),区分同一笔交易内发送多个消息的标识

# 用户链码实现,跨链链码调用
function:recvMessage
usage: 跨链链码调用用户链码接收消息
params: args []string
    args[0] 源区块链的域名(必选)
    args[1] 源合约账号(必选),十六进制字符串,长度为 64
    args[2] 消息内容(必选)

API 使用示例(Go 语言)

Fabric 平台提供的 API 是链码间调用 API,客户链码可以使用任意语言来实现其业务。以下是以 Go 语言开发的客户链码示例。

以下链码示例中需要通过go.mod下载依赖,才能编译成功。您可以下载 Go 合约代码示例,通过执行命令go build cross_chain_demo.go即可成功编译该合约。

package main

import (
    "fmt"
    "github.com/hyperledger/fabric/core/chaincode/shim"
    pb "github.com/hyperledger/fabric/protos/peer"
)

// 实例化合约
func main() {
    if err := shim.Start(NewCrossChainTest()); err != nil {
        fmt.Printf("Error starting Biz chaincode: %s", err)
    }
}

// 跨链合约数据结构
type CrossChainTest struct {
}

// 构造跨链合约
func NewCrossChainTest() *CrossChainTest {
    return &CrossChainTest{
    }
}

// 初始化 Init 函数
func (bs *CrossChainTest) Init(stub shim.ChaincodeStubInterface) pb.Response {
    return shim.Success([]byte("Init success"))
}

/*
 * 合约调用
 */
func (bs *CrossChainTest) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    fn, args := stub.GetFunctionAndParameters()

    switch fn {

    // 用户自定义方法
    case "testSendMessage":
        // 对外发送的消息
        // args[0]: 跨链链码名字,从 Baas 平台上查询获取
        // args[1]: 目的区块链域名
        // args[2]: 接收者身份
        //          如果目的区块链是蚂蚁区块链,账号为账号地址 (32 字节) hex 字符串,不要以 0x 开头
        //          如果目的区块链是 Fabric,账号为接收消息的链码名字进行 sha256 后哈希值的 hex 字符串
        // args[3]: 发送消息内容
        // args[4]: 发送消息内容 nounce
        if len(args) != 5 {
            fmt.Println("Unexpected args len")
            return shim.Error("Unexpected args len")
        }
        fmt.Printf("CrossChainTest send message to %s::%s, content is %s\n", args[0], args[1], args[2])

        // 调用跨链链码
        var (
            cc = args[0]   // 跨链utility链码名字
        )
        var args_cross = [][]byte {
            []byte("sendMessage"),  // 发送跨链消息
            []byte(args[1]),  // 目标区块链域名
            []byte(args[2]),  // 接收消息的 mychain 客户合约地址
            []byte(args[3]),  // 发送的消息
            []byte(args[4]),  // 发送的消息 nounce
        }
        re := stub.InvokeChaincode(cc, args_cross, stub.GetChannelID())
        return re

    //客户合约实现接收有序消息接口
    case "recvMessage": // 接收消息
        return bs.recvMessage(stub, args[0], args[1], args[2])

    default:
        return shim.Error("Method not found")
    }
}

//客户合约必须实现接口
func (bs *CrossChainTest) recvMessage(stub shim.ChaincodeStubInterface, sourceDomain string, sourceIdentity string, message string) pb.Response {
    //  sourceDomain stirng,   // 消息来源区块链的域名
    //  sourceIdentity string, // 消息发送者身份
    //  message string)        // 消息内容
    //  补充具体实现
    fmt.Printf("CrossChainTest recv message from domain:%s, identity:%s, msg:%s\n", sourceDomain, sourceIdentity, message)
    return shim.Success(nil)
}