2021/10/16
[Golang]Cosmos TxのAminoエンコードされたバイト配列をデコードする
概要
最近少しだけCosmos Hub関連のプロジェクトに関わっており、
Cosmos nodeを立てて、RPCリクエストを試したりと・・色々と検証作業を行なっていました。
Cosmos Hubはgaiadというアプリケーションでブロックチェーンサーバーを動かして、そのネットワークで構成されています。
gaiad
はブロックチェーンデータの検証・保存の他に、grpc
のエンドポイントを(オプションで)公開することができます。
Cosmos Hubを使ったWebアプリケーションの構築には、そのgrpc apiを使ってノードとコミニケーションを取りながら開発していきます。
例えば、送金したいときは、送金用のgrpcエンドポイントに対して送信したいトランザクションのバイト文字列を送信することで実現できます。
そんな中・・・
grpcでは任意の戻り値を表すAnyと呼ばれるタイプがあり、それが曲者です。
Anyは
- どんな種類のデータかを表す
url
文字列 - 実際のデータ(バイト配列)
を持っています。
戻り値を受け取ったクライアントは、このurl
をもとに、バイト配列をうまくデコードして構造体に戻してあげる必要が出てきます。
gaiad
のgrpc
エンドポイントの一つに、ブロックチェーンのブロック情報を取得するエンドポイントがあるのですが、その戻り値のブロックに含まれるトランザクションのデータがこの形式(さらにいうとさらにbase64エンコードされた文字列の形式)で記述されています。
なので・・ブロックの情報からどんなトランザクションが含まれているかを調べるのに、このバイト配列をデコードしてあげる必要があり、その簡易ツールを作成しました。
今回は、そのgrpcのサーバーからの戻り値(レスポンス)に含まれるトランザクションのバイト配列を、構造体に戻してjsonで表示する部分をGo
で実装したものになります。
補足
- このエンコード/デコード方式はAminoといい、proto3で使われているものの一部みたいでした。
- 今回作ったツールはあくまでも検証用です。
- 商用で使う予定のアプリケーションは、Rsut製のprost::Message::decodeを使ってprost_buildされた構造体にデコードしています。
- gaiadの公開している
grpc
に対して、grpcurl
がうまく使えませんでした。どうやらgogo/protobuf
が悪さをしているようでした。gogo/protobuf
がgrpcurl
が内部で利用している、Server Reflectionに対応していないからみたいでした。- Rust製のtonicを使って呼び出したら普通に呼び出せました。tonic以外でも、refrectionを使わないプログラムであれば問題なく動作すると思います。
作ったGoコード
dependencies
go.mod
module github.com/your-name/your-app-name
go 1.16
require (
github.com/cosmos/cosmos-sdk v0.44.1
)
// @see
// * https://github.com/cosmos/gaia/blob/main/go.mod#L121
replace google.golang.org/grpc => google.golang.org/grpc v1.33.2
replace github.com/tendermint/tendermint => github.com/tendermint/tendermint v0.34.13
replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
codes
以下のような感じになりました。
引数のbase64EncodedString
はbase64エンコードされたトランザクションデータ文字列です。
import (
"encoding/base64"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)
func decodeTx(base64EncodedString string) ([]byte, error) {
txBytes, err := base64.StdEncoding.DecodeString(base64EncodedString)
if err != nil {
return nil, err
}
registry := codectypes.NewInterfaceRegistry()
txtypes.RegisterInterfaces(registry)
cryptocodec.RegisterInterfaces(registry)
authtypes.RegisterInterfaces(registry)
banktypes.RegisterInterfaces(registry)
cdc := codec.NewProtoCodec(registry)
var raw txtypes.TxRaw
err = cdc.Unmarshal(txBytes, &raw)
if err != nil {
return nil, err
}
var body txtypes.TxBody
err = cdc.Unmarshal(raw.BodyBytes, &body)
if err != nil {
return nil, err
}
var authInfo txtypes.AuthInfo
err = cdc.Unmarshal(raw.AuthInfoBytes, &authInfo)
if err != nil {
return nil, err
}
theTx := &txtypes.Tx{
Body: &body,
AuthInfo: &authInfo,
Signatures: raw.Signatures,
}
// encode to json
return cdc.MarshalJSON(theTx)
}
ちなみに、上記の戻り値を文字列に変換して表示させると以下のようになりました。
base64EncodedString := "Co4BCosBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmsKLWNvc21vczE2Nmt2dWZ0eHJqaDU2OXJweDk2a2hrazJoZzNra2Fzc2g5bHoyaxItY29zbW9zMWYyNnh2anUwNGNnYXdrcDB6Y2x3MHBqeWZ3M2tjaHY2c2doeWp4GgsKBXN0YWtlEgIxMBJYClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDqjU5/L2oENq1OQeu9mYfs1m55V8LUu5499pFLvGsPRcSBAoCCAEYARIEEMCaDBpA3gzsC3v+EKJAoOMxv4cANNO9/tRBzg7eFXSR5/cHERAJ+i+Yj89A0muH4L9kW3sxRPcvsWhhyUIAirzhq1somg=="
if result, err := decodeTx(base64EncodedString); err != nil {
log.Fatal(err.Error())
} else {
fmt.Println(string(result))
}
{
"body": {
"messages": [
{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": "cosmos166kvuftxrjh569rpx96khkk2hg3kkassh9lz2k",
"to_address": "cosmos1f26xvju04cgawkp0zclw0pjyfw3kchv6sghyjx",
"amount": [
{
"denom": "stake",
"amount": "10"
}
]
}
],
"memo": "",
"timeout_height": "0",
"extension_options": [],
"non_critical_extension_options": []
},
"auth_info": {
"signer_infos": [
{
"public_key": {
"@type": "/cosmos.crypto.secp256k1.PubKey",
"key": "A6o1Ofy9qBDatTkHrvZmH7NZueVfC1LuePfaRS7xrD0X"
},
"mode_info": {
"single": {
"mode": "SIGN_MODE_DIRECT"
}
},
"sequence": "1"
}
],
"fee": {
"amount": [],
"gas_limit": "200000",
"payer": "",
"granter": ""
}
},
"signatures": [
"3gzsC3v+EKJAoOMxv4cANNO9/tRBzg7eFXSR5/cHERAJ+i+Yj89A0muH4L9kW3sxRPcvsWhhyUIAirzhq1somg=="
]
}
以上になります。
関連する記事
tmkmsをdockerでビルドしてローカルのdocker-compose環境で利用してみる
tendermintのkey management systemであるtmkmsをsoftsignモードでテスト環境に導入してみる
[Golang]Cosmos TxのAminoエンコードされたバイト配列をデコードする
Cosmos HubのBlockを取得するRPCの結果に含まれるトランザクションのバイト配列をGolangでデコードしてみました
COSMOS SDKのTutorialに沿ってblockchainアプリを作ってみた
COSMOS SDKを使って、nameserviceのチュートリアルを触り基本的な概念を理解しました