3. XuperChain RPC 接口使用说明

XuperChain为方便用户深度使用 XuperChain 的各项功能,提供了多语言版本的SDK(JSGolangC#JavaPython),这里我们以Golang为例来介绍一下XuperChain的RPC接口使用方式。

注解

目前官方提供的SDK中,golang语言版本的功能最为全面,其他语言的功能弱一些,我们非常欢迎社区朋友一起建设SDK,参与贡献会获得开放网络的资源,可用于购买开放网络的服务。

3.1. RPC接口介绍

查看XuperChain的 proto文件 ,可以在service定义中获取所有支持的RPC接口

3.1.1. GetBalance

此接口用于查询指定地址中的余额

参数结构

AddressStatus

返回结构

AddressStatus

这里 AddressStatus 的定义如下

1message AddressStatus {
2    Header header = 1;
3    string address = 2;
4    repeated TokenDetail bcs = 3;
5}

其中的 address 字段为需要查询的地址,传入string即可

其中的 bcs 字段为需要查询的链名,因为XuperChain支持平行链的功能,此字段为列表,亦可传入多个链名,

TokenDetail 定义如下:

1message TokenDetail {
2    string bcname = 1;
3    string balance = 2;
4    XChainErrorEnum error = 3;
5}

请求时只需传入 bcname 字段,例如 “xuper”,其余字段为返回时携带的,balance即为对应平行链上的余额

其中的 Header 如下

1message Header {
2    string logid = 1;
3    string from_node = 2;
4    XChainErrorEnum error = 3;
5}

Header中的logid是回复中也会携带的id,用来对应请求或追溯日志使用的,一般用 core/global/common.go 中的 Glogid() 生成一个全局唯一id

Header中的from_node一般不需要填写,error字段也是返回中携带的错误内容,发请求时不需填写

以下为Golang示例

 1opts := make([]grpc.DialOption, 0)
 2opts = append(opts, grpc.WithInsecure())
 3opts = append(opts, grpc.WithMaxMsgSize(64<<20-1))
 4conn, _ := grpc.Dial("127.0.0.1:37101", opts...)
 5cli := pb.NewXchainClient(conn)
 6
 7bc := &pb.TokenDetail{
 8    Bcname: "xuper",
 9}
10in := &pb.AddressStatus{
11    Header: global.Glogid(),
12    Address: "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN",
13    Bcs: []*pb.TokenDetail{bc},
14}
15out, _ := cli.GetBalance(context.Background(), in)

3.1.2. GetBalanceDetail

此接口用于查询指定地址中的余额详细情况

参数结构

AddressBalanceStatus

返回结构

AddressBalanceStatus

AddressBalanceStatus 定义如下

1message AddressBalanceStatus {
2    Header header = 1;
3    string address = 2;
4    repeated TokenFrozenDetails tfds = 3;
5}

address字段与GetBalance一样,tfds字段则多了是否冻结的内容,tfds在请求中只需要填充bcname,返回时会有TokenFrozenDetail数组给出正常余额和冻结余额的信息

以下为Golang示例

 1opts := make([]grpc.DialOption, 0)
 2opts = append(opts, grpc.WithInsecure())
 3opts = append(opts, grpc.WithMaxMsgSize(64<<20-1))
 4conn, _ := grpc.Dial("127.0.0.1:37101", opts...)
 5cli := pb.NewXchainClient(conn)
 6
 7tfd := &pb.TokenFrozenDetails{
 8    Bcname: "xuper",
 9}
10in := &pb.AddressBalanceStatus{
11    Header: global.Glogid(),
12    Address: "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN",
13    Tfds: []*pb.TokenFrozenDetails{bc},
14}
15out, _ := cli.GetBalanceDetail(context.Background(), in)

3.1.3. GetFrozenBalance

此接口用于查询指定地址中的冻结余额,请求方式与GetBalance完全一致,这里不再赘述

3.1.4. GetBlock

此接口用于查询指定id的区块内容

参数结构

BlockID

返回结构

Block

BlockID 定义如下

1message BlockID {
2    Header header = 4;
3    string bcname = 1;
4    bytes blockid = 2;
5    bool need_content = 3;  //是否需要内容
6}

header和bcname字段如上所述,blocked为要查询的区块id,注意是bytes类型,可能需要hex decode

need_content字段为布尔值,表明是否需要详细的区块内容(还是只查询区块是否在链和前驱后继)

以下为Golang示例

 1opts := make([]grpc.DialOption, 0)
 2opts = append(opts, grpc.WithInsecure())
 3opts = append(opts, grpc.WithMaxMsgSize(64<<20-1))
 4conn, _ := grpc.Dial("127.0.0.1:37101", opts...)
 5cli := pb.NewXchainClient(conn)
 6
 7id, _ := hex.DecodeString("ee0d6fd34df4a7e1540df309d47441af4fda6fdd9d841046f18e7680fe0cea8c")
 8in := &pb.BlockID{
 9    Header: global.Glogid(),
10    Bcname: "xuper",
11    Blockid: id,
12    NeedContent: true,
13}
14out, _ := cli.GetBlock(context.Background(), in)

3.1.5. GetBlockByHeight

此接口用于查询指定高度的区块内容

参数结构

BlockHeight

返回结构

Block

BlockHeight定义如下

1message BlockHeight {
2    Header header = 3;
3    string bcname = 1;
4    int64 height = 2;
5}

同GetBlock类似,id换成整型的高度即可,返回内容也是类似的

3.1.6. GetBlockChainStatus

此接口用于查询指定链的当前状态

参数结构

BCStatus

返回结构

BCStatus

BCStatus定义如下

1message BCStatus {
2    Header header = 1;
3    string bcname = 2;
4    LedgerMeta meta = 3;
5    InternalBlock block = 4;
6    UtxoMeta utxoMeta = 5;
7    repeated string branchBlockid = 6;
8}

传入参数只需填充header,bcname即可

以下为Golang示例

 1opts := make([]grpc.DialOption, 0)
 2opts = append(opts, grpc.WithInsecure())
 3opts = append(opts, grpc.WithMaxMsgSize(64<<20-1))
 4conn, _ := grpc.Dial("127.0.0.1:37101", opts...)
 5cli := pb.NewXchainClient(conn)
 6
 7in := &pb.BCStatus{
 8    Header: global.Glogid(),
 9    Bcname: "xuper",
10}
11out, _ := cli.GetBlockChainStatus(context.Background(), in)

3.1.7. GetBlockChains

此接口用于查询当前节点上有哪些链

参数结构

CommonIn

返回结构

BlockChains

CommonIn结构很简单,只有header字段,返回的BlockChains也仅有一个链名的string数组

以下为Golang示例

 1opts := make([]grpc.DialOption, 0)
 2opts = append(opts, grpc.WithInsecure())
 3opts = append(opts, grpc.WithMaxMsgSize(64<<20-1))
 4conn, _ := grpc.Dial("127.0.0.1:37101", opts...)
 5cli := pb.NewXchainClient(conn)
 6
 7in := &pb.CommonIn{
 8    Header: global.Glogid(),
 9}
10out, _ := cli.GetBlockChains(context.Background(), in)

3.1.8. GetSystemStatus

此接口用于查询当前节点的运行状态

参数结构

CommonIn

返回结构

SystemsStatusReply

此接口相当于先查询了GetBlockChains,在用GetBlockChainStatus查询每个链的状态,不在赘述

3.1.9. GetNetURL

此接口用于查询当前节点的netUrl

参数结构

CommonIn

返回结构

RawUrl

RawUrl除了header字段外仅有一个string字段,表示返回的netURL

3.1.10. QueryACL

此接口用于查询指定合约账号的ACL内容

参数结构

AclStatus

返回结构

AclStatus

AclStatus定义如下

1message AclStatus {
2    Header header = 1;
3    string bcname = 2;
4    string accountName = 3;
5    string contractName = 4;
6    string methodName = 5;
7    bool confirmed = 6;
8    Acl acl = 7;
9}

请求中仅需填充header,bcname,accountName即可,其余为返回内容

以下为Golang示例

1in := &pb.AclStatus{
2    Header: global.Glogid(),
3    Bcname: "xuper",
4    AccountName: "XC1111111111111111@xuper",
5}
6out, _ := cli.QueryACL(context.Background(), in)

3.1.11. QueryTx

此接口用于查询指定id的交易内容

参数结构

TxStatus

返回结构

TxStatus

TxStatus定义如下

1message TxStatus {
2    Header header = 1;
3    string bcname = 2;
4    bytes txid = 3;
5    TransactionStatus status = 4;  //当前状态
6    int64 distance = 5;  //离主干末端的距离(如果在主干上)
7    Transaction tx = 7;
8}

请求中仅需填充header,bcname,txid字段

以下为Golang示例

1id, _ := hex.DecodeString("763ac8212c80b8789cefd049f1529eafe292f4d64eaffbc2d5fe19c79062a484")
2in := &pb.AclStatus{
3    Header: global.Glogid(),
4    Bcname: "xuper",
5    Txid: id,
6}
7out, _ := cli.QueryTx(context.Background(), in)

3.1.12. SelectUTXO

此接口用于获取账号可用的utxo列表

参数结构

UtxoInput

返回结构

UtxoOutput

UtxoInput定义如下

 1message UtxoInput {
 2    Header header = 1;
 3    // which bcname to select
 4    string bcname = 2;
 5    // address to select
 6    string address = 3;
 7    // publickey of the address
 8    string publickey = 4;
 9    // totalNeed refer the total need utxos to select
10    string totalNeed = 5;
11    // userSign of input
12    bytes userSign = 7;
13    // need lock
14    bool needLock = 8;
15}

请求中只需填充header,bcname,address,totalNeed,needLock,其中needLock表示是否需要锁定utxo(适用于并发执行场景)

UtxoOutput中的返回即可在组装交易时使用,具体组装交易的过程可参考文档下方

1in := &pb.UtxoInput{
2    Header: global.Glogid(),
3    Bcname: "xuper",
4    Address: "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN",
5    TotalNeed: "50",
6    NeedLock: true,
7}
8out, _ := cli.SelectUTXO(context.Background(), in)

3.1.13. SelectUTXOBySize

此接口用于获取账号中部分utxo,填满交易后便不在继续获取

参数结构

UtxoInput

返回结构

UtxoOutput

使用过程和SelectUTXO基本相同,仅少了totalNeed字段。适用拥有太多utxo,一次SelectUtxo内容超过交易容纳上限时使用

3.1.14. PreExec

此接口用于在节点上进行合约的预执行操作,返回预执行后的请求和回复

参数结构

InvokeRPCRequest

返回结构

InvokeRPCResponse

InvokeRPCRequest定义如下

1message InvokeRPCRequest {
2    Header header = 1;
3    string bcname = 2;InvokeRequest
4    repeated  requests = 3;
5    string initiator = 4;
6    repeated string auth_require = 5;
7}

其中的InvokeRequest定义如下

1message InvokeRequest {
2    string module_name = 1;
3    string contract_name = 2;
4    string method_name = 3;
5    map<string, bytes> args = 4;
6    repeated ResourceLimit resource_limits = 5;
7    string amount = 6;
8}

其中必填字段有module_name,contract_name,method_name,args,具体示例可参见下一章节

3.1.15. PreExecWithSelectUTXO

此接口用于在节点上进行消耗资源的合约预执行操作,内部是由一个PreExec加上一个SelectUTXO实现的,预执行并选择出需要消耗数额的utxo

参数结构

PreExecWithSelectUTXORequest

返回结构

PreExecWithSelectUTXOResponse

PreExecWithSelectUTXORequest定义如下,实际上就是把预执行的请求结构放在了SelectUTXO结构中

1message PreExecWithSelectUTXORequest {
2    Header header = 1;
3    string bcname = 2;
4    string address = 3;
5    int64 totalAmount = 4;
6    SignatureInfo signInfo = 6;
7    bool needLock = 7;
8    InvokeRPCRequest request = 5;
9}

具体填充方式可参考下一章节

3.1.16. PostTx

此接口用于提交交易,是大部分操作都需要的最终环节

参数结构

TxStatus

返回结构

CommonReply

请求结构TxStatus定义在QueryTx中已经给出,但提交交易时需要填充Transaction字段,定义如下

 1message Transaction {
 2    // txid is the id of this transaction
 3    bytes txid = 1;
 4    // the blockid the transaction belong to
 5    bytes blockid = 2;
 6    // Transaction input list
 7    repeated TxInput tx_inputs = 3;
 8    // Transaction output list
 9    repeated TxOutput tx_outputs = 4;
10    // Transaction description or system contract
11    bytes desc = 6;
12    // Mining rewards
13    bool coinbase = 7;
14    // Random number used to avoid replay attacks
15    string nonce = 8;
16    // Timestamp to launch the transaction
17    int64 timestamp = 9;
18    // tx format version; tx格式版本号
19    int32 version = 10;
20    // auto generated tx
21    bool autogen = 11;
22    repeated TxInputExt tx_inputs_ext = 23;
23    repeated TxOutputExt tx_outputs_ext = 24;
24    repeated InvokeRequest contract_requests = 25;
25    // 权限系统新增字段
26    // 交易发起者, 可以是一个Address或者一个Account
27    string initiator = 26;
28    // 交易发起需要被收集签名的AddressURL集合信息,包括用于utxo转账和用于合约调用
29    repeated string auth_require = 27;
30    // 交易发起者对交易元数据签名,签名的内容包括auth_require字段
31    repeated SignatureInfo initiator_signs = 28;
32    // 收集到的签名
33    repeated SignatureInfo auth_require_signs = 29;
34    // 节点收到tx的时间戳,不参与签名
35    int64 received_timestamp = 30;
36    // 统一签名(支持多重签名/环签名等,与initiator_signs/auth_require_signs不同时使用)
37    XuperSignature xuper_sign = 31;
38    // 可修改区块链标记
39    ModifyBlock modify_block = 32;
40}

Transaction属于XuperChain中比较核心的结构了,下一章我们将介绍各种场景的交易如何构造并提交

3.2. RPC接口应用

本章节将以几个简单的场景为例描述RPC接口的使用方法,主要体现逻辑和步骤。代码中仅使用了原始的RPC接口,如果使用SDK则会简便很多。

3.2.1. 发起一次转账

这里我们演示如何使用RPC接口实现从账号Aclie向账号Bob的一次数额为10的转账,为了进行此操作,我们事先需要有以下信息(均为string)

Alice的地址

addr_alice

Alice的公钥

pub_alice

Alice的私钥

pri_alice

Bob的地址

addr_bob

发起转账交易的总体逻辑为,首先通过SelectUTXO获取Alice数额为10的资产,然后构造交易,最后通过PostTx提交

 1// 获取Alice的utxo
 2utxoreq := &pb.UtxoInput{
 3    Header: global.Glogid(),
 4    Bcname: "xuper",
 5    Address: addr_alice,
 6    TotalNeed: "10",
 7    NeedLock: true,
 8}
 9utxorsp, _ := cli.SelectUTXO(context.Background(), utxoreq)
10// 声明一个交易,发起者为Alice地址,因为是转账,所以Desc字段什么都不填
11// 如果是提案等操作,将客户端的 --desc 参数写进去即可
12tx := &pb.Transaction{
13    Version: 1,
14    Coinbase: false,
15    Desc: []byte(""),
16    Nonce: global.GenNonce(),
17    Timestamp: time.Now().UnixNano(),
18    Initiator: addr_alice,
19}
20// 填充交易的输入,即Select出来的Alice的utxo
21for _, utxo := range utxorsp.UtxoList {
22    txin := &pb.TxInput{
23        RefTxid: utxo.RefTxid,
24        RefOffset: utxo.RefOffset,
25        FromAddr: utxo.ToAddr,
26        Amount: utxo.Amount,
27    }
28    tx.TxInputs = append(tx.TxInputs, txin)
29}
30// 填充交易的输出,即给Bob的utxo,注意Amount字段的类型
31amount, _ := big.NewInt(0).SetString("10", 10)
32txout := &pb.TxOutput{
33    ToAddr: []byte(addr_bob),
34    Amount: amount.Bytes(),
35}
36tx.TxOutputs = append(tx.TxOutputs, txout)
37// 如果Select出来的Alice的utxo多于10,需要构造一个给Alice的找零
38total, _ := big.NewInt(0).SetString(utxorsp.TotalSelected, 10)
39if total.Cmp(amount) > 0 {
40    delta := total.Sub(total, amount)
41    charge := &pb.TxOutput{
42        ToAddr: []byte(addr_alice),
43        Amount: delta.Bytes(),
44    }
45    tx.TxOutputs = append(tx.TxOutputs, charge)
46}
47// 接下来用Alice的私钥对交易进行签名,在此交易中,我们只需Alice签名确认即可
48tx.AuthRequire = append(tx.AuthRequire, addr_alice)
49// 签名需要的库在 github.com/xuperchain/xuperchain/core/crypto/client
50// 和 github.com/xuperchain/xuperchain/core/crypto/hash
51cryptoCli, _ := client.CreateCryptoClient("default")
52sign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_alice))
53signInfo := &pb.SignatureInfo{
54    PublicKey: pub_alice,
55    Sign: sign,
56}
57// 将签名填充进交易
58tx.InitiatorSigns = append(tx.InitiatorSigns, signInfo)
59tx.AuthRequireSigns = append(tx.AuthRequireSigns, signInfo)
60// 生成交易ID
61tx.Txid, _ = txhash.MakeTransactionID(tx)
62// 构造最终要Post的TxStatus
63txs := &pb.TxStatus{
64    Bcname: "xuper",
65    Status: pb.TransactionStatus_UNCONFIRM,
66    Tx: tx,
67    Txid: tx.Txid,
68}
69// 最后一步,执行PostTx
70rsp, err := cli.PostTx(context.Background(), txs)
71// 这里的rsp即CommonReply,包含logid等内容
72// 交易id我们已经生成在tx.Txid中,不过是bytes,输出可能需要hex.EncodeToString一下

3.2.2. 新建合约账号

这里我们演示创建一个合约账号 XC1234567812345678@xuper ,ACL如下

 1{
 2    "pm": {
 3        "rule": 1,
 4        "acceptValue": 1.0
 5    },
 6    "aksWeight": {
 7        "XXXaddress-aliceXXX" : 0.6,
 8        "XXXXaddress-bobXXXX" : 0.4
 9    }
10}

为了进行此操作,我们事先需要有以下信息

Alice的地址

addr_alice

Alice的公钥

pub_alice

Alice的私钥

pri_alice

ACL的内容

acct_acl

创建合约账号的总体逻辑为,首先进行创建合约账号的预执行,然后构造相应的交易内容(如果需要支付资源由Alice出),最后提交交易

  1// 构造创建合约账号的请求
  2args := make(map[string][]byte)
  3args["account_name"] = []byte(1234567812345678)
  4args["acl"] = []byte(acct_acl)
  5invokereq := &pb.InvokeRequest{
  6    ModuleName: "xkernel",
  7    MethodName: "NewAccount",
  8    Args: args,
  9}
 10invokereqs := []*pb.InvokeRequest{invokereq}
 11// 构造合约预执行的请求
 12authrequire := []string{addr_alice}
 13rpcreq := &pb.InvokeRPCRequest{
 14    Header: global.Glogid(),
 15    Bcname: "xuper",
 16    Requests: invokereqs,
 17    Initiator: addr_alice,
 18    AuthRequire: authrequire,
 19}
 20// 花手续费需要出资的账号确认,填充一个验证的签名,才能正确的拿出utxo来
 21// 签名需要的库在 github.com/xuperchain/xuperchain/core/crypto/client
 22// 和 github.com/xuperchain/xuperchain/core/crypto/hash
 23content := hash.DoubleSha256([]byte("xuper" + addr_alice + "0" + "true"))
 24cryptoCli, _ := client.CreateCryptoClient("default")
 25prikey, _ := cryptoCli.GetEcdsaPrivateKeyFromJSON([]byte(pri_alice))
 26sign, _ := cryptoCli.SignECDSA(prikey, content)
 27signInfo := &pb.SignatureInfo{
 28    PublicKey: pub_alice,
 29    Sign: sign,
 30}
 31// 组合一个PreExecWithSelectUTXORequest用来预执行同时拿出需要支付的Alice的utxo
 32prereq := &pb.PreExecWithSelectUTXORequest{
 33    Header: global.Glogid(),
 34    Bcname: "xuper",
 35    Address: addr_alice,
 36    TotalAmount: 0,
 37    SignInfo: signInfo,
 38    NeedLock: true,
 39    Request: rpcreq,
 40}
 41prersp := cli.PreExecWithSelectUTXO(context.Background(), prereq)
 42// 构造一个Alice发起的交易
 43tx := &pb.Transaction{
 44    Version: 1,
 45    Coinbase: false,
 46    Desc: []byte(""),
 47    Nonce: global.GenNonce(),
 48    Timestamp: time.Now().UnixNano(),
 49    Initiator: addr_alice,
 50}
 51// 填充支付的手续费,手续费需要“转账”给地址“$”
 52amount := big.NewInt(prersp.Response.GasUsed)
 53fee := &pb.TxOutput{
 54    ToAddr: []byte("$"),
 55    Amount: amount.Bytes(),
 56}
 57tx.TxOutputs = append(tx.TxOutputs, fee)
 58// 填充select出来的Alice的utxo
 59for _, utxo := range prersp.UtxoOutput.UtxoList {
 60    txin := &pb.TxInput{
 61        RefTxid: utxo.RefTxid,
 62        RefOffset: utxo.RefOffset,
 63        FromAddr: utxo.ToAddr,
 64        Amount: utxo.Amount,
 65    }
 66    tx.TxInputs = append(tx.TxInputs, txin)
 67}
 68// 处理找零的逻辑
 69total, _ := big.NewInt(0).SetString(prersp.UtxoOutput.TotalSelected, 10)
 70if total.Cmp(amount) > 0 {
 71    delta := total.Sub(total, amount)
 72    charge := &pb.TxOutput{
 73        ToAddr: []byte(addr_alice),
 74        Amount: delta,
 75    }
 76}
 77// 填充预执行的结果
 78tx.ContractRequests = prersp.GetResponse().GetRequests()
 79tx.TxInputsExt = prersp.GetResponse().GetInputs()
 80tx.TxOutputsExt = prersp.GetResponse().GetOutputs()
 81// 给交易签名
 82tx.AuthRequire = append(tx.AuthRequire, addr_alice)
 83txsign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_alice))
 84txsignInfo := &pb.SignatureInfo{
 85    PublicKey: pub_alice,
 86    Sign: txsign,
 87}
 88tx.InitiatorSigns = append(tx.InitiatorSigns, txsignInfo)
 89tx.AuthRequireSigns = append(tx.AuthRequireSigns, txsignInfo)
 90// 生成交易ID
 91tx.Txid, _ = txhash.MakeTransactionID(tx)
 92// 构造最终要Post的TxStatus
 93txs := &pb.TxStatus{
 94    Bcname: "xuper",
 95    Status: pb.TransactionStatus_UNCONFIRM,
 96    Tx: tx,
 97    Txid: tx.Txid,
 98}
 99// 最后一步,执行PostTx
100rsp, err := cli.PostTx(context.Background(), txs)

3.2.3. 修改合约账号ACL

延续上一小节的例子,假设我们要把ACL修改成以下状态

 1{
 2    "pm": {
 3        "rule": 1,
 4        "acceptValue": 1.0
 5    },
 6    "aksWeight": {
 7        "XXXaddress-aliceXXX" : 1.0,
 8        "XXXXaddress-bobXXXX" : 1.0
 9    }
10}

为了进行此操作,我们事先需要有以下信息

Alice的地址

addr_alice

Alice的公钥

pub_alice

Alice的私钥

pri_alice

Bob的地址

addr_bob

Bob的公钥

pub_bob

Bob的私钥

pri_bob

新ACL的内容

new_acl

修改ACL的总体逻辑为,首先进行修改的预执行,然后构造交易发送,这里需要注意的是,修改ACL操作需要满足现有的ACL要求才有权限,即Alice Bob都需要签名确认。简单起见,当中的手续费依然由Alice支付。

 1// 构造修改ACL的请求
 2args := make(map[string][]byte)
 3args["account_name"] = []byte(1234567812345678)
 4args["acl"] = []byte(new_acl)
 5invokereq := &pb.InvokeRequest{
 6    ModuleName: "xkernel",
 7    MethodName: "SetAccountAcl",
 8    Args: args,
 9}
10invokereqs := []*pb.InvokeRequest{invokereq}
11
12// 构造合约预执行的请求,和上一节一样,此处省略
13///////////////////////////////////////////////
14// 花手续费需要出资的账号确认,填充验证的签名,和上一节一样,此处省略
15/////////////////////////////////////////////////////////////////////
16// 按上一节逻辑一样,填充花费、找零,然后填充预执行的结果
17tx.ContractRequests = prersp.GetResponse().GetRequests()
18tx.TxInputsExt = prersp.GetResponse().GetInputs()
19tx.TxOutputsExt = prersp.GetResponse().GetOutputs()
20// 给交易签名需要原ACL里的多个账号了
21tx.AuthRequire = append(tx.AuthRequire, addr_alice)
22tx.AuthRequire = append(tx.AuthRequire, addr_bob)
23alicesign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_alice))
24alicesignInfo := &pb.SignatureInfo{
25    PublicKey: pub_alice,
26    Sign: alicesign,
27}
28bobsign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_bob))
29bobsignInfo := &pb.SignatureInfo{
30    PublicKey: pub_bob,
31    Sign: bobsign,
32}
33tx.InitiatorSigns = append(tx.InitiatorSigns, alicesignInfo)
34tx.AuthRequireSigns = append(tx.AuthRequireSigns, alicesignInfo)
35tx.AuthRequireSigns = append(tx.AuthRequireSigns, bobsignInfo)
36// 然后和上一节一致了,生成交易ID
37tx.Txid, _ = txhash.MakeTransactionID(tx)
38// 构造最终要Post的TxStatus
39txs := &pb.TxStatus{
40    Bcname: "xuper",
41    Status: pb.TransactionStatus_UNCONFIRM,
42    Tx: tx,
43    Txid: tx.Txid,
44}
45// 最后一步,执行PostTx
46rsp, err := cli.PostTx(context.Background(), txs)

3.2.4. 部署一个合约

这里我们演示使用合约账号 XC1234567812345678@xuper 部署一个C++的counter合约,init参数为{“creator”:”xchain”},假设合约账号的ACL是修改过的版本

为了进行此操作,我们事先需要有以下信息

合约文件字节内容

contract_code

Alice的地址

addr_alice

Alice的公钥

pub_alice

Alice的私钥

pri_alice

部署合约的总体逻辑为,首先构造deploy操作预执行,部署需要的手续费由合约账号出,需要的签名由Alice提供(因为一个签名就满足ACL了)

  1// 构造部署合约的请求,关注args的内容,基本上和使用xchain-cli一致
  2args := make(map[string][]byte)
  3args["account_name"] = []byte("XC1234567812345678@xuper")
  4args["contract_name"] = []byte("counter")
  5// github.com/golang/protobuf/proto
  6codedesc := desc := &pb.WasmCodeDesc{
  7    Runtime: "c",
  8}
  9desc, _ := proto.Marshal(codedesc)
 10args["contract_desc"] = desc
 11args["contract_code"] = contract_code
 12initarg := `{"creator":"` + base64.StdEncoding.EncodeToString([]byte("xchain")) + `"}`
 13args["init_args"] = []byte(initarg)
 14invokereq := &pb.InvokeRequest{
 15    ModuleName: "xkernel",
 16    MethodName: "Deploy",
 17    Args: args,
 18}
 19invokereqs := []*pb.InvokeRequest{invokereq}
 20// 这里预执行的authrequire格式为 XC1234567812345678@xuper/dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN,
 21// 表示是“某个合约账号的股东”,与直接写账号地址含义是不同的,ACL需求多个签名的时候即多个“股东”
 22authrequires := []string{"XC1234567812345678@xuper/XXXaddress-aliceXXX"}
 23rpcreq := &pb.InvokeRPCRequest{
 24    Header: global.Glogid(),
 25    Bcname: "xuper",
 26    Requests: invokereqs,
 27    Initiator: addr_alice,
 28    AuthRequire: authrequires,
 29}
 30// SelectUTXO的目标是合约账号中的余额,出资账号签名中的地址变成了合约账号,与“创建账号”小节有区别
 31content := hash.DoubleSha256([]byte("xuper" + "XC1234567812345678@xuper" + "0" + "true"))
 32prikey, _ := cryptoCli.GetEcdsaPrivateKeyFromJSON([]byte(pri_alice))
 33sign, _ := cryptoCli.SignECDSA(prikey, content)
 34signInfo := &pb.SignatureInfo{
 35    PublicKey: pub_alice,
 36    Sign: sign,
 37}
 38// 组合一个PreExecWithSelectUTXORequest用来预执行同时拿出需要支付的合约账号的utxo
 39prereq := &pb.PreExecWithSelectUTXORequest{
 40    Header: global.Glogid(),
 41    Bcname: "xuper",
 42    Address: "XC1234567812345678@xuper",
 43    TotalAmount: 0,
 44    SignInfo: signInfo,
 45    NeedLock: true,
 46    Request: rpcreq,
 47}
 48prersp, _ := cli.PreExecWithSelectUTXO(context.Background(), prereq)
 49// 构造一个Alice发起的交易
 50tx := &pb.Transaction{
 51    Version: 1,
 52    Coinbase: false,
 53    Desc: []byte(""),
 54    Nonce: global.GenNonce(),
 55    Timestamp: time.Now().UnixNano(),
 56    Initiator: addr_alice,
 57}
 58// 填充支付的手续费,手续费需要“转账”给地址“$”
 59amount := big.NewInt(prersp.Response.GasUsed)
 60fee := &pb.TxOutput{
 61    ToAddr: []byte("$"),
 62    Amount: amount.Bytes(),
 63}
 64tx.TxOutputs = append(tx.TxOutputs, fee)
 65// 填充select出来的Alice的utxo
 66for _, utxo := range prersp.UtxoOutput.UtxoList {
 67    txin := &pb.TxInput{
 68        RefTxid: utxo.RefTxid,
 69        RefOffset: utxo.RefOffset,
 70        FromAddr: utxo.ToAddr,
 71        Amount: utxo.Amount,
 72    }
 73    tx.TxInputs = append(tx.TxInputs, txin)
 74}
 75// 处理找零的逻辑
 76total, _ := big.NewInt(0).SetString(prersp.UtxoOutput.TotalSelected, 10)
 77if total.Cmp(amount) > 0 {
 78    delta := total.Sub(total, amount)
 79    charge := &pb.TxOutput{
 80        ToAddr: []byte("XC1234567812345678@xuper"),
 81        Amount: delta,
 82    }
 83}
 84// 填充预执行的结果
 85tx.ContractRequests = prersp.GetResponse().GetRequests()
 86tx.TxInputsExt = prersp.GetResponse().GetInputs()
 87tx.TxOutputsExt = prersp.GetResponse().GetOutputs()
 88// 给交易签名,此处也是以“股东”身份签名
 89tx.AuthRequire = append(tx.AuthRequire, "XC1234567812345678@xuper/XXXaddress-aliceXXX")
 90txsign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_alice))
 91txsignInfo := &pb.SignatureInfo{
 92    PublicKey: pub_alice,
 93    Sign: txsign,
 94}
 95// 虽然Alice和“股东Alice”含义不同,但签名的私钥是一样的
 96tx.InitiatorSigns = append(tx.InitiatorSigns, signInfo)
 97tx.AuthRequireSigns = append(tx.AuthRequireSigns, signInfo)
 98tx.Txid, _ = txhash.MakeTransactionID(tx)
 99// 构造最终要Post的TxStatus
100txs := &pb.TxStatus{
101    Bcname: "xuper",
102    Status: pb.TransactionStatus_UNCONFIRM,
103    Tx: tx,
104    Txid: tx.Txid,
105}
106// 最后一步,执行PostTx
107rsp, err := cli.PostTx(context.Background(), txs)

3.2.5. 执行一个wasm合约

这里我们演示使用Alice账号调用上一节部署的counter合约,执行 increase 方法,参数为 {“key”: “example”}

为了进行此操作,我们事先需要有以下信息

Alice的地址

addr_alice

Alice的公钥

pub_alice

Alice的私钥

pri_alice

执行合约的总体逻辑为,首先构造相应预执行请求并预执行,如果是查询,那么直接读预执行结果即可,如果是要调用上链的操作,使用预执行结果组建交易并发送

 1// 构造执行合约的请求
 2args := make(map[string][]byte)
 3args["key"] = []byte("example")
 4invokereq := &pb.InvokeRequest{
 5    ModuleName: "wasm",
 6    MethodName: "increase",
 7    ContractName: "counter",
 8    Args: args,
 9}
10invokereqs := []*pb.InvokeRequest{invokereq}
11// 其他内容和“创建合约账号”一节完全一致