资讯详情

Cosmos SDK 应用解剖:深入理解 `app` 的结构、生命周期与模块体系

📅 2026/10/12 2:09:17 | 华诺云谱 👁 阅读
Cosmos SDK 应用解剖:深入理解 `app` 的结构、生命周期与模块体系
区块链【免费下载链接】cosmos-sdkFramework for building performant, customizable blockchains with native interoperability项目地址https://gitcode.com/gh_mirrors/co/cosmos-sdk点击查看免费下载导读本文以 Cosmos SDK 官方文档 Anatomy of a Cosmos SDK Application 为骨架结合当前仓库源码simapp、baseapp、types/module、runtime逐层拆解一个 Cosmos SDK 区块链应用的组成全节点守护进程如何启动、app.go如何定义与构造状态机、InitChainer/PreBlocker/BeginBlocker/EndBlocker等生命周期钩子如何被接入、模块Modules如何通过Msg服务与 gRPCQuery服务参与交易与查询、以及 CLI 与构建工具链如何组织。读完你将掌握 Cosmos SDK 应用的骨架级知识为后续阅读 交易生命周期、gas 与费用 以及 BaseApp 高级主题 打下基础。Node Client全节点守护进程Node Client守护进程 / 全节点客户端是一个 Cosmos SDK 区块链的核心进程。网络参与者运行该进程来初始化自己的状态机、连接其他全节点并随着新区块的到来持续更新状态机。它在概念上由三层构成网络层Networking、共识层Consensus与应用层State-machine Application。前两层通常由共识引擎默认是 CometBFT提供第三层才是用 Cosmos SDK 构建的状态机二者通过ABCIApplication Blockchain Interface通信^ ------------------------------- ^ | | | | | | State-machine Application | | | | | | Built with Cosmos SDK | | ^ | | | ----------- | ABCI | ---------- v | | v | ^ | | | | Blockchain Node | | Consensus | | | | | | | ------------------------------- | CometBFT | | | | | | Networking | | | | | | v ------------------------------- v构建与启动main函数与start命令全节点客户端以二进制形式呈现通常以-d后缀命名例如应用app的守护进程为appdgaia的守护进程为gaiad。该二进制由一个位于./cmd/appd/的简单main函数构建构建动作一般通过 Makefile 完成。当前仓库中 simapp/simd/main.go 就是这样的入口func main() { rootCmd : cmd.NewRootCmd() if err : svrcmd.Execute(rootCmd, clientv2helpers.EnvPrefix, simapp.DefaultNodeHome); err ! nil { fmt.Fprintln(rootCmd.OutOrStderr(), err) os.Exit(1) } }NewRootCmd见 simapp/simd/cmd/root.go会先预实例化一次simapp.NewSimApp以获得注入好的编码配置EncodingConfig再以此初始化客户端上下文client.Context最后用 cobra 组装根命令simd并挂载各模块的 CLI 命令与服务器级标准命令如start。二进制构建完成后运行start命令即可启动节点。start命令主要由以下三个动作构成创建状态机实例调用定义在 app.go 中的构造函数AppCreator创建应用实例。用已知最新状态初始化状态机状态从存储在~/.app/data目录中的db提取此时状态机位于高度appBlockHeight。创建并启动新的 CometBFT 实例节点与对等节点握手获取它们的最新blockHeight若其大于本地appBlockHeight则重放区块同步到该高度。若节点从创世启动CometBFT 会通过 ABCI 向app发送InitChain消息触发InitChainer。注意启动 CometBFT 实例时创世文件对应高度0创世文件中的状态在高度1提交。因此查询高度 0 的状态会返回错误。从源码看start命令位于 server/start.go它先从 context 中取出config打开db默认是 leveldb 实例再用appCreator创建应用实例随后以该应用实例实例化 CometBFT 节点——应用之所以能满足abci.Application接口是因为它内嵌了baseapp.BaseApp。node.New会确保应用高度与节点高度一致若本地落后则重放区块若应用高度为 0 则调用InitChain从创世文件初始化状态。核心应用文件app.go状态机的核心通常定义在一个名为app.go的文件中。它主要包含两部分内容应用的类型定义与创建、初始化应用的函数。当前仓库的参考实现是 simapp/app.go。应用的类型定义app.go中首先定义应用的type通常由以下部分组成内嵌runtime.Appruntime 包 通过依赖注入管理应用的核心组件与模块提供模块管理、状态存储与 ABCI 处理的声明式配置。Runtime包装了BaseApp当 CometBFT 将交易转发给应用时app通过runtime的方法将其路由到对应模块。BaseApp实现了全部 ABCI 方法 与路由逻辑。它根据 App Wiring 配置自动装配模块管理器。模块管理器负责模块间的协同操作例如注册模块的Msg服务 与 gRPCQuery服务以及为InitChainer、PreBlocker、BeginBlocker与EndBlocker设置模块间的执行顺序。一份 App Wiring 配置文件包含runtime必须实例化的模块清单通过depinject完成以及所有模块InitGenesis、Pre/Begin/EndBlocker方法的执行顺序。一个appCodec引用用于序列化/反序列化数据结构以存入 storestore 只能持久化[]bytes默认编解码器是 Protocol Buffers。一个legacyAmino编解码器引用Cosmos SDK 中仍有部分组件未迁移到appCodec而硬编码使用 Amino另有一些组件出于向后兼容显式使用 Amino。注意 Amino 编解码器将在未来版本中从 SDK 移除。以simapp为例其应用类型定义见 simapp/app.go内嵌了*baseapp.BaseApp持有legacyAmino、appCodec、txConfig、interfaceRegistry、store keys以及一批模块 KeeperAccountKeeper、BankKeeper、StakingKeeper、GovKeeper……和ModuleManager、BasicModuleManager、configurator等字段。补充在当前仓库中SimApp还实现了runtime.AppI与servertypes.Application接口见 simapp/app.go并导出了大部分参数以便测试辅助函数使用——注释中说明object capabilities 在测试中并不需要。构造函数app.go中同时定义了构造函数用于构造前述类型的新应用实例。该函数必须满足AppCreator签名定义于 server/types/app.go才能被守护进程的start命令 使用type AppCreator func(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions) servertypes.Application构造函数主要执行以下动作对照 simapp/app.go 的NewSimApp实例化新的codec并通过 BasicManager 初始化每个模块的 codec。NewSimApp首先创建InterfaceRegistry、appCodec : codec.NewProtoCodec(interfaceRegistry)与legacyAmino : codec.NewLegacyAmino()再通过std.RegisterLegacyAminoCodec/std.RegisterInterfaces完成全局注册。实例化新应用引用一个baseapp实例、一个 codec 以及所有合适的 store keys。NewSimApp通过baseapp.NewBaseApp(...)创建bApp并通过storetypes.NewKVStoreKeys(...)声明各模块的 KVStore key。实例化应用类型中定义的所有keeper使用各模块的NewKeeper函数。注意 keeper 必须按正确顺序实例化因为某个模块的NewKeeper可能需要引用另一个模块的 keeper——例如BankKeeper需要AccountKeeperStakingKeeper需要AccountKeeper与BankKeeper这在 simapp/app.go 的实例化顺序中体现得非常明显。实例化模块管理器传入每个模块的AppModule对象。NewSimApp通过module.NewManager(genutil.NewAppModule(...), auth.NewAppModule(...), ...)一次性注册 15 个模块并用module.NewBasicManagerFromManager构建 BasicManager。注册服务与路由借助模块管理器初始化应用的Msg服务、gRPCQuery服务、legacyMsg路由与 legacy query 路由。当 CometBFT 通过 ABCI 转发交易时应用据此将交易路由到对应模块的Msg服务收到 gRPC 查询请求时则据此路由到对应模块的 gRPC query 服务。Cosmos SDK 仍支持 legacyMsg与 legacy CometBFT 查询。注册模块不变量invariants不变量是每个区块结束时被评估的变量如代币总供给。检查过程由专门的 InvariantsRegistry 完成。若实际值与模块中预测值不一致将触发注册表中的特殊逻辑通常是链停止防止严重 bug 被忽略并造成难以修复的长期影响。设置模块间执行顺序为各模块的InitGenesis、PreBlocker、BeginBlocker、EndBlocker函数排序并非所有模块都实现这些函数。NewSimApp中依次调用了SetOrderPreBlockers、SetOrderBeginBlockers、SetOrderEndBlockers、SetOrderInitGenesis与SetOrderExportGenesis见 simapp/app.go并为排序写了详尽的注释例如begin block 中 slashing 必须排在 distr.BeginBlocker 之后以保证 validator fee pool 无残留、维持CanWithdrawInvariant、genutils 必须排在 staking 之后以保证质押池用创世账户的代币正确初始化。设置剩余应用参数InitChainer用于应用首次启动时初始化。PreBlocker在BeginBlock之前调用。BeginBlocker、EndBlocker每个区块开始与结束时调用。anteHandler处理手续费与签名验证。NewSimApp通过app.setAnteHandler(txConfig)组装 ante 装饰器链含SigGasConsumer、FeegrantKeeper、unordered tx gas cost 等并通过app.setPostHandler()设置 post 处理器链。挂载 storesNewSimApp调用app.MountKVStores(keys)。返回应用实例若loadLatest为真调用app.LoadLatestVersion()加载最新版本状态。需要强调构造函数只创建应用实例实际状态要么在节点重启时从~/.app/data目录延续要么在首次启动时由创世文件生成。InitChainerInitChainer是从创世文件初始化应用状态的函数例如创世账户的代币余额。当应用收到 CometBFT 引擎的InitChain消息即节点在appBlockHeight 0、处于创世时启动时被调用。应用必须在构造函数中通过SetInitChainer方法设置它。一般地InitChainer主要由各模块的InitGenesis函数构成调用模块管理器的InitGenesis进而调用其包含的每个模块的InitGenesis。模块InitGenesis的调用顺序必须在模块管理器中用SetOrderInitGenesis方法设定须在SetInitChainer之前调用。simapp的InitChainer见 simapp/app.go先反序列化创世状态genesisState再调用app.UpgradeKeeper.SetModuleVersionMap记录模块版本最后返回app.ModuleManager.InitGenesis(ctx, app.appCodec, genesisState)。BaseApp侧则在收到InitChainABCI 消息时调用app.abciHandlers.InitChainer见 baseapp/abci.go。PreBlockerPreBlocker是较新的生命周期方法围绕它有两层语义它在所有模块的BeginBlocker之前运行它可以修改存储中的共识参数并通过返回值向调用方发出信号。当它返回ConsensusParamsChangedtrue时调用方必须在 finalize context 中刷新共识参数app.finalizeBlockState.ctx app.finalizeBlockState.ctx.WithConsensusParams(app.GetConsensusParams())新的 ctx 必须传递给其余所有生命周期方法。simapp的PreBlocker直接委托给模块管理器return app.ModuleManager.PreBlock(ctx)见 simapp/app.go。模块管理器的PreBlock实现见 types/module/module.go遍历OrderPreBlockers中的模块仅对实现了appmodule.HasPreBlocker接口的模块调用其PreBlock并聚合各模块返回的ConsensusParamsChanged标志。simapp将 upgrade 与 auth 模块设为 pre-blockers见 simapp/app.go。BeginBlocker 与 EndBlockerCosmos SDK 允许开发者实现应用内自动执行的代码通过BeginBlocker与EndBlocker两个函数完成。它们分别在应用收到 CometBFT 共识引擎的FinalizeBlock消息时、每个区块的开始与结束时被调用。应用必须在构造函数中通过SetBeginBlocker与SetEndBlocker设置它们。一般地BeginBlocker与EndBlocker主要由各模块的BeginBlock与EndBlock函数构成通过模块管理器间接调用。模块BeginBlock/EndBlock的调用顺序须分别在模块管理器中用SetOrderBeginBlockers与SetOrderEndBlockers设定须在SetBeginBlocker/SetEndBlocker之前完成。模块管理器的BeginBlock实现见 types/module/module.go为每次调用创建带事件管理器EventManager的子上下文以聚合事件遍历OrderBeginBlockers仅对实现appmodule.HasBeginBlocker的模块调用其BeginBlock最终返回聚合的sdk.BeginBlock{Events: ...}。EndBlock同理见 types/module/module.go。simapp的对应实现见 simapp/app.gofunc (app *SimApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) { return app.ModuleManager.BeginBlock(ctx) } func (app *SimApp) EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) { return app.ModuleManager.EndBlock(ctx) }重要提醒应用专属区块链是确定性的。开发者必须小心不要在BeginBlocker或EndBlocker中引入非确定性同时避免让它们过于昂贵——因为 gas 并不约束BeginBlocker与EndBlocker的执行成本。Register CodecEncodingConfigapp.go中最后一个重要部分是EncodingConfig结构其目标是定义整个应用将使用的编解码器。simapp中的定义见 simapp/params/encoding.gotype EncodingConfig struct { InterfaceRegistry types.InterfaceRegistry Codec codec.Codec TxConfig client.TxConfig Amino *codec.LegacyAmino }四个字段的含义如下InterfaceRegistryProtobuf codec 用它处理使用google.protobuf.Any。细节上Cosmos SDK 使用 Protobuf 规范的 gogoprotobuf。CodecCosmos SDK 全链默认使用的编解码器由BinaryCodec编码/解码状态与JSONCodec向用户输出数据如 CLI 中组成。默认使用 Protobuf。TxConfig定义客户端可用来生成应用自定义具体交易类型的接口。当前 SDK 处理两种交易类型SIGN_MODE_DIRECT使用 Protobuf 二进制作为线上编码与SIGN_MODE_LEGACY_AMINO_JSON依赖 Amino。更多见 交易。AminoCosmos SDK 部分遗留组件出于向后兼容仍使用 Amino。每个模块暴露RegisterLegacyAmino方法注册模块专属类型。应用开发者不应再使用该 Amino codec它将在未来版本移除。应用应创建自己的编码配置。simapp中simd的根命令正是通过预实例化应用、把四个字段逐一从SimApp取出来组装params.EncodingConfig见 simapp/simd/cmd/root.go。模块应用的心脏与灵魂模块 是 Cosmos SDK 应用的心脏与灵魂可视为嵌套在状态机中的状态机。当交易经 ABCI 从底层 CometBFT 引擎转发到应用时由baseapp路由到对应模块处理。这一范式让开发者能轻松构建复杂状态机因为所需的多数模块往往已存在。对开发者而言构建 Cosmos SDK 应用的大部分工作围绕构建尚不存在的自定义模块并将其与已有模块整合成一个连贯应用。按惯例应用自己的模块存放在应用的x/目录不要与 Cosmos SDK 内置模块的x/目录混淆。Application Module Interface模块必须实现 Cosmos SDK 定义的两个接口AppModuleBasic与AppModule。前者实现模块的基本非依赖元素如 codec后者处理模块方法的主体包括需要引用其他模块 keeper 的方法。按惯例AppModule与AppModuleBasic类型定义在module.go文件中。AppModule暴露一组便于将模块组合成连贯应用的方法由模块管理器调用。Msg服务每个应用模块定义两个 Protobuf 服务处理消息的Msg服务与处理查询的 gRPCQuery服务。若把模块看作状态机Msg服务就是一组状态转换 RPC 方法。每个 ProtobufMsg服务方法与一个 Protobuf 请求类型一一对应该类型必须实现sdk.Msg接口。sdk.Msg被打包进交易每笔交易包含一条或多条消息。当全节点收到有效交易区块时CometBFT 通过DeliverTx将每条交易转发给应用应用按如下步骤处理收到交易后先将其从[]byte反序列化。验证交易的若干属性如费用支付与签名然后提取交易中包含的Msg。sdk.Msg使用 ProtobufAny编码。通过分析每个Any的type_urlbaseapp 的msgServiceRouter将sdk.Msg路由到对应模块的Msg服务。消息处理成功则更新状态。更多细节见交易生命周期。模块开发者构建自定义模块时会创建自定义Msg服务。惯例是在tx.proto文件中定义MsgProtobuf 服务。例如x/bank模块定义了两个转账方法见 proto/cosmos/bank/v1beta1/tx.protoservice Msg { option (cosmos.msg.v1.service) true; rpc Send(MsgSend) returns (MsgSendResponse); rpc MultiSend(MsgMultiSend) returns (MsgMultiSendResponse); }服务方法使用keeper更新模块状态。每个模块还应实现AppModule接口的RegisterServices方法调用生成的 Protobuf 代码提供的RegisterMsgServer函数。当前仓库中模块管理器的RegisterServicesapp.ModuleManager.RegisterServices(app.configurator)在构造函数中统一触发见 simapp/app.go。gRPCQuery服务gRPCQuery服务允许用户通过 gRPC。gRPCQuery服务定义在模块 Protobuf 定义文件的query.proto中暴露单一的QueryProtobuf 服务每个 gRPC 查询端点对应Query服务中以rpc关键字开头的一个服务方法。Protobuf 为每个模块生成QueryServer接口包含全部服务方法模块的keeper需要实现该接口为每个服务方法提供具体实现。这个具体实现就是对应 gRPC 查询端点的 handler。最后每个模块还要在AppModule接口的RegisterServices方法中调用生成的RegisterQueryServer函数。KeeperKeepers是模块 store 的守门人。读写模块 store 必须经由某个keeper方法这由 Cosmos SDK 的对象能力模型 保证只有持有 store 钥匙的对象才能访问它而只有模块的keeper应持有模块 store 的钥匙。Keepers通常定义在keeper.go文件中包含keeper的类型定义与方法。keeper类型定义通常由以下部分组成模块在 multistore 中的 store钥匙key对其他模块keeper 的引用仅当该keeper需要读写其他模块的 store 时才需要对应用codec 的引用因为 store 只接受[]bytes作为值keeper需要它来在存储前 marshal 结构、取回时 unmarshal。除类型定义外keeper.go的另一个重要组件是构造函数NewKeeper。它用codec、storekeys以及可能引用到的其他模块keeper作为参数实例化上述类型的新keeper。NewKeeper从应用的构造函数中调用文件其余部分定义keeper的方法主要是 getter 与 setter。CLI、gRPC 服务与 REST 接口每个模块都定义暴露给终端用户的命令行命令、gRPC 服务与 REST 路由通过应用接口暴露使用户能创建模块定义类型的消息或查询该模块管理的状态子集。CLI模块相关的命令 一般定义在模块目录的client/cli文件夹中。CLI 将命令分为交易与查询两类分别定义在client/cli/tx.go与client/cli/query.go中都构建在 Cobra 库之上交易命令让用户生成新交易使其可被打包进区块并最终更新状态。模块中每个消息类型应创建一个命令。命令用终端用户提供的参数调用消息构造函数并将其包装成交易签名与交易元数据的添加由 Cosmos SDK 处理。查询命令让用户查询模块定义的状态子集。查询命令将查询转发给应用的查询路由器后者依据queryRoute参数将其路由到合适的 querier。gRPCgRPC 是一个现代、开源、高性能、多语言支持的 RPC 框架是外部客户端钱包、浏览器、其他后端服务与节点交互的推荐方式。每个模块可暴露称为服务方法 的 gRPC 端点定义在模块的 Protobufquery.proto文件中。服务方法由名称、输入参数与输出响应定义。模块随后需要在AppModuleBasic上定义RegisterGRPCGatewayRoutes方法将客户端 gRPC 请求接入模块内的正确 handler为每个服务方法定义对应 handler。handler 实现服务 gRPC 请求的核心逻辑位于keeper/grpc_query.go文件中。gRPC-gateway REST 端点有些外部客户端可能不想使用 gRPC。为此 Cosmos SDK 提供 gRPC gateway 服务将每个 gRPC 服务暴露为对应 REST 端点参见 grpc-gateway 文档。REST 端点在 Protobuf 文件中与 gRPC 服务一同定义使用 Protobuf 注解。想要暴露 REST 查询的模块应在其rpc方法上添加google.api.http注解。默认情况下SDK 中定义的所有 REST 端点 URL 以/cosmos/前缀开头。Cosmos SDK 还提供一个开发端点为这些 REST 端点生成 Swagger 定义文件。该端点可在app.toml配置文件中通过api.swagger键启用。应用接口接口 让终端用户与全节点客户端交互查询全节点数据或创建并发送新交易使其被全节点转发并最终进入区块。主要接口是命令行接口CLI。Cosmos SDK 应用的 CLI 通过聚合应用所用各模块定义的 CLI 命令 构建与守护进程同名如appd定义在appd/main.go文件中包含以下内容main()函数执行以构建appd接口客户端。该函数准备每个命令并将它们添加到rootCmd后构建。在appd根部添加status、keys、config等通用命令、查询命令、交易命令以及rest-server。查询命令通过调用queryCmd函数添加返回一个 Cobra 命令包含应用各模块定义的查询命令从main()以sdk.ModuleClients数组传入以及其他底层查询命令如区块或验证者查询。查询命令通过appd query [query]调用。交易命令通过调用txCmd函数添加与queryCmd类似返回包含各模块交易命令及签名、广播等底层交易命令的 Cobra 命令。交易命令通过appd tx [tx]调用。simapp中 simapp/simd/main.go 即调用cmd.NewRootCmd()构建根命令再交给svrcmd.Execute执行根命令内部则完成编码配置注入、客户端上下文初始化与各类命令挂载见 simapp/simd/cmd/root.go。依赖与 Makefile本节为可选内容开发者可自由选择依赖管理工具与项目构建方式。目前最常用的版本控制框架是go.mod声明模块依赖。构建应用一般使用 Makefile 定义了build、install等目标例如将simd二进制安装到$(GOBIN)scripts/go-mod-tidy-all.sh 等脚本则用于批量维护各子模块的go.mod。进一步阅读本文覆盖了应用的结构骨架你可以顺着以下文档继续深入交易的完整处理流程交易生命周期 与 查询生命周期底层执行引擎BaseApp 详解 与 Node Client 详解编码层原理Encoding编码 与 ADR-019Protobuf 状态编码构建你自己的应用使用 runtime 构建应用 与 构建模块指南运行节点实操运行节点、API 与 CLI。赞分享区块链【免费下载链接】cosmos-sdkFramework for building performant, customizable blockchains with native interoperability项目地址https://gitcode.com/gh_mirrors/co/cosmos-sdk点击查看免费下载相关推荐Cosmos SDK查询生命周期深度解析Cosmos SDK查询生命周期深度解析 引言 在区块链应用开发中查询Query功能是连接用户与链上数据的核心桥梁。Cosmos SDK作为构建高性能区块区块链wuzz源码走读App结构体与生命周期管理wuzz源码走读App结构体与生命周期管理 一、App结构体核心定义 在wuzz项目中 App 结构体是整个应用的核心控制单元定义于 wuzz.go ht开发工具Nuke项目构建基础深入理解构建结构与生命周期Nuke项目构建基础深入理解构建结构与生命周期 构建项目的基本结构 在Nuke构建系统中构建项目本质上是一个标准的.NET控制台应用程序但与传统控制台应用上一篇Wand-Enhancer如何通过开源扩展优化游戏修改体验下一篇专业指南掌握国家中小学智慧教育平台电子课本高效下载的3个关键步骤创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑