Cosmos SDK Proto 文件管理与 Buf 集成指南:从目录规范到代码生成
Cosmos SDK Proto 文件管理与 Buf 集成指南:从目录规范到代码生成
本文以 proto/README.md 为核心脉络,系统讲解 Cosmos SDK 中 Protocol Buffers(proto)文件的组织方式、与 buf.build 的同步发布流程、模块配置(buf.yaml)细则,以及基于 buf 的 gogo / pulsar / swagger 三条代码生成流水线。读完本文,你将掌握如何在 Cosmos SDK 仓库中定位与维护 proto 文件、如何将指定 commit 的 proto 导出到本地、如何配置 lint 与 breaking 检查,以及生成代码最终落入哪些目录。
proto 目录:Cosmos SDK 公共 API 的唯一事实来源
Cosmos SDK 将所有 proto 文件集中定义在仓库根目录的 proto/ 文件夹中。根据 proto/README.md 的说明,该目录是 Cosmos SDK 全部 protobuf 定义的唯一存放位置,并与 buf.build/cosmos/cosmos-sdk 这一 Buf Schema Registry(BSR)模块保持定期同步。
从目录树可以看到,proto 文件按命名空间与模块双重维度组织:
cosmos/:SDK 各业务模块的接口定义,例如auth、bank、gov、staking、distribution、mint、slashing、upgrade、feegrant、authz、evidence、consensus、epochs、tx等;amino/:Amino 编码扩展定义(amino.proto),用于在 proto 层面声明 Amino 注册名、编码格式等元数据;tendermint/:来自 CometBFT(原 Tendermint)的协议类型,包括abci、crypto、types、p2p、libs、version等子目录,由make proto-update-deps从上游拉取(见 Makefile)。
每个模块的 proto 内部普遍遵循「版本化子目录」约定,例如 cosmos/bank/v1beta1/、cosmos/gov/v1/、cosmos/gov/v1beta1/,同时在 cosmos/<module>/module/v1/ 下放置模块配置(module.proto)。以 proto/cosmos/bank/v1beta1/query.proto 为例,package cosmos.bank.v1beta1; 与 go_package 声明共同决定了生成的 Go 包路径与 gRPC 服务签名。
注意:proto 目录本身不承担面向最终用户的文档职责。按 proto/README.md 的要求,面向用户的文档应放在 proto/buf.md 以及各 protobuf 包内部,并遵循 Buf 的 BSR 文档编写规范。
与 Buf Schema Registry 的同步与版本对齐
Cosmos SDK 的 proto 模块在 Buf 上以 buf.build/cosmos/cosmos-sdk 对外发布。SDK 维护者需要定期在该目录下执行 buf push,把最新的 proto 定义推送到 BSR,实现仓库与远端模块的同步。
proto/README.md 给出了 SDK 版本与 Buf 上提交(commit)的对应关系,这是定位历史 proto 快照的关键索引:
| Cosmos SDK 版本 | Buf Commit 版本 |
|---|---|
| v0.46.0 之前 | 不可用(受 bufbuild/buf issue #1415 限制) |
| v0.46.x | 8cb30a2c4de74dc9bd8d260b1e75e176 |
| v0.47.x | v0.47.0 |
| v0.50.x | v0.50.0 |
| Next(开发中) | BSR 上的 main 分支最新提交 |
这张表说明了两层事实:其一,从 v0.46.0 起 SDK 才开始系统性地将 proto 同步到 BSR,更早版本因工具链原因无法在 BSR 上追溯;其二,稳定版本(如 v0.47.x、v0.50.x)以版本标签形式存在,而开发主线始终以 main 的最新 commit 为准。当你需要为某个 SDK 版本获取对应 proto 时,可依据此表确定 commit 标识。
从 BSR 拉取指定版本的 proto:buf export 实战
proto/README.md 提供了一条把远端模块导出到本地目录的命令:
buf export buf.build/cosmos/cosmos-sdk:${commit} --output .
其中 ${commit} 即上表中对应的 commit 版本(例如 8cb30a2c4de74dc9bd8d260b1e75e176、v0.47.0 或 v0.50.0)。该命令会把指定 commit 的整套 proto 文件导出到当前目录(--output .),非常适合:
- 为特定 SDK 版本生成客户端代码;
- 对比不同版本间的接口差异;
- 在 CI 中断言当前 proto 与已发布版本的兼容性。
仓库中的 buf.work.yaml 声明了 Buf 工作区仅包含 proto 一个目录,因此所有与 buf 相关的操作(buf lint、buf breaking、buf generate、buf push)都应在该目录上下文内执行。
buf.yaml:模块依赖、lint 与 breaking 规则详解
proto/buf.yaml 是驱动整个 Buf 流程的核心配置,其内容与 buf.build/cosmos/cosmos-sdk 模块一一对应:
version: v1
name: buf.build/cosmos/cosmos-sdk
deps:
- buf.build/cosmos/cosmos-proto
- buf.build/cosmos/gogo-proto
- buf.build/googleapis/googleapis
- buf.build/protocolbuffers/wellknowntypes
breaking:
use:
- FILE
ignore:
- testpb
lint:
use:
- STANDARD
- COMMENTS
- FILE_LOWER_SNAKE_CASE
except:
- UNARY_RPC
- COMMENT_FIELD
- SERVICE_SUFFIX
- PACKAGE_VERSION_SUFFIX
- RPC_REQUEST_STANDARD_NAME
ignore:
- tendermint
各字段的工程含义如下:
name:模块在 BSR 上的完整标识,buf push时即推送到该模块;deps:依赖的外部 proto 模块。cosmos/cosmos-proto提供了cosmos_proto包中的自定义扩展(如scalar、method_added_in、message_added_in),cosmos/gogo-proto提供 gogoproto 扩展(如customtype、nullable、equal),googleapis与wellknowntypes则提供google/api/annotations.proto(REST 路由注解)与google/protobuf/any.proto、timestamp.proto等标准类型。具体的锁定版本与内容摘要记录在 proto/buf.lock 中,其中包含各依赖的 commit 与 shake256 摘要,保证构建可复现;breaking.use: [FILE]:以文件级粒度检测破坏性变更,ignore: [testpb]豁免测试用 proto;lint.use:启用 Buf 的标准检查集,外加注释检查与文件名小写下划线(FILE_LOWER_SNAKE_CASE)规则;lint.except:显式豁免的规则,例如允许一元 RPC(UNARY_RPC)、允许省略字段注释(COMMENT_FIELD)、不强制 Service 名称带Service后缀、不强制包名带版本后缀等——这些豁免反映了 Cosmos SDK 长期形成的 proto 风格;lint.ignore: [tendermint]:来自上游 CometBFT 的 proto 不做 SDK 风格约束。
在 proto/cosmos/base/v1beta1/coin.proto 中可以看到这些扩展的实际用法:Coin.amount 字段同时声明了 (cosmos_proto.scalar) = "cosmos.Int"、(gogoproto.customtype) = "cosmossdk.io/math.Int" 与 (gogoproto.nullable) = false,使生成的 Go 代码直接使用 SDK 自己的高精度整数类型。而 proto/cosmos/bank/v1beta1/query.proto 中的 (cosmos.query.v1.module_query_safe) = true 与 (google.api.http).get 注解,则分别标记了模块内查询安全性并生成了 REST 路由。此外 proto/amino/amino.proto 定义了 amino.name、amino.field_name、amino.dont_omitempty 等扩展,用于把 Amino 编码的注册名、字段别名与空值策略直接沉淀在 proto 层。
从 proto 到 Go:三条代码生成流水线
buf generate 是生成 Go 代码的入口,仓库在 proto 目录下提供了三个生成模板,对应三套不同的目标产物。
gogo 流水线(传统 API,面向 SDK 内部)
proto/buf.gen.gogo.yaml 配置了两个插件:
version: v1
plugins:
- name: gocosmos
out: ..
opt: plugins=grpc,Mgoogle/protobuf/any.proto=github.com/cosmos/gogoproto/types/any
- name: grpc-gateway
out: ..
opt: logtostderr=true,allow_colon_final_segments=true
gocosmos 插件基于 gogoproto 生成带自定义类型的代码(输出到仓库根目录,即 out: ..),grpc-gateway 插件则生成 REST 网关桩。真正驱动生成的是 scripts/protocgen.sh:它先用 clang-format 统一格式化全部 *.proto,再遍历 cosmos/ 与 amino/ 目录下每个 proto 文件,通过正则判断 go_package 是否指向 cosmossdk.io/api——只有不指向 pulsar 新 API 的文件才走 gogo 生成,避免两套工具链互相覆盖;随后把生成的 github.com/cosmos/cosmos-sdk/* 与 cosmossdk.io/* 拷贝回正确位置,并处理 store/v2 的特殊目录映射,最后调用 go mod tidy。
pulsar 流水线(新一代 google.golang.org/protobuf API)
proto/buf.gen.pulsar.yaml 面向新的 protobuf v2 API:
version: v1
managed:
enabled: true
go_package_prefix:
default: cosmossdk.io/api
except:
- buf.build/googleapis/googleapis
- buf.build/cosmos/gogo-proto
- buf.build/cosmos/cosmos-proto
override:
plugins:
- name: go-pulsar
out: ../api
opt: paths=source_relative
- name: go-grpc
out: ../api
opt: paths=source_relative
它启用 managed 模式,统一将 go_package 前缀改写为 cosmossdk.io/api(外部依赖模块除外),生成物落入 api/ 目录。例如 api/cosmos/bank/v1beta1/query.pulsar.go 即为 protoc-gen-go-pulsar 生成的、基于 google.golang.org/protobuf 运行时的新式代码。与之对应的 api/go.mod 是一个独立的 Go 模块(module cosmossdk.io/api),依赖 cosmos-proto、gogoproto、grpc 与 google.golang.org/protobuf,把生成的公共 API 与 SDK 主模块解耦。执行入口是 scripts/protocgen-pulsar.sh:它先清空 api 目录中的旧生成文件,再对 proto 目录生成 pulsar 代码,并顺带生成 testutil/testdata 与 x/tx 的测试用代码。
swagger 流水线(REST 文档)
proto/buf.gen.swagger.yaml 使用 openapiv2 插件把查询服务转换为 OpenAPI/Swagger 定义。配合 scripts/protoc-swagger-gen.sh,SDK 只对每个目录下的 query.proto 与 service.proto 生成 swagger 片段,再借助 Node.js 工具 swagger-combine 依据 client/docs/config.json 合并为 client/docs/swagger-ui/swagger.yaml。
Makefile 封装与容器化构建
日常开发中无需手动拼装上述命令,Makefile 提供了统一的 proto 工作流目标:
make proto-format:用 clang-format 格式化所有 proto 文件;make proto-lint:执行buf lint --error-format=json;make proto-check-breaking:基于main分支执行buf breaking,拦截破坏性变更;make proto-gen:运行 scripts/protocgen.sh,串联 gogo 与 pulsar 生成;make proto-swagger-gen:运行 swagger 生成脚本;make proto-all:依次执行 format、lint、gen 三个步骤;make proto-update-deps:从 CometBFT v0.38.0 拉取tendermint相关 proto 并执行buf mod update刷新依赖锁。
这些目标统一通过 Docker 镜像 ghcr.io/cosmos/proto-builder:0.18.1 执行(见 Makefile),镜像定义在 contrib/devtools/Dockerfile 中:它基于 bufbuild/buf:1.64 与 golang:1.26.6-alpine,预装了 nodejs、npm、clang-extra-tools、g++、jq、swagger-combine,以及 protoc-gen-go-pulsar、protoc-gen-go、protoc-gen-grpc-gateway、protoc-gen-openapiv2、protoc-gen-go-grpc 和整套 gogoproto 工具链。如需在本地手动复现生成流程,可参考 scripts/protocgen.sh 顶部注释中的 Docker 运行方式:
docker build --pull --rm -f "contrib/devtools/Dockerfile" -t cosmossdk-proto:latest "contrib/devtools"
docker run --rm -v $(pwd):/workspace --workdir /workspace cosmossdk-proto sh ./scripts/protocgen.sh
文档与公共 API 的边界
proto/buf.md 是模块在 BSR 上展示的简要说明,内容为:proto 目录定义了 Cosmos SDK 的公共 protobuf API。结合 proto/README.md 的约束可以总结出清晰的边界规则:
- 接口定义:只放在
proto/目录,任何新增或修改的 proto 都在此落地; - 面向用户的文档:写入 proto/buf.md 或各 protobuf 包内部(BSR 文档体系),而非仓库 README;
- 生成的代码:gogo 风格代码输出到仓库根目录对应包(如
types/tx),pulsar 风格代码输出到独立的 api/ 模块,swagger 文档合并进 client/docs/swagger-ui/swagger.yaml。
对 SDK 模块开发者而言,理解这条「proto 定义 → Buf 模块 → 生成代码」的链路,是参与接口演进、保证链上状态兼容(Makefile 的 breaking 检查)与对外提供 gRPC/REST 服务(proto/cosmos/bank/v1beta1/query.proto 中的 HTTP 注解)的基础。