app.go 是将应用组装成一条可运行链的地方。它会创建与 CometBFT 通信的 BaseApp 实例,分配 store key,初始化 keeper,注册模块,配置执行顺序,挂载 store,并设置生命周期钩子和 AnteHandler。最后,它通过 LoadLatestVersion 封装整个应用。 最终产物是一个单一构造函数 NewExampleApp,它返回一条已完成全部接线、可直接运行的链。 本页中的大多数示例都来自 example 仓库中的计数器模块示例,其中 x/counter 被接入到一条更完整的链中。最小计数器模块示例则展示了将 x/counter 添加到一个精简应用时,app.go 需要做的较小改动。参见“构建模块”教程中的第 10 步:接入 app.go。

app.go 的作用

app.go 按顺序对整条链执行一次性的初始化:
1. Create BaseApp and codecs
2. Allocate store keys
3. Initialize keepers
4. Create the ModuleManager
5. Configure execution ordering
6. Register module services
7. Mount KV stores
8. Set lifecycle hooks (InitChainer, PreBlocker, BeginBlocker, EndBlocker, AnteHandler)
9. Load latest version
这个顺序是严格的:
  • keeper 依赖 store key,因此必须先创建 key。
  • ModuleManager 依赖 keeper,因此模块必须在 keeper 构造完成后再创建。
  • 生命周期钩子依赖 ModuleManager,因此钩子的接线要更晚进行。
  • LoadLatestVersion 会封装 BaseApp,因此它必须最后执行。

应用结构体

应用结构体会嵌入 BaseApp,并保存所有 keeper 以及 ModuleManager:
type ExampleApp struct {
	*baseapp.BaseApp
	appCodec          codec.Codec
	interfaceRegistry codectypes.InterfaceRegistry

	keys map[string]*storetypes.KVStoreKey

	// representative keepers
	AccountKeeper         authkeeper.AccountKeeper
	BankKeeper            bankkeeper.Keeper
	ConsensusParamsKeeper consensusparamkeeper.Keeper
	CounterKeeper         *counterkeeper.Keeper

	// application wiring helpers
	ModuleManager      *module.Manager
	BasicModuleManager module.BasicManager
	configurator module.Configurator
}
嵌入 *baseapp.BaseApp 让 ExampleApp 具备完整的 BaseApp 接口:ABCI 方法、消息与查询路由、store 管理以及生命周期钩子。keeper 字段会导出,这样测试代码和 CLI 辅助工具就可以直接引用它们。keys map 保存初始化过程中分配的 KV store key。真实示例应用还包含额外的 keeper 和辅助字段;这段摘录只展示了理解接线模式所必需的部分。

创建 BaseApp

NewExampleApp 首先会设置 codec 并创建 BaseApp 实例:
appCodec := codec.NewProtoCodec(interfaceRegistry)
txConfig := authtx.NewTxConfig(appCodec, authtx.DefaultSignModes)

bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseAppOptions...)
bApp.SetVersion(version.Version)
bApp.SetInterfaceRegistry(interfaceRegistry)
bApp.SetTxEncoder(txConfig.TxEncoder())
baseapp.NewBaseApp 会使用名称、logger、数据库和 TxDecoder 创建 BaseApp。TxDecoder 负责将来自 CometBFT 的原始交易字节转换为 BaseApp 可检查并路由的 sdk.Tx。额外的函数式选项(baseAppOptions)允许调用方在不直接修改 NewExampleApp 的情况下配置 pruning、最低 gas price、chain ID 和 optimistic execution。完整示例应用在这段摘录之外,还接入了 legacy Amino 支持、tracing 和 interface 注册。关于其字段和行为的更完整说明,参见 BaseApp 概览。

分配 store key

每个需要持久化状态的模块都需要一个专用的 KV store key。所有 key 都会在创建任何 keeper 之前统一分配:
keys := storetypes.NewKVStoreKeys(
	authtypes.StoreKey,
	banktypes.StoreKey,
	stakingtypes.StoreKey,
	distrtypes.StoreKey,
	slashingtypes.StoreKey,
	govtypes.StoreKey,
	consensusparamtypes.StoreKey,
	countertypes.StoreKey,
)
每个模块都会在 types/keys.go 中将自己的 store key 名称定义为字符串常量(例如,countertypes.StoreKey = "counter",参见“构建模块”教程中的第 4 步:类型)。NewKVStoreKeys 接收这些名称,并为每一个分配一个 *storetypes.KVStoreKey。这些 key 会传给 keeper 构造函数,之后再通过 MountKVStores 挂载到 CommitMultiStore 上。不会有两个模块共享同一个 key;正是这种隔离保证了模块状态彼此分离。

初始化 keeper

每个 keeper 都会使用自己的 store key、codec,以及对其他 keeper 的依赖来完成初始化。ConsensusParamsKeeper 会最先初始化,因为它必须在任何其他 keeper 创建之前调用 bApp.SetParamStore:
app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper(...)
bApp.SetParamStore(app.ConsensusParamsKeeper.ParamsStore)

app.AccountKeeper = authkeeper.NewAccountKeeper(...)
app.BankKeeper = bankkeeper.NewBaseKeeper(..., app.AccountKeeper, ...)
app.CounterKeeper = counterkeeper.NewKeeper(
	runtime.NewKVStoreService(keys[countertypes.StoreKey]),
	appCodec,
	app.BankKeeper,
)
runtime.NewKVStoreService(key) 会将原始 store key 封装为一个服务接口,keeper 可以通过它从上下文中打开自己的 store。这样 keeper 就不需要直接持有底层 store 的引用,而是在每个方法运行时,从传入的 context 中动态获取它。 keeper 的初始化顺序很重要:BankKeeper 会接收 app.AccountKeeper 作为参数,因此必须先初始化 AccountKeeper。同样的依赖顺序规则也适用于其他地方。计数器模块示例还会把 app.BankKeeper 传给 counterkeeper.NewKeeper,展示了自定义模块如何依赖已有模块服务(参见预期 keeper 与费用收集)。当模块彼此依赖时,可以在两个 keeper 都存在之后再通过 hook 将它们连接起来:
app.StakingKeeper.SetHooks(
	stakingtypes.NewMultiStakingHooks(
		app.DistrKeeper.Hooks(),
		app.SlashingKeeper.Hooks(),
	),
)
传给大多数 keeper 的 authority address(authtypes.NewModuleAddress(govtypes.ModuleName).String())是被允许调用 MsgUpdateParams 这类特权消息的地址。治理模块通过从治理模块账户发送消息来控制参数变更。关于这种模式如何工作,参见参数。

注册模块

在所有 keeper 都初始化完成之后,会使用应用中启用的全部模块来创建模块管理器:
app.ModuleManager = module.NewManager(
	auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, nil),
	bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, nil),
	consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper),
	counter.NewAppModule(appCodec, app.CounterKeeper),
	// ...other modules...
)
module.NewManager 接收一组 AppModule 实现。每个 AppModule 都会封装一个 keeper,并实现 ModuleManager 所需的接口:genesis、区块钩子、消息与查询服务注册,以及 simulation 支持。真实示例应用会在 x/counter 周围包含完整的内置模块集合;关于模块注册的完整讲解,参见第 10 步:接入 app.go;这段摘录展示的是最基础的注册模式。随后还会基于 ModuleManager 派生出 BasicModuleManager,用于 codec 注册和默认 genesis 处理。

模块管理器

ModuleManager 是应用的模块注册表。它持有所有 AppModule 实例的引用,并协调这些模块参与区块生命周期。当 BaseApp 触发生命周期钩子(PreBlock、BeginBlock、EndBlock、InitGenesis)时,它会委托给 ModuleManager,后者再按配置好的顺序调用每个模块对应的方法。 ModuleManager 还负责服务注册:它会遍历所有模块,并调用每个模块的 RegisterServices,将 MsgServer 和 QueryServer 实现注册到 BaseApp 的路由器中。关于执行模型视角下的说明,参见BaseApp 中的模块管理器。

执行顺序

模块运行其区块钩子和 genesis 初始化的顺序非常重要。有些模块依赖其他模块已经先完成状态更新。模块管理器创建完成后,会显式配置执行顺序:
app.ModuleManager.SetOrderPreBlockers(
	authtypes.ModuleName,
)
app.ModuleManager.SetOrderBeginBlockers(
	distrtypes.ModuleName,
	slashingtypes.ModuleName,
	stakingtypes.ModuleName,
	countertypes.ModuleName,
	genutiltypes.ModuleName,
)
app.ModuleManager.SetOrderEndBlockers(
	banktypes.ModuleName,
	govtypes.ModuleName,
	stakingtypes.ModuleName,
	countertypes.ModuleName,
	genutiltypes.ModuleName,
)
genesis 初始化顺序是独立配置的,而且同样重要:
genesisModuleOrder := []string{
	authtypes.ModuleName,
	banktypes.ModuleName,
	distrtypes.ModuleName,
	stakingtypes.ModuleName,
	slashingtypes.ModuleName,
	govtypes.ModuleName,
	consensusparamtypes.ModuleName,
	vestingtypes.ModuleName,
	countertypes.ModuleName,
	genutiltypes.ModuleName,
}
app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...)
app.ModuleManager.SetOrderExportGenesis(exportModuleOrder...)
SetOrderExportGenesis 控制的是:当链导出为 genesis 文件时,各模块按什么顺序序列化自己的状态,例如在硬分叉期间,或创建基于快照的测试网时。导出顺序可以与 init genesis 顺序不同;在这个示例链中,两者使用了不同的顺序。 示例应用中的注释解释了原因:genutil 必须在 staking 之后运行,这样才能先初始化 staking pool,再处理 genesis transaction;同时它还必须在 auth 之后运行,这样它才能访问 auth 参数。 每一种 hook 类型都有自己的顺序约束:
  • PreBlock:在 BeginBlock 之前运行。用于必须在区块开始前生效的升级和共识参数变更。
  • BeginBlock:在每个区块开始时运行。用于每区块的例行处理,例如铸造通胀奖励和分发 staking 奖励。
  • EndBlock:在区块内所有交易执行完之后运行。用于依赖区块累计状态的逻辑,例如统计治理投票结果或重新计算验证者权重。
  • InitGenesis:仅在链启动时运行一次,将 genesis.json 中的数据填充到各模块的 store 中。
如果想看一个在自定义模块中实现这些 hook 的完整示例,参见“完整计数器模块演练”中的BeginBlock 和 EndBlock。

路由设置

在配置好执行顺序后,模块服务会注册到 BaseApp 的路由器中:
app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
err := app.ModuleManager.RegisterServices(app.configurator)
RegisterServices 会遍历所有模块,并调用每个模块的 RegisterServices(cfg) 方法。每个模块都使用 configurator 将自己的 MsgServer 注册到消息路由器中,并将自己的 QueryServer 注册到 gRPC 查询路由器中。完成这一步后,BaseApp 就可以将任何已注册的消息类型路由到正确的模块处理器,并将任何已注册的查询路由到正确的查询处理器。示例应用还会在这一步之后注册一个 AutoCLI 查询服务。 注册 AutoCLI 查询服务后,CLI 就可以自动探查模块选项,而不需要为每个模块单独编写 CLI 命令样板代码。关于这些服务如何向客户端暴露,参见CLI、gRPC 和 REST。

区块提案与投票扩展处理器

BaseApp 为 ABCI 2.0 的提案阶段暴露了四个处理器:SetPrepareProposal、SetProcessProposal、SetExtendVoteHandler 和 SetVerifyVoteExtensionHandler。它们都有合理的默认实现。需要自定义行为的链会在模块管理器配置完成后,于 app.go 中接入自己的处理器:
app.SetPrepareProposal(myPrepareProposalHandler)
app.SetProcessProposal(myProcessProposalHandler)
关于每个处理器具体作用的完整说明,参见区块提案与投票扩展。

挂载存储并设置钩子

完成路由配置后,就会挂载存储,并设置应用的生命周期钩子:
// initialize stores
app.MountKVStores(keys)

// initialize BaseApp
app.SetInitChainer(app.InitChainer)
app.SetPreBlocker(app.PreBlocker())
app.SetBeginBlocker(app.BeginBlocker)
app.SetEndBlocker(app.EndBlocker)
app.setAnteHandler(txConfig)
MountKVStores 会将每个 key 注册到 BaseApp 的 CommitMultiStore 中。这些钩子会委托给 ModuleManager:
func (app *ExampleApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) {
	return app.ModuleManager.BeginBlock(ctx)
}
EndBlocker 也以同样方式进行委托,而 InitChainer 则会在解码 genesis.json 后委托给 ModuleManager.InitGenesis。AnteHandler 会单独配置,因为它依赖 TxConfig:
func (app *ExampleApp) setAnteHandler(txConfig client.TxConfig) {
	anteHandler, err := ante.NewAnteHandler(
		ante.HandlerOptions{
			AccountKeeper:   app.AccountKeeper,
			BankKeeper:      app.BankKeeper,
			SignModeHandler: txConfig.SignModeHandler(),
			SigGasConsumer:  ante.DefaultSigVerificationGasConsumer,
		},
	)
	if err != nil {
		panic(err)
	}
	app.SetAnteHandler(anteHandler)
}
AnteHandler 会在交易中的任何消息执行之前运行。它会校验签名、验证并递增账户序列号、扣除费用并计量 gas。如果它失败,交易会在任何模块逻辑运行之前被拒绝。 还可以通过 app.SetPostHandler 注册一个 PostHandler。它会在交易中的所有消息执行完之后运行(无论这些消息是否成功),运行在同一个状态分支中,并且如果它失败也会被回滚。SDK 默认的 PostHandler 链非常精简。若想更深入了解 AnteHandler 在交易执行中的位置,参见AnteHandler。

使用 LoadLatestVersion 完成封闭

NewExampleApp 的最后一步是加载最新一次已提交的状态:
if loadLatest {
	if err := app.LoadLatestVersion(); err != nil {
		panic(fmt.Errorf("error loading last version: %w", err))
	}
}
LoadLatestVersion 会调用 storeLoader 从数据库中加载最新已提交的存储状态,然后调用 Init;Init 会校验必需组件是否已完成配置、初始化检查状态,并将 BaseApp.sealed 设置为 true。在此之后调用任何 setter 都会触发 panic。这保证了所有组装工作都必须在应用开始对外提供请求服务之前完成。 在首次启动时,CometBFT 会调用 InitChain,从而触发 InitChainer,再由它调用 ModuleManager.InitGenesis,根据 genesis.json 填充每个模块的状态。

整体如何协同工作

Cosmos SDK 链会被组装进一个统一的构造函数中,该函数返回一条已完成全部接线、可直接运行的链。每一步都建立在前一步的基础之上:
NewBaseApp
  ↓
NewKVStoreKeys     → 每个模块一个 key
  ↓
NewKeeper(key, ...)  → 每个模块一个 keeper,依赖关系显式接线
  ↓
NewManager(modules)  → 模块管理器持有所有 AppModule 实例
  ↓
SetOrder*(...)       → 配置钩子与 genesis 执行顺序
  ↓
RegisterServices(configurator)  → 将 MsgServer 和 QueryServer 接入 BaseApp 路由器
  ↓
MountKVStores(keys)  → 将模块存储挂载到 CommitMultiStore
  ↓
SetInitChainer / SetPreBlocker / SetBeginBlocker / SetEndBlocker / SetAnteHandler
  ↓
LoadLatestVersion    → 封闭 BaseApp,准备对外提供服务
在运行时,CometBFT 通过 ABCI 驱动应用。每一次 ABCI 调用都会通过 BaseApp 分发:
  • InitChain 调用 InitChainer,后者运行 ModuleManager.InitGenesis。
  • FinalizeBlock 调用 PreBlocker、BeginBlocker、每笔交易的 AnteHandler 和消息处理器,最后调用 EndBlocker。
  • CheckTx 通过 AnteHandler 校验交易;如果通过,就写入内部的 CheckTx 状态。
  • Commit 持久化最终确定的区块状态。
模块之间永远不会直接互相调用。它们通过初始化时接好的 keeper 接口进行交互,并通过由 ModuleManager 按固定声明顺序协调的钩子参与区块生命周期。关于 ABCI 调用如何流经应用,参见BaseApp 概览;关于单个模块如何组织,参见模块简介。下一节CLI、gRPC 和 REST API将说明在这些接线完成后,客户端如何与链进行交互。
app.go is where an application is assembled into a working chain. It creates the BaseApp instance that talks to CometBFT, allocates store keys, initializes keepers, registers modules, configures execution ordering, mounts stores, and sets lifecycle hooks and the AnteHandler. Finally, it seals the application with LoadLatestVersion. The result is a single constructor, NewExampleApp, that returns a fully wired, ready-to-run chain. Most examples on this page come from the counter module example in the example repo, where x/counter is wired into a fuller chain. The minimal counter module example shows the smaller app.go delta needed to add x/counter to a stripped-down app. See Step 10: Wire into app.go in the Build a Module tutorial.

What app.go does

app.go performs a one-time, ordered initialization of the entire chain:
1. Create BaseApp and codecs
2. Allocate store keys
3. Initialize keepers
4. Create the ModuleManager
5. Configure execution ordering
6. Register module services
7. Mount KV stores
8. Set lifecycle hooks (InitChainer, PreBlocker, BeginBlocker, EndBlocker, AnteHandler)
9. Load latest version
This sequence is strict:
  • Keepers require store keys, so keys come first.
  • The ModuleManager depends on keepers, so modules come after keeper construction.
  • Lifecycle hooks depend on the ModuleManager, so hook wiring comes later.
  • LoadLatestVersion seals BaseApp, so it runs last.

The app struct

The application struct embeds BaseApp and holds all keepers and the ModuleManager:
type ExampleApp struct {
	*baseapp.BaseApp
	appCodec          codec.Codec
	interfaceRegistry codectypes.InterfaceRegistry

	keys map[string]*storetypes.KVStoreKey

	// representative keepers
	AccountKeeper         authkeeper.AccountKeeper
	BankKeeper            bankkeeper.Keeper
	ConsensusParamsKeeper consensusparamkeeper.Keeper
	CounterKeeper         *counterkeeper.Keeper

	// application wiring helpers
	ModuleManager      *module.Manager
	BasicModuleManager module.BasicManager
	configurator module.Configurator
}
Embedding *baseapp.BaseApp gives ExampleApp the full BaseApp interface: ABCI methods, message and query routers, store management, and lifecycle hooks. The keeper fields are exported so test code and CLI helpers can reference them. The keys map holds the KV store keys allocated during initialization. The real example app includes additional keepers and helper fields; this excerpt shows the part of the struct that matters for understanding the wiring pattern.

Creating BaseApp

NewExampleApp begins by setting up codecs and creating the BaseApp instance:
appCodec := codec.NewProtoCodec(interfaceRegistry)
txConfig := authtx.NewTxConfig(appCodec, authtx.DefaultSignModes)

bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseAppOptions...)
bApp.SetVersion(version.Version)
bApp.SetInterfaceRegistry(interfaceRegistry)
bApp.SetTxEncoder(txConfig.TxEncoder())
baseapp.NewBaseApp creates the BaseApp with a name, logger, database, and TxDecoder. The TxDecoder is how BaseApp turns raw transaction bytes from CometBFT into an sdk.Tx it can inspect and route. Additional functional options (baseAppOptions) let callers configure pruning, minimum gas prices, chain ID, and optimistic execution without modifying NewExampleApp directly. The full example app also wires legacy Amino support, tracing, and interface registration around this excerpt. See BaseApp Overview for a fuller description of its fields and behavior.

Allocating store keys

Each module that persists state needs a dedicated KV store key. All keys are allocated together before any keeper is created:
keys := storetypes.NewKVStoreKeys(
	authtypes.StoreKey,
	banktypes.StoreKey,
	stakingtypes.StoreKey,
	distrtypes.StoreKey,
	slashingtypes.StoreKey,
	govtypes.StoreKey,
	consensusparamtypes.StoreKey,
	countertypes.StoreKey,
)
Each module defines its store key name as a string constant in types/keys.go (for example, countertypes.StoreKey = "counter" — see Step 4: Types in the Build a Module tutorial). NewKVStoreKeys takes those names and allocates a *storetypes.KVStoreKey for each one. Keys are passed to keeper constructors and later mounted on the CommitMultiStore via MountKVStores. No two modules share a key; that isolation is what keeps module state separate.

Initializing keepers

Each keeper is initialized with its store key, codec, and any dependencies on other keepers. ConsensusParamsKeeper is initialized first because it must call bApp.SetParamStore before any other keeper is created:
app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper(...)
bApp.SetParamStore(app.ConsensusParamsKeeper.ParamsStore)

app.AccountKeeper = authkeeper.NewAccountKeeper(...)
app.BankKeeper = bankkeeper.NewBaseKeeper(..., app.AccountKeeper, ...)
app.CounterKeeper = counterkeeper.NewKeeper(
	runtime.NewKVStoreService(keys[countertypes.StoreKey]),
	appCodec,
	app.BankKeeper,
)
runtime.NewKVStoreService(key) wraps the raw store key in a service interface that keepers use to open their store from a context. This keeps keepers from holding direct references to the underlying store. Instead, they retrieve it at runtime from the context passed into each method. The keeper initialization order matters: BankKeeper receives app.AccountKeeper as an argument, so AccountKeeper must be initialized first. The same dependency ordering applies throughout. The counter module example also passes app.BankKeeper into counterkeeper.NewKeeper, showing how custom modules depend on existing module services (see Expected keepers and fee collection in the Full Counter Module Walkthrough). Where modules are interdependent, hooks connect them after both keepers exist:
app.StakingKeeper.SetHooks(
	stakingtypes.NewMultiStakingHooks(
		app.DistrKeeper.Hooks(),
		app.SlashingKeeper.Hooks(),
	),
)
The authority address passed to most keepers (authtypes.NewModuleAddress(govtypes.ModuleName).String()) is the address that is allowed to call privileged messages such as MsgUpdateParams. Governance controls parameter changes by sending messages from the governance module account. See Params for how this pattern works.

Registering modules

After all keepers are initialized, the module manager is created with every module the application uses:
app.ModuleManager = module.NewManager(
	auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, nil),
	bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, nil),
	consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper),
	counter.NewAppModule(appCodec, app.CounterKeeper),
	// ...other modules...
)
module.NewManager takes a list of AppModule implementations. Each AppModule wraps a keeper and satisfies the interfaces the ModuleManager uses: genesis, block hooks, message and query service registration, and simulation support. The real example app includes the full built-in module set around x/counter — see Step 10: Wire into app.go for a walkthrough of module registration; this excerpt shows the basic registration pattern. The BasicModuleManager is then derived from the ModuleManager for codec registration and default genesis handling.

Module Manager

The ModuleManager is the application’s registry of modules. It holds references to all AppModule instances and coordinates their participation in the block lifecycle. When BaseApp fires a lifecycle hook (PreBlock, BeginBlock, EndBlock, InitGenesis), it delegates to the ModuleManager, which calls each module’s corresponding method in the configured order. The ModuleManager is also responsible for service registration: it iterates all modules and calls each module’s RegisterServices to register MsgServer and QueryServer implementations with BaseApp’s routers. For the execution-model view, see Module Manager in BaseApp.

Execution ordering

The order in which modules run their block hooks and genesis initialization matters. Some modules depend on others having already updated state. Ordering is configured explicitly after the module manager is created:
app.ModuleManager.SetOrderPreBlockers(
	authtypes.ModuleName,
)
app.ModuleManager.SetOrderBeginBlockers(
	distrtypes.ModuleName,
	slashingtypes.ModuleName,
	stakingtypes.ModuleName,
	countertypes.ModuleName,
	genutiltypes.ModuleName,
)
app.ModuleManager.SetOrderEndBlockers(
	banktypes.ModuleName,
	govtypes.ModuleName,
	stakingtypes.ModuleName,
	countertypes.ModuleName,
	genutiltypes.ModuleName,
)
Genesis initialization order is separate and equally important:
genesisModuleOrder := []string{
	authtypes.ModuleName,
	banktypes.ModuleName,
	distrtypes.ModuleName,
	stakingtypes.ModuleName,
	slashingtypes.ModuleName,
	govtypes.ModuleName,
	consensusparamtypes.ModuleName,
	vestingtypes.ModuleName,
	countertypes.ModuleName,
	genutiltypes.ModuleName,
}
app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...)
app.ModuleManager.SetOrderExportGenesis(exportModuleOrder...)
SetOrderExportGenesis controls the order modules serialize their state when the chain is exported to a genesis file, for example during a hard fork or when creating a snapshot-based testnet. The export order can differ from the init genesis order; in the example chain they use different orderings. The comments in the example app explain the reasoning: genutil must run after staking so that staking pools are initialized before genesis transactions are processed, and after auth so that it can access auth parameters. Each hook type has its own ordering constraint:
  • PreBlock: runs before BeginBlock. Used for upgrades and consensus parameter changes that must take effect before the block begins.
  • BeginBlock: runs at the start of each block. Used for per-block housekeeping such as minting inflation rewards and distributing staking rewards.
  • EndBlock: runs after all transactions in the block. Used for logic that depends on cumulative block state, such as tallying governance votes or recalculating validator power.
  • InitGenesis: runs once at chain start, populating each module’s store from genesis.json.
For a worked example of implementing these hooks in a custom module, see BeginBlock and EndBlock in the Full Counter Module Walkthrough.

Routing setup

After execution ordering is configured, module services are registered with BaseApp’s routers:
app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
err := app.ModuleManager.RegisterServices(app.configurator)
RegisterServices iterates all modules and calls each module’s RegisterServices(cfg) method. Each module uses the configurator to register its MsgServer with the message router and its QueryServer with the gRPC query router. After this step, BaseApp can route any registered message type to the correct module handler, and any registered query to the correct query handler. The example app also registers an AutoCLI query service after this step. The AutoCLI query service registration lets the CLI introspect module options without requiring per-module CLI command boilerplate. For how these services are exposed to clients, see CLI, gRPC, and REST.

Block proposal and vote extension handlers

BaseApp exposes four handlers for the ABCI 2.0 proposal phase: SetPrepareProposal, SetProcessProposal, SetExtendVoteHandler, and SetVerifyVoteExtensionHandler. All have sensible defaults. Chains that need custom behavior wire their handlers in app.go after the module manager is configured:
app.SetPrepareProposal(myPrepareProposalHandler)
app.SetProcessProposal(myProcessProposalHandler)
For a full explanation of what each handler does, see Block proposal and vote extensions.

Mounting stores and setting hooks

With routing configured, stores are mounted and the application’s lifecycle hooks are set:
// initialize stores
app.MountKVStores(keys)

// initialize BaseApp
app.SetInitChainer(app.InitChainer)
app.SetPreBlocker(app.PreBlocker())
app.SetBeginBlocker(app.BeginBlocker)
app.SetEndBlocker(app.EndBlocker)
app.setAnteHandler(txConfig)
MountKVStores registers each key with BaseApp’s CommitMultiStore. The hooks delegate to the ModuleManager:
func (app *ExampleApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) {
	return app.ModuleManager.BeginBlock(ctx)
}
EndBlocker delegates in the same way, and InitChainer delegates to ModuleManager.InitGenesis after decoding genesis.json. The AnteHandler is configured separately because it takes a TxConfig dependency:
func (app *ExampleApp) setAnteHandler(txConfig client.TxConfig) {
	anteHandler, err := ante.NewAnteHandler(
		ante.HandlerOptions{
			AccountKeeper:   app.AccountKeeper,
			BankKeeper:      app.BankKeeper,
			SignModeHandler: txConfig.SignModeHandler(),
			SigGasConsumer:  ante.DefaultSigVerificationGasConsumer,
		},
	)
	if err != nil {
		panic(err)
	}
	app.SetAnteHandler(anteHandler)
}
The AnteHandler runs before any message in a transaction executes. It verifies signatures, validates and increments the account sequence number, deducts fees, and meters gas. If it fails, the transaction is rejected before any module logic runs. A PostHandler can also be registered with app.SetPostHandler. It runs after all messages in a transaction execute (regardless of whether they succeeded), in the same state branch, and is reverted if it fails. The SDK’s default PostHandler chain is minimal. For a deeper look at how the AnteHandler fits into transaction execution, see AnteHandler.

Sealing with LoadLatestVersion

The final step in NewExampleApp is loading the latest committed state:
if loadLatest {
	if err := app.LoadLatestVersion(); err != nil {
		panic(fmt.Errorf("error loading last version: %w", err))
	}
}
LoadLatestVersion calls storeLoader to load the latest committed store state from the database, then calls Init, which validates that required components are configured, initializes the check state, and sets BaseApp.sealed to true. Any setter called after this point panics. This enforces that all wiring happens before the application starts serving requests. On first launch, CometBFT calls InitChain which triggers InitChainer, which calls ModuleManager.InitGenesis to populate each module’s state from genesis.json.

How everything fits together

A Cosmos SDK chain is assembled into a single constructor function that returns a fully wired, ready-to-run chain. Each step builds on the previous:
NewBaseApp
  ↓
NewKVStoreKeys     → one key per module
  ↓
NewKeeper(key, ...)  → one keeper per module, dependencies wired explicitly
  ↓
NewManager(modules)  → module manager holds all AppModule instances
  ↓
SetOrder*(...)       → configure hook and genesis execution ordering
  ↓
RegisterServices(configurator)  → wire MsgServer and QueryServer into BaseApp routers
  ↓
MountKVStores(keys)  → attach module stores to CommitMultiStore
  ↓
SetInitChainer / SetPreBlocker / SetBeginBlocker / SetEndBlocker / SetAnteHandler
  ↓
LoadLatestVersion    → seal BaseApp, ready to serve
At runtime, CometBFT drives the application through ABCI. Each ABCI call dispatches through BaseApp:
  • InitChain calls InitChainer, which runs ModuleManager.InitGenesis.
  • FinalizeBlock calls PreBlocker, BeginBlocker, each transaction’s AnteHandler and message handlers, then EndBlocker.
  • CheckTx validates a transaction through the AnteHandler and writes to the internal CheckTx state if it passes.
  • Commit persists the finalized block state.
Modules never call each other directly. They interact through keeper interfaces wired at initialization time, and they participate in the block lifecycle through hooks that the ModuleManager coordinates in a fixed, declared order. See BaseApp Overview for how ABCI calls flow through the application, and Intro to Modules for how individual modules are structured. The next section, CLI, gRPC, and REST API, explains how clients interact with the chain once that wiring is in place.