Cosmos SDK Go 实战:TxBuilder、SIGN_MODE_DIRECT 与 Sequence¶
30 秒版(开场)¶
Cosmos SDK 交易不是“把 Msg JSON 用 secp256k1 签一下”。
SIGN_MODE_DIRECT的 SignDoc 绑定序列化后的 body bytes、auth info bytes、chain ID 和 account number;auth info 又包含 signer info/sequence、fee 和 gas。构建时先设置空签名以生成正确 signer info, 获取 sign bytes 后签名,再写回 SignatureV2 并编码 TxRaw。广播sync的 CheckTx 成功 不代表交易已提交到区块或应用执行成功;“DeliverTx”是旧 ABCI 常见术语,ABCI++ 版本通常 在FinalizeBlock中产生每笔交易的执行结果,客户端应统一检查 committed tx result。
3 分钟版(精讲深度)¶
- 为目标 app 注册 interface registry、消息类型、address prefix 和 TxConfig。
- 用官方
TxBuilder设置MsgSend、memo、gas、fee。 - 先写入带公钥、sign mode 和 sequence 的空
SignatureV2,使 AuthInfo 完整。 - 用
SignerData{chainID, accountNumber, sequence}生成 DIRECT sign bytes 并签名。 - 编码 tx bytes,保存签名输入与 raw tx,再广播和查询最终
code/log/height。
可运行示例固定 Cosmos SDK v0.53.7:
cd examples/non-evm-sdk/cosmos
GOMAXPROCS=2 go test -p=1 ./...
10 分钟版(原理 + 图示)¶
flowchart LR
Msg["MsgSend"] --> Body["TxBody: msgs + memo"]
Fee["fee + gas"] --> Auth["AuthInfo"]
Seq["pubkey + sequence + sign mode"] --> Auth
Body --> SignDoc["SignDoc"]
Auth --> SignDoc
Chain["chain-id + account-number"] --> SignDoc
SignDoc --> Sign["secp256k1 signature"]
Sign --> TxRaw["TxRaw bytes"]
哪些字段会改变签名¶
| 字段 | 所在层 | 改变后 |
|---|---|---|
| messages / memo | TxBody | 必须重签 |
| pubkey / sequence / sign mode | AuthInfo | 必须重签 |
| fee / gas | AuthInfo | 必须重签 |
| chain ID / account number | SignDoc | 必须重签 |
因此签名器应接收并审计规范化 sign bytes 及可解释摘要,不能只相信调用方提供的
to/amount 文本。
Sequence 与 unordered 能力¶
传统账户交易使用 sequence 防重放。同一 signer 的并发 worker 若各自查询链上 sequence, 会构造冲突交易。应由 durable sequence manager 预占,广播未知时先查询 tx 与 committed sequence,再决定重播或重建。
Cosmos SDK 从 v0.53.0 起还支持由目标链显式启用的 unordered transaction:它必须把 sequence 留为零,并用唯一 timeout timestamp 等机制防重放,额外 gas 与 ante-handler 规则也可由链配置。这不表示所有 Cosmos 链都已启用;本文 v0.53.7 示例构建的是传统 sequence 交易。讲解时应先说“按 chain/app capability 选择”,不能把 sequence manager 永久泛化到 unordered 路径。
广播模式¶
broadcast_tx_sync 通常只等待 CheckTx;进入 mempool 不等于已经执行。即使被打包,也要看
committed transaction result 的 code、events 和 height。旧接口/资料可能把这层称为
DeliverTx;ABCI++ 栈通常通过 FinalizeBlock 产生 ExecTxResult。不同 CometBFT/Cosmos app
可能禁用或调整某些模式,adapter 应保留原始响应并按实际版本解码。
App-specific 不是小细节¶
Cosmos SDK 链共享框架,但模块、消息类型、denom、Bech32 prefix、fee token、sign mode、
extension options 和升级高度都可能不同。示例只注册 crypto 与 bank MsgSend,不能直接
声称适用于所有 Cosmos 链。
生产场景¶
- 对传统有序交易,每个 chain ID 独立 sequence domain 与配置,禁止仅按 address 共享 sequence;unordered 路径则使用链启用的独立防重放规则。
- 交易 build/simulate/sign 之间冻结 account number、fee、gas、消息 bytes,以及传统 sequence 或 unordered flag + timeout timestamp。
- 超时后查询 tx hash、mempool 与对应防重放状态;传统路径还要查询账户 sequence,不能仅凭请求超时释放预占。
- 链升级前用官方节点/CLI 做 golden vector,对比 sign bytes、tx hash 与执行结果。
排查与工具¶
保存 TxBody/AuthInfo/SignDoc 或其 hash、account number、sequence/timeout timestamp、chain ID、
raw tx、CheckTx 与 committed execution result(旧栈常称 DeliverTx)响应。常见错误按
wrong sequence、insufficient fee、out of gas、unauthorized 和 app-specific
codespace 分类。
架构取舍¶
直接依赖完整 Cosmos SDK 能获得官方类型与编码,但依赖图较大;薄 RPC/Protobuf 客户端更轻, 却需要自己保证 interface registry 和 sign mode 正确。无论选择哪种,都应放在独立 adapter/ module 中,避免把链 SDK 依赖扩散到核心账务。
深挖问答¶
- 为什么先设置空签名? → 让 builder 生成带 signer info/sequence 的 AuthInfo,再计算正确 sign bytes。
- sequence 在哪里签进去? → AuthInfo 的 signer info;SignerData 同时参与签名流程。
- sync 返回 code 0 是否完成? → 只代表 CheckTx 通过,仍需查询上链执行结果。
- 同地址跨两条 Cosmos 链共用 sequence 吗? → 不共用;chain ID 与各链账户状态独立。
- 能否只用 JSON 签名? → DIRECT 使用 protobuf 编码;其他 sign mode 也必须按链支持明确选择。
- Cosmos 交易都必须递增 sequence 吗? → 传统交易是;SDK 0.53+ 的 unordered 交易在链启用后使用零 sequence + 唯一 timeout timestamp,必须按目标 app 能力判断。
反模式与事故¶
- 漏掉 chain ID、account number 或 sequence,仍声称签名可防跨链重放。
- 多 worker 无预占地并发读取同一 sequence。
- 看到 SDK 有
SetUnordered就假定所有目标链已经启用,或给 unordered 交易填非零 sequence。 - CheckTx 成功立即记账为 settled。
- 所有 Cosmos 链共用一套 prefix、denom 和 interface registry。
- 交易失败后不保存 codespace/code/raw log,无法定位 app 模块错误。
代码示例¶
signed, err := cosmostx.BuildSignedBankSend(ctx, cosmostx.BuildInput{
Secret: secret,
AddressPrefix: "cosmos",
Recipient: recipient,
Denom: "uatom",
Amount: 1_000,
FeeAmount: 250,
GasLimit: 200_000,
ChainID: "cosmoshub-4",
AccountNumber: 7,
Sequence: 42,
})
测试会证明修改 chain ID 或 sequence 会改变 sign bytes。endpoint adapter 另实现 CometBFT
status、broadcast_tx_sync 与按 hash 查询 committed tx,并分别保留 CheckTx 和 committed
execution 的原始 code/codespace/log;后者在旧栈/旧资料中常被称为 DeliverTx。它仍不替代
app-specific 账户查询、simulate 和业务 finality。
外网 smoke 必须校验预期 chain ID,不能只凭 endpoint 返回 HTTP 200 判定连对了网络。