# gRPC
Plug Chain Hub v1.0.0(依赖Cosmos-SDK v0.45)引入了 Protobuf 作为主要的编码 (opens new window)库,这带来了可插入 SDK 的各种基于 Protobuf 的工具。一种这样的工具是 gRPC (opens new window),这是一种现代的开源高性能 RPC 框架,具有多语言客户端支持。
# gRPC 服务端口、激活方式和配置
grpc.Server
是一个具体的 gRPC 服务,它产生并服务任何gRPC请求。可以在 ~/.plugchain/config/app.toml
中配置:
grpc.enable = true|false
字段定义了 gRPC 服务是否可用,默认为true
。grpc.address = {string}
字段定义了服务绑定的地址(实际上是端口,因为主机必须保持为0.0.0.0
),默认为0.0.0.0:9090
。
###############################################################################
### gRPC Configuration ###
###############################################################################
[grpc]
# Enable defines if the gRPC server should be enabled.
enable = true
# Address defines the gRPC server address to bind to.
address = "0.0.0.0:9090"
gRPC 服务启动后,您可以使用 gRPC 客户端向其发送请求。
# gRPC 端点
Plug Chain Hub 附带的所有可用 gRPC 端点的概述见Protobuf 文档。
# 构造、签名和广播交易
可以使用 Cosmos SDK 的 TxBuilder
接口,通过 Golang 以编程方式处理交易。
# 构造一个交易
在生成交易之前,需要创建一个新的 TxBuilder
实例。 由于 SDK 支持 Amino 和 Protobuf 交易,因此第一步将是确定要使用哪种编码方案。无论您使用的是 Amino 还是 Protobuf,所有后续步骤均保持不变,因为 TxBuilder
抽象了编码机制。在以下代码段中,我们将使用 Protobuf。
# gomod 中添加
require (
github.com/cosmos/cosmos-sdk v0.45.9
github.com/oracleNetworkProtocol/plugchain v1.7.2
)
replace (
github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76
github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
google.golang.org/grpc => google.golang.org/grpc v1.33.2
)
import (
sdk "github.com/cosmos/cosmos-sdk/types"
plugchainapp "github.com/oracleNetworkProtocol/plugchain/app"
ethencoding "github.com/evmos/ethermint/encoding"
)
func sendTx() error {
// 选择您的编解码器:Amino 或 Protobuf
encCfg := ethencoding.MakeConfig(plugchainapp.ModuleBasics)
config := sdk.GetConfig()
plugchainapp.SetBech32Prefixes(config)
// 创建一个新的 TxBuilder。
txBuilder := encCfg.TxConfig.NewTxBuilder()
// --剪断--
}
// TxBuilder 定义了一个应用程序定义的具体事务的接口 // 类型必须实现。 即,它必须能够设置消息,生成 // 签名,并提供规范字节进行签名。 交易必须 // 也知道如何编码自己。
TxBuilder interface {
GetTx() signing.Tx
SetMsgs(msgs ...sdk.Msg) error
SetSignatures(signatures ...signingtypes.SignatureV2) error
SetMemo(memo string)
SetFeeAmount(amount sdk.Coins)
SetGasLimit(limit uint64)
SetTimeoutHeight(height uint64)
}
根据自己操作的不同功能,需要准备不同的数据,以下以转账为例,需要准备:
- 发送方地址
gx14z0773hnqq0qvwpr95stvn5tmtgvp8033e7aky
节点上name是walletName
- 发起者地址的私钥 可以通过
plugchaind keys export walletName --unarmored-hex --unsafe
得到 - 发起者地址的
accountNumber
和Sequence
可以通过查询节点ip:1317/cosmos/auth/v1beta1/accounts/{address}
- 接收者地址
import (
"encoding/hex"
sdk "github.com/cosmos/cosmos-sdk/types"
_ "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/evmos/ethermint/crypto/ethsecp256k1"
)
chainID := "plugchain_520-1"
addr1, _ := sdk.AccAddressFromBech32("gx14z0773hnqq0qvwpr95stvn5tmtgvp8033e7aky")
addr2, _ := sdk.AccAddressFromBech32("gx1d0ug2e7ehy6prw6msrtqwt55mydmxdsx4em9ds")
//发起者私钥
priv := "55e2413b83e590944c6a4bcb443374c60bba847fc079788bd97ea455cb555bf0"
privB, _ := hex.DecodeString(priv)
// 如下查询地址的信息,使用account_number,sequence,需要根据 `@type` 类型 来锁定私钥类型,EthAcount 类型为`eth_secp256k1`,BaseAccount为`secp256k1`
//curl -X GET "http://8.210.180.240:1317/cosmos/auth/v1beta1/accounts/gx13udxpqpmq6herxqk9yqa3agln8a0va9whjuqe7" -H "accept: application/json"
accountSeq := uint64(2)
acountNumber := uint64(73991)
//EthAccount 类型, 使用包 "github.com/evmos/ethermint/crypto/ethsecp256k1"
//BaseAccount 类型 ,使用包 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
priv1 := ethsecp256k1.PrivKey{Key: privB}
import (
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)
func sendTx() error {
// --剪断--
//发送一笔转账:
//地址addr1 到 addr2
//交易需要 addr1 签名
msg1 := banktypes.NewMsgSend(addr1, addr2, types.NewCoins(types.NewInt64Coin("uplugcn", 1000000)))
err := txBuilder.SetMsgs(msg1)
if err != nil {
return err
}
txBuilder.SetGasLimit(200000)
txBuilder.SetFeeAmount(types.NewCoins(types.NewInt64Coin("uplugcn", 20)))
txBuilder.SetMemo("give your my friend to LiLei")
// txBuilder.SetTimeoutHeight(...)
}
至此,以 TxBuilder
为基础的交易已经准备好进行签名。
# 签名一个交易
我们将编码设置为使用 Protobuf,默认情况下将使用 SIGN_MODE_DIRECT
。 根据ADR-020 (opens new window),每个签名者都需要对所有其他签名者的 SignerInfo
s 进行签名。这意味着我们需要依次执行两个步骤:
- 对于每个签名者,在
TxBuilder
中设置签名者的SignerInfo
, - 设置所有
SignerInfo
之后,每个签名者对SignDoc
(要签名的有效数据)进行签名。
在当前的 TxBuilder
API中,两个步骤都使用相同的方法 SetSignatures()
完成。当前的 API 要求我们首先循环执行带不带签名的 SetSignatures()
,仅设置 SignerInfo
s,然后进行第二轮 SetSignatures()
来对正确的有效数据进行签名。
import (
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
cliTx "github.com/cosmos/cosmos-sdk/client/tx"
)
func sendTx() error {
// --剪断--
//第一轮:我们收集所有签名者信息。 我们使用“设置空签名”技巧来做到这一点
sign := signing.SignatureV2{
PubKey: priv1.PubKey(),
Data: &signing.SingleSignatureData{
SignMode: encCfg.TxConfig.SignModeHandler().DefaultMode(),
Signature: nil,
},
Sequence: accountSeq,
}
err = txBuilder.SetSignatures(sign)
if err != nil {
return err
}
//第二轮: 设置所有签名者信息,因此每个签名者都可以签名。
sign = signing.SignatureV2{}
signerD := xauthsigning.SignerData{
ChainID: chainID,
AccountNumber: acountNumber,
Sequence: accountSeq,
}
sign, err = cliTx.SignWithPrivKey(
encCfg.TxConfig.SignModeHandler().DefaultMode(), signerD,
txBuilder, cryptotypes.PrivKey(&priv1), encCfg.TxConfig, accountSeq)
if err != nil {
return err
}
err = txBuilder.SetSignatures(sign)
if err != nil {
return err
}
}
现在已经正确配置了 TxBuilder
。 要打印它,您可以使用初始编码配置 encCfg
中的 TxConfig
接口:
func sendTx() error {
// --剪断--
// 生成的 Protobuf 编码字节。
txBytes, err := encCfg.TxConfig.TxEncoder()(txBuilder.GetTx())
if err != nil {
return err
}
// Generate a JSON string.
// txJSONBytes, err := encCfg.TxConfig.TxJSONEncoder()(txBuilder.GetTx())
// if err != nil {
// return err
// }
// txJSON := string(txJSONBytes)
}
# 广播一个交易
广播交易的首选方法是使用 gRPC,尽管也可以使用 REST(通过 gRPC-gateway
)或 Tendermint RPC。 本教程中,我们仅介绍 gRPC 方法。
import (
"context"
"fmt"
"google.golang.org/grpc"
"github.com/cosmos/cosmos-sdk/types/tx"
)
func sendTx() error {
// --剪断--
grpcConn, err := grpc.Dial(
"127.0.0.1:9090", // 你的 gRPC 服务器地址。
grpc.WithInsecure(), // SDK 不支持任何传输安全机制。
)
if err != nil {
return err
}
defer grpcConn.Close()
// 通过 gRPC 广播 tx。 我们为 Protobuf Tx 服务创建了一个新客户端。
txClient := tx.NewServiceClient(grpcConn)
//然后我们在这个客户端上调用 BroadcastTx 方法。
grpcRes, err := txClient.BroadcastTx(
context.Background(),
&tx.BroadcastTxRequest{
Mode: tx.BroadcastMode_BROADCAST_MODE_SYNC,
TxBytes: txBytes,
},
)
if err != nil {
return err
}
fmt.Println(grpcRes.GetTxResponse())
return nil
}
# 完整代码
package main
import (
"context"
"encoding/hex"
"fmt"
"github.com/cosmos/cosmos-sdk/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"google.golang.org/grpc"
ethencoding "github.com/evmos/ethermint/encoding"
plugchainapp "github.com/oracleNetworkProtocol/plugchain/app"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
_ "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/evmos/ethermint/crypto/ethsecp256k1"
cliTx "github.com/cosmos/cosmos-sdk/client/tx"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/types/tx"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
)
func main() {
err := sendTx()
fmt.Println(err)
}
func sendTx() error {
// 选择您的编解码器:Amino 或 Protobuf
encCfg := ethencoding.MakeConfig(plugchainapp.ModuleBasics)
config := sdk.GetConfig()
plugchainapp.SetBech32Prefixes(config)
// 创建一个新的 TxBuilder。
txBuilder := encCfg.TxConfig.NewTxBuilder()
// --剪断--
chainID := "plugchain_520-1"
addr1, _ := sdk.AccAddressFromBech32("gx14z0773hnqq0qvwpr95stvn5tmtgvp8033e7aky")
addr2, _ := sdk.AccAddressFromBech32("gx1d0ug2e7ehy6prw6msrtqwt55mydmxdsx4em9ds")
//发起者私钥
priv := "55e2413b83e590944c6a4bcb443374c60bba847fc079788bd97ea455cb555bf0"
privB, _ := hex.DecodeString(priv)
accountSeq := uint64(1)
acountNumber := uint64(73991)
//EthAccount 类型, 使用包 "github.com/evmos/ethermint/crypto/ethsecp256k1"
//BaseAccount 类型 ,使用包 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
priv1 := ethsecp256k1.PrivKey{Key: privB}
msg1 := banktypes.NewMsgSend(addr1, addr2, types.NewCoins(types.NewInt64Coin("uplugcn", 1000000)))
err := txBuilder.SetMsgs(msg1)
if err != nil {
return err
}
txBuilder.SetGasLimit(200000)
txBuilder.SetFeeAmount(types.NewCoins(types.NewInt64Coin("uplugcn", 20)))
txBuilder.SetMemo("give your my friend to LiLei")
//第一轮:我们收集所有签名者信息。 我们使用“设置空签名”技巧来做到这一点
sign := signing.SignatureV2{
PubKey: priv1.PubKey(),
Data: &signing.SingleSignatureData{
SignMode: encCfg.TxConfig.SignModeHandler().DefaultMode(),
Signature: nil,
},
Sequence: accountSeq,
}
err = txBuilder.SetSignatures(sign)
if err != nil {
return err
}
//第二轮: 设置所有签名者信息,因此每个签名者都可以签名。
sign = signing.SignatureV2{}
signerD := xauthsigning.SignerData{
ChainID: chainID,
AccountNumber: acountNumber,
Sequence: accountSeq,
}
sign, err = cliTx.SignWithPrivKey(
encCfg.TxConfig.SignModeHandler().DefaultMode(), signerD,
txBuilder, cryptotypes.PrivKey(&priv1), encCfg.TxConfig, accountSeq)
if err != nil {
return err
}
err = txBuilder.SetSignatures(sign)
if err != nil {
return err
}
// 生成的 Protobuf 编码字节。
txBytes, err := encCfg.TxConfig.TxEncoder()(txBuilder.GetTx())
if err != nil {
return err
}
// Generate a JSON string.
txJSONBytes, err := encCfg.TxConfig.TxJSONEncoder()(txBuilder.GetTx())
if err != nil {
return err
}
txJSON := string(txJSONBytes)
fmt.Println(txJSON)
// 创建一个grpc服务
grpcConn, err := grpc.Dial(
"127.0.0.1:9090", // 你的 gRPC 服务器地址。
grpc.WithInsecure(), // SDK 不支持任何传输安全机制。
)
if err != nil {
return err
}
defer grpcConn.Close()
// 通过 gRPC 广播 tx。 我们为 Protobuf Tx 服务创建了一个新客户端。
txClient := tx.NewServiceClient(grpcConn)
//然后我们在这个客户端上调用 BroadcastTx 方法。
grpcRes, err := txClient.BroadcastTx(
context.Background(),
&tx.BroadcastTxRequest{
Mode: tx.BroadcastMode_BROADCAST_MODE_SYNC,
TxBytes: txBytes,
},
)
if err != nil {
return err
}
fmt.Println(grpcRes.GetTxResponse())
return nil
}
← Restful API SDK →