Developer guide
Ant Blockchain Cross-chain Service is an on-chain data service for smart contracts. This service deploys a cross-chain service contract or chaincode to a customer's blockchain environment and provides APIs that user contracts or chaincodes can call. The cross-chain service currently offers two types of services and their corresponding APIs: Ledger Data Access and Contract Message Push. The Ledger Data Access service allows user smart contracts to retrieve data from other blockchain ledgers, such as block headers, full blocks, and transactions. The Contract Message Push service enables communication between smart contracts on different blockchains where the cross-chain data service is deployed. This supports scenarios such as processing associated cross-chain business.
Ledger Data Access service
Currently, the Ledger Data Access service only supports Ant Blockchain contracts to access Ant Blockchain ledger data. It supports smart contracts developed in both Solidity and C++.
Mychain/Solidity contracts
Solidity contract development flow
To use the Ledger Data Access API in a user smart contract, follow these steps:
Obtain the ledger data contract name from the BaaS platform.
Obtain the API definition for Ledger Data Access (
ChainDataInterface.sol). For more information, see the API definition section.Import the Ledger Data Access API definition into the user contract.
Implement the callback interface in the user contract to asynchronously receive ledger data callbacks.
Build the Ledger Data Access request in the user contract (
ChainDataCmdHelper.sol).Send the request from the user contract to the ledger data contract. For more information, see API usage example.
The Ledger Data Access service returns data in JSON format. For more information about the format, see the Ledger data structure section.
API usage flow
The user contract calls the Ledger Data Access API of the ledger data contract to initiate a query request. In response, the ledger data contract synchronously returns a query request handle, which is the request ID (`request_id`).
After the ledger data contract retrieves the query result data, it asynchronously calls the callback interface of the user contract.
API definition
ChainDataInterface.sol defines the Ledger Data Access API provided by the ledger data contract. You can send requests by calling the ChainDataRequest interface of the ledger data contract. The user contract must implement the ChainDataCallbackResponse interface to receive the ledger data query results from the ledger data contract.
pragma solidity ^0.4.22;
interface ChainDataInterface {
/**
* function: chainDataRequest
* usage: The user contract calls the ledger data contract to send a cross-chain ledger data access request.
* parameters:
* _biz_id: A user-defined business request ID.
* _chain_data_cmd: The ledger data access command. For more information, see the ledger data access command description.
* _if_callback: Specifies whether the ledger data contract needs to call back the user contract with the request result.
* _callback_identity: The ID of the contract to be called back with the result. This can be the contract that sent the request or another contract.
* _delay_time: This feature is not activated. Set it to 0.
* return value: The query request ID. This is a unique request ID generated by the ledger data contract for this request.
*/
function chainDataRequest(bytes32 _biz_id, string _chain_data_cmd, bool _if_callback, identity _callback_identity, uint256 _delay_time) external returns (bytes32);
/**
* function: chainDataCallbackResponse
* usage: The interface that the user contract must implement for the ledger data contract to call back.
* parameters:
* _request_id: The ledger data access request ID. The ledger data contract returns this ID when the request is sent.
* _biz_id: The business request ID from the user contract.
* _error_code: The request result code. A value of 0 indicates that the oracle request was processed successfully. Other values indicate that the request failed. For more information, see the contract error code table.
* _resp_body: The ledger data body.
* _call_identity: The ID of the contract that initiated the request.
* return value: The call result.
*/
function chainDataCallbackResponse (bytes32 _request_id, bytes32 _biz_id, uint32 _error_code, bytes _resp_body, identity _call_identity) external returns (bool);
}API parameter description
The Ledger Data Access API requires you to specify the domain name of the blockchain to access, the resource type, the resource ID, and how to process the resource data. The command syntax is as follows.
NAME
udag
VERSION
1.0.0_BETA
SYNOPSIS
udag udag://udag-domain/CID[/path] [options]
udag-domain is the destination blockchain domain name. CID is the blockchain resource description ID. We recommend using ChainDataCmdHelper to construct it. path is the content extraction path. We do not recommend using it. Use --json-path instead.
DESCRIPTION
The udag command is a required input parameter for the Ledger Data Access service API of the cross-chain data service. The udag command describes the URI of the destination blockchain resource to access and other optional configuration items.
OPTIONS
--json-path <value>
Specifies JSONPath processing for the raw HTTP response body. This is limited to processing JSON data.
JSONPath syntax description: https://goessner.net/articles/JsonPath/
Currently supports: $.[]*
e.g.
--json-path '$.obj' Get a sub-object
--json-path '$[0]' Get an element from an array by index
--json-path "$['obj']" Get a sub-object
--json-path '$[0,1].obj' Get multiple objectsTo help you use the Ledger Data Access service, we provide the ChainDataCmdHelper.sol helper library to assist you in quickly constructing commands and developing applications.
Download the Solidity contract code sample to view the complete Solidity contract sample.
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 usage example
pragma solidity ^0.4.0;
pragma experimental ABIEncoderV2;
import './ChainDataInterface.sol';
import './ChainDataCmdHelper.sol';
// Implement a demo contract
contract BizContract {
// Ledger data contract
identity ibc_base_address;
// Association between the business ID and the ledger data access request ID
mapping(bytes32 => bytes32) requests;
// Access control
modifier onlyIbcBase() {
require(msg.sender == ibc_base_address, "PERMISSION_ERROR: Permission denied!");
_;
}
// Configure the ledger data contract ID
function setIbcBaseAddress(identity _addr) public {
ibc_base_address = _addr;
}
// Call the Ledger Data Access API of the ledger data contract to access a specific transaction on the destination blockchain
function chainDataRequest(bytes32 biz_id, string domain, bytes block_hash, string path, string parser) public {
// 1. For a cross-contract call, create a contract object using the contract interface definition and the ledger data contract ID
ChainDataInterface ibc = ChainDataInterface(ibc_base_address);
// 2. Construct the ledger data access command. Construct the block header query command
string cid = ChainDataCmdHelper.buildMychain010HeaderCID(block_hash);
string cmd = ChainDataCmdHelper.buildCommand(domain, cid, path, parser);
// 3. Send the ledger data access request
bytes32 request_id = ibc.chainDataRequest(biz_id, cmd, true, this, 0);
// 4. Record the request id returned by the ledger data contract
requests[biz_id] = request_id;
// 5. The request phase is complete. Wait for the callback
return;
}
// The business contract receives the callback for the ledger data access request result from the ledger data contract
function chainDataCallbackResponse (bytes32 _request_id, bytes32 _biz_id, uint32 _error_code, bytes _resp_body, identity _call_identity) onlyIbcBase external returns (bool) {
// Business logic to process the returned ledger data
return true;
}
}Mychain/C++ contracts
C++ contract development flow
To use the Ledger Data Access API in a user smart contract, follow these steps:
Obtain the ledger data contract name from the BaaS platform.
Obtain the API definition for Ledger Data Access. For more information, see the API definition section.
Build the Ledger Data Access request in the user contract.
Implement the callback interface in the user contract to asynchronously receive ledger data callbacks.
Send the request from the user contract to the ledger data contract. For more information, see API usage example.
The Ledger Data Access service returns data in JSON format. For more information about the format, see the Ledger data structure section.
API usage flow
The user contract calls the Ledger Data Access API of the ledger data contract to initiate a query request. In response, the ledger data contract synchronously returns a query request handle, which is the request ID (`request_id`). After the ledger data contract retrieves the query result data, it asynchronously calls the callback interface of the user contract.
API definition
The ledger data contract implements the ChainDataRequest interface. A customer contract calls the ChainDataRequest interface of the ledger data contract to send a request. The customer contract must implement the ChainDataCallbackResponse interface to receive the ledger data query results from the ledger data contract. The ledger data contract calls the ChainDataCallbackResponse interface of the customer contract to return the ledger data.
/**
* function: ChainDataRequest
* usage: The user contract calls the ledger data contract to send a cross-chain ledger data access request.
* parameters:
* _biz_id: A user-defined business request ID.
* _chain_data_cmd: The ledger data access command. For more information, see the parameter modification description in the parameter description section.
* _if_callback: Specifies whether the ledger data contract needs to call back the user contract with the request result.
* _callback_identity: The ID of the contract to be called back with the result. This can be the contract that sent the request or another contract.
* _delay_time: This feature is not activated. Set it to 0.
* return value: The query request ID. This is a unique request ID generated by the ledger data contract for this request.
*/
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: The interface that the user contract must implement for the ledger data contract to call back.
* parameters:
* _request_id: The ledger data access request ID. The ledger data contract returns this ID when the request is sent.
* _biz_id: The business request ID from the user contract.
* _error_code: The request result code. A value of 0 indicates that the oracle request was processed successfully. Other values indicate that the request failed. For more information, see the contract error code table.
* _resp_body: The ledger data body.
* _call_identity: The ID of the contract that initiated the request.
* return value: None.
*/
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 parameter description
This section describes how to construct the chain_data_cmd command. For the Ledger Data Access API, the command must specify the domain name of the blockchain to access, the resource type, the resource ID, and how to process the resource data. The command syntax is as follows.
Syntax:
udag udag://udag-domain/CID [options]
udag-domain is the destination blockchain domain name. CID is the blockchain resource description ID. options are optional parameters.
Description:
The udag command is a required input parameter for the Ledger Data Access service API of the cross-chain data service. The udag command describes the URI of the destination blockchain resource to access and other optional configuration items.
Options:
--json-path <value>
Specifies JSONPath processing for the ledger data.
JSONPath syntax description: https://goessner.net/articles/JsonPath/
Currently supports: $.[]*
e.g.
--json-path '$.obj' Get a sub-object
--json-path '$[0]' Get an element from an array by index
--json-path "$['obj']" Get a sub-object
--json-path '$[0,1].obj' Get multiple objects
API usage example
The following is a sample contract for the Ledger Data Access service.
#include <mychainlib/contract.h>
using namespace mychain;
class ChainDataRequestDemo:public Contract {
public:
// API definition for the cross-chain Ledger Data Access interface
const std::string CHAIN_DATA_REQUEST_API = "ChainDataRequest";
/**
* Call the Ledger Data Access API of the ledger data contract to access a specific transaction on the destination blockchain
**/
INTERFACE void ChainDataRequest(const std::string& ibc_base_address, const std::string& biz_id, const std::string& chain_data_cmd){
// 1. Call the cross-chain contract interface to send the request
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. Verify the call result
Require(ret.code == 0, "call corss_chain_contract fail " + address.to_hex() + " : " + ret.msg);
// 3. Process the request information
// Typically, save the request ID, which is the ret.result field
// 4. The request phase is complete. Wait for the callback
return;
}
/**
* The business contract receives the callback for the ledger data access request result from the ledger data contract
**/
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){
// Custom business logic to process the returned ledger data
// ...
}
}Ledger data structure
Structure of the Mychain block header
{
"Hash": "750a1ae6f4053ff141bd243e48713130501eb19c00ad04e0befe9dcb0f69381c",
"Number": 64259,
"Version": 0,
"ParentHash": "4ebbbb964fec85dfbe8fbb2e800944bba0532042b08fc5978da4e619cc101f1e",
"TransactionRoot": "36b8d9e7834fb09cdc62fdd73dfa96ee447b5a346417cb9d913277a9b67639c0",
"ReceiptRoot": "2ffb03de688b2163960aa86a408caf49dc8d0f984d9b22134c561371074d0e8f",
"StateRoot": "faf6f0bad73aa4bf11e60552e3b53242b7d7983ba408ca485ae7bf3799fd17b0",
"GasUsed": 20690,
"Timestamp": 1539921657732
}MyChain transaction structure
{
"Hash": "36b8d9e7834fb09cdc62fdd73dfa96ee447b5a346417cb9d913277a9b67639c0",
"Type": 0,
"Timestamp": 1539921657722,
"Nonce": 13717934618841259934,
"Period": 1539921657722,
"From": "cb84ac09120827b41e01de5494cd25bb06fd7b709879a34f72b8e44b0e6b276f",
"To": "cb84ac09120827b41e01de5494cd25bb06fd7b709879a34f72b8e44b0e6b276f",
"Value": 0,
"Gas": 1000000,
"Data": "f843b840fdbc52f9acdd26a8382b1384c831f88b99a0c19716e386b0899c9fee43befcf0b2eba0cdabc71506be74f1de93d9752023ebe999eac323f60d7c1717fed9128f64",
"Signatures": ["9ddd1019e85b8857989503de5d4aa7c5520467265e3d0dda2b3d461a60dea2906c079264bc65b04e7ffe7c6c3f60ed96c313d9c2bff57cac36835c397712e46101"]
}Mychain Block Structure
{
"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": ""
}]
}]
}Error codes
The following table lists the error codes for the ledger data contract in the Ledger Data Access service:
Error code | Hexadecimal error code | Decimal error code | Description | Solution |
OE_SUCCESS | 0x0000 | 0 | Query successful | |
OE_UNKNOWN_ERROR | 0x0002 | 2 | Unknown error | Contact the administrator |
OE_UDAG_QUERY_FAILED | 0x3000 | 12288 | Query failed | Contact the administrator |
OE_UDAG_DOMAIN_ERROR | 0x3010 | 12304 | Domain name does not exist | Check if the domain name is correct |
OE_UDAG_CID_ERROR | 0x3012 | 12306 | CID error | Check if the ledger data hash is correct |
Contract Message Push service
Currently, the Contract Message Push service supports smart contracts written in Solidity and C++ on the Mychain platform, while on the Hyperledger Fabric platform, it supports any language.
Mychain/Solidity contracts
Solidity contract development flow
To call the Contract Message Push service API from a user smart contract, follow these steps:
You can obtain the message contract name on the BaaS platform.
You can obtain the API definition for Contract Message Push (see the API definition section).
You can import the Contract Message Push API into the user contract.
You can implement the message receiving interface in the user contract so that the cross-chain message contract can call it.
You can call the message sending interface of the cross-chain message contract from the user contract.
API definition
InterContractMessageInterface.sol defines the Contract Message Push API. A user contract calls the sendMessage function of the cross-chain message contract to send a message. The user contract implements the recvMessage function to receive cross-chain messages.
pragma solidity ^0.4.22;
interface InterContractMessageInterface {
/**
* function: sendMessage
* usage: The user contract calls the cross-chain message contract to send a cross-chain message.
* parameters:
* _destination_domain: The destination blockchain domain name.
* _receiver: The contract account that receives the message. It is obtained by calculating the SHA-256 hash of the contract name or chaincode name.
* _message: The message content.
* return value: None.
*/
function sendMessage(string _destination_domain, identity _receiver, bytes _message) external;
/**
* function: recvMessage
* usage: The interface that the user contract must implement for the cross-chain message contract to call to receive cross-chain messages.
* parameters:
* _from_domain: The source blockchain of the message.
* _sender: The SHA-256 hash of the source contract account name or chaincode name.
* _message: The message content.
* return value: None.
*/
function recvMessage(string _from_domain, identity _sender, bytes _message) external ;
}API usage example
pragma solidity ^0.4.0;
pragma experimental ABIEncoderV2;
import './InterContractMessageInterface.sol';
// Implement a demo contract
contract BizContract {
// Message contract
identity ibc_msg_address;
// Access control
modifier onlyIbcMsg() {
require(msg.sender == ibc_msg_address, "PERMISSION_ERROR: Permission denied!");
_;
}
// Configure the message contract ID
function setIbcMsgAddress(identity _addr) public {
ibc_msg_address = _addr;
}
// Call the message sending interface of the message contract to send a message to a specific contract on the destination blockchain
function sendRemoteMsg (string _domain, identity _receiver, bytes _msg) public {
// 1. For a cross-contract call, create a contract object using the contract interface definition and the message contract ID
InterContractMessageInterface ibc = InterContractMessageInterface(ibc_msg_address);
// 2. Send the cross-chain message
ibc.sendMessage(_domain, _receiver, _msg);
// 3. Message sending is complete
return;
}
// The business contract receives the cross-chain message
function recvMessage ( string _from_domain, identity _sender, bytes _message ) onlyIbcMsg external {
// Business logic to process the received cross-chain message
}
}Mychain/C++ contracts
C++ contract development flow
To call the Contract Message Push service API from your smart contract, follow these steps:
You can obtain the message contract name on the BaaS platform.
You can obtain the API definition for Contract Message Push. For more information, see the API definition section.
You can import the Contract Message Push API into your user contract.
You can implement the message receiving interface in your user contract so that the cross-chain message contract can call it.
You can call the message sending interface of the cross-chain message contract from your user contract.
API definition
The following is the C++ contract API definition for the Contract Message Push service. A user contract calls the SendMessage interface of the cross-chain message contract to send a message, and implements the RecvMessage interface to receive cross-chain messages.
/**
* function: SendMessage
* usage: The user contract calls the cross-chain message contract to send a cross-chain message.
* parameters:
* _destination_domain: The destination blockchain domain name.
* _receiver: The contract account that receives the message. This is the hexadecimal value of the SHA-256 hash of the contract name or chaincode name.
* _message: The message content.
* return value: None.
*/
INTERFACE void SendMessage(const string& _destination_domain, const string& _receiver,
const string& _message);
/**
* function: RecvMessage
* usage: The interface that the user contract must implement for the cross-chain message contract to call to receive cross-chain messages.
* parameters:
* _from_domain: The source blockchain of the message.
* _sender: The hexadecimal value of the SHA-256 hash of the source contract account name or chaincode name.
* _message: The message content.
* return value: None.
*/
INTERFACE void RecvMessage(const string& _from_domain, const string& _sender,
const string& _message);API usage example
The following is a sample contract for the Contract Message Push service.
#include <mychainlib/contract.h>
using namespace mychain;
class BizContract:public Contract {
public:
// Call the message sending interface of the message contract to send a message to a specific contract on the destination blockchain
INTERFACE void sendRemoteMsg(const std::string& domain, const std::string& receiver, const std::string& msg, const std::string& p2p_msg_address){
// 1.1 Declare the contract address of the cross-chain message contract
Identity p2p(p2p_msg_address);
// 1.2 Call the message contract
auto ret = CallContract<void>(p2p, "SendMessage", 0, 0,
domain, // Destination blockchain domain name
receiver, // Receiver. The address calculated by applying SHA-256 to the Ant Chain contract name or Fabric chaincode name
msg // Cross-chain message content
);
// 2. Verify if the message was sent successfully
Require(ret.code == 0, "call p2p fail." + ret.msg);
// 3. Message sending is complete
return;
}
// The business contract receives the cross-chain message
INTERFACE void RecvMessage(const std::string& _from_domain, const std::string& _sender, const std::string& _message) {
// Custom business logic to process the received cross-chain message
return true;
}
};Fabric contracts (any language)
Smart contract development flow
To use the Contract Message Push service API in a smart contract, follow these steps:
Obtain the cross-chain chaincode name from the BaaS platform.
The user chaincode implements an interface to receive cross-chain messages, which is invoked by the cross-chain chaincode.
From the user chaincode, call the message sending interface of the cross-chain chaincode.
API definition
# Implemented by the cross-chain chaincode, called by the user chaincode
function: sendMessage
usage: The customer chaincode calls the cross-chain chaincode to send a message
params: args []string
args[0] Domain name of the destination blockchain (required)
args[1] Destination contract account (required), a 64-character hexadecimal string
args[2] Message content (required)
args[3] Message nonce (optional), an identifier to distinguish multiple messages sent within the same transaction
# Implemented by the user chaincode, called by the cross-chain chaincode
function: recvMessage
usage: The cross-chain chaincode calls the user chaincode to receive a message
params: args []string
args[0] Domain name of the source blockchain (required)
args[1] Source contract account (required), a 64-character hexadecimal string
args[2] Message content (required)API usage example (Go)
The Fabric platform provides an API for chaincode-to-chaincode invocation. Customer chaincode can be implemented in any language. The following is a customer chaincode sample written in Go.
To compile the following chaincode sample, you must download its dependencies using go.mod. You can then download the Go contract code sample and run the go build cross_chain_demo.go command.
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
)
// Instantiate the contract
func main() {
if err := shim.Start(NewCrossChainTest()); err != nil {
fmt.Printf("Error starting Biz chaincode: %s", err)
}
}
// Cross-chain contract data structure
type CrossChainTest struct {
}
// Construct the cross-chain contract
func NewCrossChainTest() *CrossChainTest {
return &CrossChainTest{
}
}
// Initialize the Init function
func (bs *CrossChainTest) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success([]byte("Init success"))
}
/*
* Contract invocation
*/
func (bs *CrossChainTest) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
fn, args := stub.GetFunctionAndParameters()
switch fn {
// User-defined method
case "testSendMessage":
// Message to be sent externally
// args[0]: Cross-chain chaincode name, obtained from the BaaS platform
// args[1]: Destination blockchain domain name
// args[2]: Receiver identity
// If the destination blockchain is Ant Blockchain, the account is the hexadecimal string of the account address (32 bytes), without the 0x prefix
// If the destination blockchain is Fabric, the account is the hexadecimal string of the SHA-256 hash of the receiving chaincode's name
// args[3]: Message content to send
// args[4]: Nonce of the message content to send
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])
// Invoke the cross-chain chaincode
var (
cc = args[0] // Cross-chain utility chaincode name
)
var args_cross = [][]byte {
[]byte("sendMessage"), // Send a cross-chain message
[]byte(args[1]), // Destination blockchain domain name
[]byte(args[2]), // Mychain customer contract address that receives the message
[]byte(args[3]), // Message to send
[]byte(args[4]), // Nonce of the message to send
}
re := stub.InvokeChaincode(cc, args_cross, stub.GetChannelID())
return re
// The customer contract implements the interface to receive ordered messages
case "recvMessage": // Receive a message
return bs.recvMessage(stub, args[0], args[1], args[2])
default:
return shim.Error("Method not found")
}
}
// The customer contract must implement this interface
func (bs *CrossChainTest) recvMessage(stub shim.ChaincodeStubInterface, sourceDomain string, sourceIdentity string, message string) pb.Response {
// sourceDomain string, // Domain name of the source blockchain
// sourceIdentity string, // Identity of the message sender
// message string) // Message content
// Add the specific implementation here
fmt.Printf("CrossChainTest recv message from domain:%s, identity:%s, msg:%s\n", sourceDomain, sourceIdentity, message)
return shim.Success(nil)
}