Bootstrap

Cosmos学习记录

Cosmos & gRPC的使用以及遇到的问题

最近一周的主要想法是使用cosmossdk构建一个类似客户端的钱包,实现创建账户、账户余额查询以及单笔转账功能。

环境配置如下:

虚拟机:Ubuntu 24.04.1 LTS
Simapp版本:v0.52.0-rc.1版本的simdv2
Cosmossdk版本:v0.52.0-rc.1
go版本:go version go1.23.4 windows/amd64

具体过程

1. 使用Keyring创建账户和助记词
  1. 在初始化秘钥环时,首先需要对秘钥环的基本参数进行配置,如编码器、后端类型、秘钥环存储路径,这里使用的后端为test,预计正式使用时使用file(使用password对秘钥文件进行加密),存储路径为项目目录下的***./keyring-test***。

  2. 创建秘钥环过程如下:

    • 初始化编码器
    // InitCodec 初始化编码器
    func InitCodec() *codec.ProtoCodec {
    	interfaceRegistry := types.NewInterfaceRegistry()
    	cryptocodec.RegisterInterfaces(interfaceRegistry) // 注册加密相关类型
    	Cdc = codec.NewProtoCodec(interfaceRegistry)
    	return Cdc
    }
    
    • 创建秘钥环
    // InitKeyring 初始化秘钥环
    func InitKeyring() {
    	// 配置keyring
    	cdc := InitCodec()
    
    	keyringBackend := viper.GetString("keyring.backend")
    	keyringDir := filepath.Join(".", viper.GetString("keyring.dir"))
    	fmt.Println(keyringDir)
    	Kr, _ = keyring.New("cosmos", keyringBackend, keyringDir, os.Stdin, cdc)
    }
    
  3. 使用秘钥环创建账户和助记词
    使用keyring中的 NewMnemonic() 方法,创建账户和助记词,该方法返回三个参数,分别为record、mnemonic 和 err

    record, mnemonic, err := utils.Kr.NewMnemonic(accountName, keyring.English, sdk.FullFundraiserPath, "", hd.Secp256k1)
    if err != nil {
    	log.Fatalf("Failed to create account: %v\n", err)
    }
    

    之后可以从record中获取账户的地址和公钥信息,用户也可以使用助记词来恢复账户(助记词是用户恢复账户的唯一手段)。


2.使用gRPC进行账户余额查询
  1. 建立gRPC连接
    建立连接时需要保证服务器端的gRPC服务开启。(在每次申请服务时建立连接还是建立一个全局连接还有待考虑,目前为了简化代码,建立了一个全局连接,在主程序运行结束后,关闭连接)

    func InitGRPCConn() {
    	// 建立连接
    	conn, err := grpc.NewClient("xxx.xxx.xxx.xxx:9090", grpc.WithInsecure())
    	if err != nil {
    		log.Fatalf("Failed to connect to gRPC server: %v", err)
    	}
    	Conn = conn
    }
    
  2. 创建查询客户端 (QueryClient)
    使用 “cosmossdk.io/x/x/bank/types” 包中的NewQueryClient 新建查询客户端

    // 创建Bank 模块客户端
    queryClient := banktypes.NewQueryClient(utils.Conn)
    
  3. 使用 (QueryBalances) 进行账户余额查询
    之后构造查询请求,包含要查询账户的地址和币种,并使用 Balance 方法查询余额

    // 构造查询请求
    req := &banktypes.QueryBalanceRequest{
    	Address: address,
    	Denom:   "stake",
    }
    // 调用余额方法进行余额查询
    resp, err := queryClient.Balance(context.Background(), req)
    if err != nil {
    	log.Fatal(err)
    }
    
3. 使用gRPC实现转账(未完成)
思路1:
  • 沿用上述查询的思路,使用 “cosmossdk.io/x/x/bank/types” 中的MsgClient 来实现消息的发送
    具体过程如下:
    func TestBankTX(t *testing.T) {
    	// 创建gRPC连接
    	conn, err := grpc.NewClient("xxx.xxx.xxx.xxx:9090", grpc.WithInsecure())
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer func(conn *grpc.ClientConn) {
    		err := conn.Close()
    		if err != nil {
    			log.Fatal(err)
    		}
    	}(conn)
    
    	// 创建Bank 模块客户端
    	msgClient := banktypes.NewMsgClient(conn)
    
    	msg := banktypes.NewMsgSend("cosmos1mea4aqq0ng3frfyk4gmxvp3hf8nagd6zxxxxxx", "cosmos19cn44dnfvgqa09jytafrxlhe3mgtttqhxxxxxx", sdk.NewCoins(sdk.NewInt64Coin("stake", 100)))
    
    	resp, err := msgClient.Send(context.Background(), msg)
    	if err != nil {
    		log.Fatal(err)
    	}
    	fmt.Println(resp)
    }
    
  • 在执行上述代码时,遇到以下问题,显示该服务无法处理,目前尚未找到解决办法…
    在这里插入图片描述
思路2

思路1失败后,转而另一个角度开始入手,在本地构造一个交易,并对交易进行签名,最后将交易和签名序列化后广播至区块链网络,在区块链网络进行验证之后,将其上链。具体过程如下:

  1. 首先使用秘钥环获取发送账户的地址,公钥信息(用于构造SignerData),私钥信息(用来对交易进行签名)。
    这里由于后端使用的是test,所以password设置为空字符,使用file时,应该填入账户创建时所使用的口令。

    privKeyArmor, _ := utils.Kr.ExportPrivKeyArmor("alice-test", "")
    privKey, _, _ := crypto.UnarmorDecryptPrivKey(privKeyArmor, "")
    
  2. 初始化交易的配置,包括初始化地址和验证人地址编码器,启用的签名模式和初始化交易配置

    // 地址和验证人地址编码器
    addrCodec := address.Bech32Codec{Bech32Prefix: "cosmos"}
    valAddrCodec := address.Bech32Codec{Bech32Prefix: "cosmos"}
    
    // 启用的签名模式
    signModes := []signingtypes.SignMode{
       signingtypes.SignMode_SIGN_MODE_DIRECT,
    }
    // 初始化交易配置
    txConfig := authtx.NewTxConfig(
       utils.Cdc,    // Protobuf 编解码器
       addrCodec,    // 地址编码器
       valAddrCodec, // 验证人地址编码器
       signModes,    // 启用签名模式
    )
    
  3. 构造交易:构造消息,构造签名者信息,构造未签名时的签名(用于临时填充到txBuilder中)

    // 构造消息并将消息添加到txBuilder中
    msg := banktypes.NewMsgSend("cosmos1mea4aqq0ng3frfyk4gmxvp3hf8nagd6ze2mnle", "cosmos19cn44dnfvgqa09jytafrxlhe3mgtttqhhywh6z", sdk.NewCoins(sdk.NewInt64Coin("stake", 100)))
    err := txBuilder.SetMsgs(msg)
    if err != nil {
       panic(err)
    }
    // 设置Gas
    txBuilder.SetGasLimit(20000)
    
    // 构造临时签名
    var sigV2 signingtypes.SignatureV2
    sigV2 = signingtypes.SignatureV2{
       PubKey: privKey.PubKey(),
       Data: &signingtypes.SingleSignatureData{
       	SignMode:  signingtypes.SignMode_SIGN_MODE_DIRECT,
       	Signature: nil,
       },
       Sequence: 1,
    }
    err = txBuilder.SetSignatures(sigV2)
    if err != nil {
       panic(err)
    }
    // 构造签名者信息
    signerData := xauthsigning.SignerData{
       ChainID:       "learning-chain-1",
       AccountNumber: 1,
       Sequence:      1,
    }
    
  4. 签名:使用 tx2 “github.com/cosmos/cosmos-sdk/client/tx” 中的 SignWithPrivKey 方法对交易进行签名

  5. 将签名信息补充到 txBuilder

    err = txBuilder.SetSignatures(sigV2)
    if err != nil {
       panic(err)
    }
    
  6. 将消息序列化

    // 产生Protobuf-encoded bytes.
    txBytes, err := txConfig.TxEncoder()(txBuilder.GetTx())
    if err != nil {
       panic(err)
    }
    
  7. 广播交易

    // 广播交易
    txClient := tx.NewServiceClient(utils.Conn)
    res, err := txClient.BroadcastTx(context.Background(), &tx.BroadcastTxRequest{
       Mode:    tx.BroadcastMode_BROADCAST_MODE_SYNC,
       TxBytes: txBytes,
    })
    if err != nil {
       log.Fatal("广播失败:", err)
    }
    fmt.Println(res.TxResponse.Code)
    

    报错结果如下:
    在这里插入图片描述
    在这里插入图片描述

    此处遇到的问题,在广播交易是签名验证失败,由于不了解simdv2的具体验证逻辑,不知道具体使用哪些数据进行签名的验证,因此猜测有可能是 txBuilder 在签名之前使用的临时签名信息 构造存在问题,因为在查询签名源代码时发现,签名的内容是txBuilder.GetTx() 的序列化内容,所以验证时有可能是将加入签名的txBuilder.GetTx() 的序列化内容进行验证,因此验证失败;
    txBuilder 不同时期内容如下:
    在这里插入图片描述
    分别是未添加临时签名、添加临时签名和添加正式签名后的内容。
    在使用 “cosmos-sdk/x/auth/signing” 包中的验证函数时,确认签名中所使用的公私钥能够正确进行签名并验证,实验代码如下

    func TestVerify(t *testing.T) {
    	privKeyArmor, _ := utils.Kr.ExportPrivKeyArmor("alice-test", "")
    	privKey, _, _ := crypto.UnarmorDecryptPrivKey(privKeyArmor, "")
    	testbytes := []byte("testbytes")
    	signature, _ := privKey.Sign(testbytes)
    	b := privKey.PubKey().VerifySignature(testbytes, signature)
    	fmt.Println(b)
    }
    

欢迎各位大佬指教~~~!!!

;