開發文檔
螞蟻區塊鏈跨鏈服務是面向智能合約提供的鏈上數據服務,本服務在客戶區塊鏈環境中部署跨鏈服務合約/鏈碼,并且提供 API 接口供用戶合約/鏈碼進行調用??珂溄臃漳壳疤峁?賬本數據訪問 和 合約消息推送 兩類服務及其對應的 API 接口。賬本數據訪問服務可以幫助用戶智能合約獲取其他區塊鏈賬本上的數據,包括但不限于區塊頭,完整區塊、交易等。合約消息推送服務可以幫助部署了跨鏈數據服務的不同區塊鏈上的智能合約進行消息通信,滿足跨鏈業務關聯處理等場景。
賬本數據訪問服務
目前,賬本數據訪問服務僅支持螞蟻區塊鏈合約訪問螞蟻區塊鏈賬本數據,同時支持 Solidity 和 C++ 語言開發的智能合約。
Mychain/Solidity 合約
Solidity 合約開發流程
用戶智能合約使用賬本數據訪問 API 接口,開發流程如下:
在 BaaS 平臺上獲取賬本數據合約名稱。
獲取賬本數據訪問 API 接口定義(
ChainDataInterface.sol
,見API 接口定義章節)。在用戶合約中引入賬本數據訪問 API 定義。
用戶合約實現回調接口,用于異步接收賬本數據回調使用。
用戶合約構建賬本數據訪問請求(
ChainDataCmdHelper.sol
)。用戶合約向賬本數據合約發送請求,具體參考API 使用示例。
賬本數據服務返回數據為 JSON 格式,具體格式參考賬本數據結構部分。
API 使用流程
用戶合約調用賬本數據合約的賬本數據訪問接口發起查詢請求,賬本數據合約同步返回查詢請求句柄,即請求單據號(request_id)。
賬本數據合約獲取到查詢結果數據后,異步回調用戶合約的回調接口。
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 接口,開發流程如下:
在 BaaS 平臺上獲取賬本數據合約名稱。
獲取賬本數據訪問 API 接口定義(見API 接口定義章節)。
用戶合約構建賬本數據訪問請求。
用戶合約實現回調接口,用于異步接收賬本數據進行回調。
用戶合約向賬本數據合約發送請求,具體參考API 使用示例。
賬本數據服務返回數據為 JSON 格式,具體格式參考賬本數據結構章節。
API 使用流程
用戶合約調用賬本數據合約的賬本數據訪問接口發起查詢請求,賬本數據合約同步返回查詢請求句柄,即請求單據號(request_id)。賬本數據合約獲取到查詢結果數據后,異步回調用戶合約的回調接口。
API 接口定義
賬本數據合約實現 ChainDataRequest
接口;客戶合約調用賬本數據合約的 ChainDataRequest
接口發送請求??蛻艉霞s實現 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 接口,開發流程如下:
在 BaaS 平臺上獲取消息合約名稱。
獲取合約消息推送 API 接口定義(見API 接口定義章節)。
在用戶合約中引入合約消息推送 API 接口。
用戶合約實現接收消息接口,供跨鏈消息收發合約調用。
用戶合約調用跨鏈消息收發合約發送消息接口。
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 接口,開發流程如下:
在 BaaS 平臺上獲取消息合約名稱。
獲取合約消息推送 API 接口定義(見API 接口定義章節)。
在用戶合約中引入合約消息推送 API 接口。
用戶合約實現接收消息接口,供跨鏈消息收發合約調用。
用戶合約調用跨鏈消息收發合約發送消息接口。
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 接口,開發流程如下:
在 BaaS 平臺上獲取跨鏈鏈碼名稱。
用戶鏈碼實現接收消息接口,供跨鏈鏈碼調用,接收跨鏈消息。
用戶鏈碼調用跨鏈鏈碼發送消息接口。
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)
}