3. XuperChain RPC 接口使用说明

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

3.1. RPC接口介绍

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

3.1.1. GetBalance

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

参数结构AddressStatus
返回结构AddressStatus

这里 AddressStatus 的定义如下

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

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

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

TokenDetail 定义如下:

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

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

其中的 Header 如下

  1. message 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示例

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

3.1.2. GetBalanceDetail

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

参数结构AddressBalanceStatus
返回结构AddressBalanceStatus

AddressBalanceStatus 定义如下

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

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

以下为Golang示例

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

3.1.3. GetFrozenBalance

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

3.1.4. GetBlock

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

参数结构BlockID
返回结构Block

BlockID 定义如下

  1. message 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示例

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

3.1.5. GetBlockByHeight

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

参数结构BlockHeight
返回结构Block

BlockHeight定义如下

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

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

3.1.6. GetBlockChainStatus

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

参数结构BCStatus
返回结构BCStatus

BCStatus定义如下

  1. message 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示例

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

3.1.7. GetBlockChains

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

参数结构CommonIn
返回结构BlockChains

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

以下为Golang示例

  1. opts := make([]grpc.DialOption, 0)
  2. opts = append(opts, grpc.WithInsecure())
  3. opts = append(opts, grpc.WithMaxMsgSize(64<<20-1))
  4. conn, _ := grpc.Dial("127.0.0.1:37101", opts...)
  5. cli := pb.NewXchainClient(conn)
  6. in := &pb.CommonIn{
  7. Header: global.Glogid(),
  8. }
  9. out, _ := 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定义如下

  1. message 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示例

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

3.1.11. QueryTx

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

参数结构TxStatus
返回结构TxStatus

TxStatus定义如下

  1. message 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示例

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

3.1.12. SelectUTXO

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

参数结构UtxoInput
返回结构UtxoOutput

UtxoInput定义如下

  1. message 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中的返回即可在组装交易时使用,具体组装交易的过程可参考文档下方

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

3.1.13. SelectUTXOBySize

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

参数结构UtxoInput
返回结构UtxoOutput

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

3.1.14. PreExec

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

参数结构InvokeRPCRequest
返回结构InvokeRPCResponse

InvokeRPCRequest定义如下

  1. message 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定义如下

  1. message 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结构中

  1. message 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字段,定义如下

  1. message 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
  2. utxoreq := &pb.UtxoInput{
  3. Header: global.Glogid(),
  4. Bcname: "xuper",
  5. Address: addr_alice,
  6. TotalNeed: "10",
  7. NeedLock: true,
  8. }
  9. utxorsp, _ := cli.SelectUTXO(context.Background(), utxoreq)
  10. // 声明一个交易,发起者为Alice地址,因为是转账,所以Desc字段什么都不填
  11. // 如果是提案等操作,将客户端的 --desc 参数写进去即可
  12. tx := &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
  21. for _, 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字段的类型
  31. amount, _ := big.NewInt(0).SetString("10", 10)
  32. txout := &pb.TxOutput{
  33. ToAddr: []byte(addr_bob),
  34. Amount: amount.Bytes(),
  35. }
  36. tx.TxOutputs = append(tx.TxOutputs, txout)
  37. // 如果Select出来的Alice的utxo多于10,需要构造一个给Alice的找零
  38. total, _ := big.NewInt(0).SetString(utxorsp.TotalSelected, 10)
  39. if 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签名确认即可
  48. tx.AuthRequire = append(tx.AuthRequire, addr_alice)
  49. // 签名需要的库在 github.com/xuperchain/xuperchain/core/crypto/client
  50. // 和 github.com/xuperchain/xuperchain/core/crypto/hash
  51. cryptoCli, _ := client.CreateCryptoClient("default")
  52. sign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_alice))
  53. signInfo := &pb.SignatureInfo{
  54. PublicKey: pub_alice,
  55. Sign: sign,
  56. }
  57. // 将签名填充进交易
  58. tx.InitiatorSigns = append(tx.InitiatorSigns, signInfo)
  59. tx.AuthRequireSigns = append(tx.AuthRequireSigns, signInfo)
  60. // 生成交易ID
  61. tx.Txid, _ = txhash.MakeTransactionID(tx)
  62. // 构造最终要Post的TxStatus
  63. txs := &pb.TxStatus{
  64. Bcname: "xuper",
  65. Status: pb.TransactionStatus_UNCONFIRM,
  66. Tx: tx,
  67. Txid: tx.Txid,
  68. }
  69. // 最后一步,执行PostTx
  70. rsp, 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. // 构造创建合约账号的请求
  2. args := make(map[string][]byte)
  3. args["account_name"] = []byte(1234567812345678)
  4. args["acl"] = []byte(acct_acl)
  5. invokereq := &pb.InvokeRequest{
  6. ModuleName: "xkernel",
  7. MethodName: "NewAccount",
  8. Args: args,
  9. }
  10. invokereqs := []*pb.InvokeRequest{invokereq}
  11. // 构造合约预执行的请求
  12. authrequire := []string{addr_alice}
  13. rpcreq := &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
  23. content := hash.DoubleSha256([]byte("xuper" + addr_alice + "0" + "true"))
  24. cryptoCli, _ := client.CreateCryptoClient("default")
  25. prikey, _ := cryptoCli.GetEcdsaPrivateKeyFromJSON([]byte(pri_alice))
  26. sign, _ := cryptoCli.SignECDSA(prikey, content)
  27. signInfo := &pb.SignatureInfo{
  28. PublicKey: pub_alice,
  29. Sign: sign,
  30. }
  31. // 组合一个PreExecWithSelectUTXORequest用来预执行同时拿出需要支付的Alice的utxo
  32. prereq := &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. }
  41. prersp := cli.PreExecWithSelectUTXO(context.Background(), prereq)
  42. // 构造一个Alice发起的交易
  43. tx := &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. // 填充支付的手续费,手续费需要“转账”给地址“$”
  52. amount := big.NewInt(prersp.Response.GasUsed)
  53. fee := &pb.TxOutput{
  54. ToAddr: []byte("$"),
  55. Amount: amount.Bytes(),
  56. }
  57. tx.TxOutputs = append(tx.TxOutputs, fee)
  58. // 填充select出来的Alice的utxo
  59. for _, 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. // 处理找零的逻辑
  69. total, _ := big.NewInt(0).SetString(prersp.UtxoOutput.TotalSelected, 10)
  70. if 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. // 填充预执行的结果
  78. tx.ContractRequests = prersp.GetResponse().GetRequests()
  79. tx.TxInputsExt = prersp.GetResponse().GetInputs()
  80. tx.TxOutputsExt = prersp.GetResponse().GetOutputs()
  81. // 给交易签名
  82. tx.AuthRequire = append(tx.AuthRequire, addr_alice)
  83. txsign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_alice))
  84. txsignInfo := &pb.SignatureInfo{
  85. PublicKey: pub_alice,
  86. Sign: txsign,
  87. }
  88. tx.InitiatorSigns = append(tx.InitiatorSigns, txsignInfo)
  89. tx.AuthRequireSigns = append(tx.AuthRequireSigns, txsignInfo)
  90. // 生成交易ID
  91. tx.Txid, _ = txhash.MakeTransactionID(tx)
  92. // 构造最终要Post的TxStatus
  93. txs := &pb.TxStatus{
  94. Bcname: "xuper",
  95. Status: pb.TransactionStatus_UNCONFIRM,
  96. Tx: tx,
  97. Txid: tx.Txid,
  98. }
  99. // 最后一步,执行PostTx
  100. rsp, 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的请求
  2. args := make(map[string][]byte)
  3. args["account_name"] = []byte(1234567812345678)
  4. args["acl"] = []byte(new_acl)
  5. invokereq := &pb.InvokeRequest{
  6. ModuleName: "xkernel",
  7. MethodName: "SetAccountAcl",
  8. Args: args,
  9. }
  10. invokereqs := []*pb.InvokeRequest{invokereq}
  11. // 构造合约预执行的请求,和上一节一样,此处省略
  12. ///////////////////////////////////////////////
  13. // 花手续费需要出资的账号确认,填充验证的签名,和上一节一样,此处省略
  14. /////////////////////////////////////////////////////////////////////
  15. // 按上一节逻辑一样,填充花费、找零,然后填充预执行的结果
  16. tx.ContractRequests = prersp.GetResponse().GetRequests()
  17. tx.TxInputsExt = prersp.GetResponse().GetInputs()
  18. tx.TxOutputsExt = prersp.GetResponse().GetOutputs()
  19. // 给交易签名需要原ACL里的多个账号了
  20. tx.AuthRequire = append(tx.AuthRequire, addr_alice)
  21. tx.AuthRequire = append(tx.AuthRequire, addr_bob)
  22. alicesign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_alice))
  23. alicesignInfo := &pb.SignatureInfo{
  24. PublicKey: pub_alice,
  25. Sign: alicesign,
  26. }
  27. bobsign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_bob))
  28. bobsignInfo := &pb.SignatureInfo{
  29. PublicKey: pub_bob,
  30. Sign: bobsign,
  31. }
  32. tx.InitiatorSigns = append(tx.InitiatorSigns, alicesignInfo)
  33. tx.AuthRequireSigns = append(tx.AuthRequireSigns, alicesignInfo)
  34. tx.AuthRequireSigns = append(tx.AuthRequireSigns, bobsignInfo)
  35. // 然后和上一节一致了,生成交易ID
  36. tx.Txid, _ = txhash.MakeTransactionID(tx)
  37. // 构造最终要Post的TxStatus
  38. txs := &pb.TxStatus{
  39. Bcname: "xuper",
  40. Status: pb.TransactionStatus_UNCONFIRM,
  41. Tx: tx,
  42. Txid: tx.Txid,
  43. }
  44. // 最后一步,执行PostTx
  45. rsp, 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一致
  2. args := make(map[string][]byte)
  3. args["account_name"] = []byte("XC1234567812345678@xuper")
  4. args["contract_name"] = []byte("counter")
  5. // github.com/golang/protobuf/proto
  6. codedesc := desc := &pb.WasmCodeDesc{
  7. Runtime: "c",
  8. }
  9. desc, _ := proto.Marshal(codedesc)
  10. args["contract_desc"] = desc
  11. args["contract_code"] = contract_code
  12. initarg := `{"creator":"` + base64.StdEncoding.EncodeToString([]byte("xchain")) + `"}`
  13. args["init_args"] = []byte(initarg)
  14. invokereq := &pb.InvokeRequest{
  15. ModuleName: "xkernel",
  16. MethodName: "Deploy",
  17. Args: args,
  18. }
  19. invokereqs := []*pb.InvokeRequest{invokereq}
  20. // 这里预执行的authrequire格式为 XC1234567812345678@xuper/dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN,
  21. // 表示是“某个合约账号的股东”,与直接写账号地址含义是不同的,ACL需求多个签名的时候即多个“股东”
  22. authrequires := []string{"XC1234567812345678@xuper/XXXaddress-aliceXXX"}
  23. rpcreq := &pb.InvokeRPCRequest{
  24. Header: global.Glogid(),
  25. Bcname: "xuper",
  26. Requests: invokereqs,
  27. Initiator: addr_alice,
  28. AuthRequire: authrequires,
  29. }
  30. // SelectUTXO的目标是合约账号中的余额,出资账号签名中的地址变成了合约账号,与“创建账号”小节有区别
  31. content := hash.DoubleSha256([]byte("xuper" + "XC1234567812345678@xuper" + "0" + "true"))
  32. prikey, _ := cryptoCli.GetEcdsaPrivateKeyFromJSON([]byte(pri_alice))
  33. sign, _ := cryptoCli.SignECDSA(prikey, content)
  34. signInfo := &pb.SignatureInfo{
  35. PublicKey: pub_alice,
  36. Sign: sign,
  37. }
  38. // 组合一个PreExecWithSelectUTXORequest用来预执行同时拿出需要支付的合约账号的utxo
  39. prereq := &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. }
  48. prersp, _ := cli.PreExecWithSelectUTXO(context.Background(), prereq)
  49. // 构造一个Alice发起的交易
  50. tx := &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. // 填充支付的手续费,手续费需要“转账”给地址“$”
  59. amount := big.NewInt(prersp.Response.GasUsed)
  60. fee := &pb.TxOutput{
  61. ToAddr: []byte("$"),
  62. Amount: amount.Bytes(),
  63. }
  64. tx.TxOutputs = append(tx.TxOutputs, fee)
  65. // 填充select出来的Alice的utxo
  66. for _, 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. // 处理找零的逻辑
  76. total, _ := big.NewInt(0).SetString(prersp.UtxoOutput.TotalSelected, 10)
  77. if 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. // 填充预执行的结果
  85. tx.ContractRequests = prersp.GetResponse().GetRequests()
  86. tx.TxInputsExt = prersp.GetResponse().GetInputs()
  87. tx.TxOutputsExt = prersp.GetResponse().GetOutputs()
  88. // 给交易签名,此处也是以“股东”身份签名
  89. tx.AuthRequire = append(tx.AuthRequire, "XC1234567812345678@xuper/XXXaddress-aliceXXX")
  90. txsign, _ := txhash.ProcessSignTx(cryptoCli, tx, []byte(pri_alice))
  91. txsignInfo := &pb.SignatureInfo{
  92. PublicKey: pub_alice,
  93. Sign: txsign,
  94. }
  95. // 虽然Alice和“股东Alice”含义不同,但签名的私钥是一样的
  96. tx.InitiatorSigns = append(tx.InitiatorSigns, signInfo)
  97. tx.AuthRequireSigns = append(tx.AuthRequireSigns, signInfo)
  98. tx.Txid, _ = txhash.MakeTransactionID(tx)
  99. // 构造最终要Post的TxStatus
  100. txs := &pb.TxStatus{
  101. Bcname: "xuper",
  102. Status: pb.TransactionStatus_UNCONFIRM,
  103. Tx: tx,
  104. Txid: tx.Txid,
  105. }
  106. // 最后一步,执行PostTx
  107. rsp, 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. // 构造执行合约的请求
  2. args := make(map[string][]byte)
  3. args["key"] = []byte("example")
  4. invokereq := &pb.InvokeRequest{
  5. ModuleName: "wasm",
  6. MethodName: "increase",
  7. ContractName: "counter",
  8. Args: args,
  9. }
  10. invokereqs := []*pb.InvokeRequest{invokereq}
  11. // 其他内容和“创建合约账号”一节完全一致