- CometBFT(共识引擎)— 对区块进行排序并提议区块
- ABCI(应用区块链接口)— CometBFT 用来与 Cosmos SDK 应用通信的协议
- SDK 应用(
BaseApp+ 模块)— 执行交易的确定性状态机 - Protobuf schema — 定义交易、消息、状态和查询类型
ABCI 概览
CometBFT 和 SDK 应用是两个相互独立的进程,各自承担不同职责。 ABCI(应用区块链接口)是连接两者的协议:CometBFT 通过调用应用上的 ABCI 方法来驱动区块生命周期的各个阶段,应用则对此作出响应。BaseApp 是 SDK 对 ABCI 接口的实现。它接收来自 CometBFT 的这些调用,并协调各模块之间的执行。模块接入 BaseApp,并在相应阶段执行自身逻辑。
InitChain(仅创世时)
InitChain 只会在链第一次启动时运行一次。BaseApp 会加载 genesis.json,该文件定义了链的初始状态,然后调用每个模块的 InitGenesis 来填充其存储。初始验证者集合也会在此建立。创世初始化发生在第一个区块开始之前。
关于 genesis.json 如何变成模块状态,参见 创世与链初始化。
CheckTx 与 mempool
在交易能够进入区块之前,它会经过CheckTx:
CheckTx 期间,SDK 应用的 BaseApp 会解码交易、验证签名和序列号、校验费用与 gas,并执行基础的消息校验。
关于账户序列模型,参见 账户。关于 gas 计量和费用相关的执行细节,参见 执行上下文、Gas 与事件。
如果验证失败,交易会被拒绝。如果通过验证,它就会进入 mempool。mempool 是节点内存中保存已验证交易的池,这些交易正在等待被纳入区块。
已验证交易会在 mempool 中等待,直到 CometBFT 为下一轮选出区块提议者。
PrepareProposal
每一轮中,CometBFT 会选择一个验证者来提议区块。PrepareProposal 只会在该验证者上被调用。BaseApp 会从 mempool 中选择交易,遵守区块的 MaxTxBytes 和 MaxGas 限制,并返回最终的交易列表。关于此处理器在何处配置,参见 区块提议与投票扩展。
ProcessProposal
当其他验证者收到提议的区块后,CometBFT 会调用ProcessProposal。BaseApp 会验证每笔交易,并返回 ACCEPT 或 REJECT。此时不会写入状态。一旦超过三分之二的投票权接受该区块并达成共识,CometBFT 就会调用 FinalizeBlock。关于这些处理器在执行模型中的视角,参见 区块提议与投票扩展。
FinalizeBlock
CometBFT 每个区块调用一次FinalizeBlock。在 FinalizeBlock 内部,BaseApp 会按顺序运行以下阶段:
PreBlock
PreBlock 在 BeginBlock 之前运行,通常用于那些必须在区块开始前影响共识关键状态的逻辑,例如激活链升级或修改共识参数。由于这些变更需要在任何区块逻辑运行之前生效,因此不能放在 BeginBlock 内进行。模块可以通过其 AppModule 上的 HasPreBlocker 扩展接口来实现这一点(通常位于 x/<module>/module.go),应用的 ModuleManager 会在 FinalizeBlock 期间调用所有已注册的 PreBlocker。
如果某个 PreBlocker 修改了共识参数,它会通过在其 ResponsePreBlock 中返回 ConsensusParamsChanged=true 来发出信号。随后,BaseApp 会在继续进入 BeginBlock 之前,刷新当前上下文中的共识参数:
BeginBlock
BeginBlock 在 PreBlock 之后运行,处理那些必须在任何交易执行之前发生、且与区块中具体交易无关的每区块维护逻辑。常见用途包括铸造通胀奖励、分发质押奖励,以及重置每区块状态。模块通过 x/<module>/module.go 中的 BeginBlock 函数实现这一点。由于 BeginBlock 和 EndBlock 会在每个区块上运行,这些钩子中的复杂或高开销逻辑可能会拖慢区块执行,因此应保持其工作尽量轻量。
交易执行
在BeginBlock 之后,BaseApp 会遍历区块中的每笔交易,并通过一个固定流水线来执行。
第 1 步:AnteHandler
在 Cosmos SDK 链的 app.go 中配置的 AnteHandler,会对每笔交易首先运行。对于标准的有序交易,它会验证签名、检查序列号、扣除费用并计量 gas。完整的中间件模型参见 BaseApp。
如果 AnteHandler 失败,交易会中止,其消息不会被执行。
第 2 步:消息路由与执行
交易中的每条消息都会通过BaseApp 的 MsgServiceRouter 路由到相应模块的 protobuf Msg 服务。消息是模块特定的,通常定义在模块的 tx.proto 中。BaseApp 会将这些消息路由到模块已注册的 protobuf Msg 服务处理器,该处理器再调用模块的 MsgServer 实现。关于路由器在执行流水线中的作用,参见 消息路由。
MsgServer 包含该消息类型的执行逻辑。它会验证消息内容、应用业务规则并更新状态。状态通过模块的 keeper 进行读写,keeper 负责管理对模块 KV 存储的访问,并封装其存储键。模块简介 解释了 MsgServer 与 Keeper 如何划分职责。
消息会按照它们在交易中出现的顺序依次执行。
第 3 步:原子性
消息执行具有原子性:要么所有消息都成功,要么消息执行产生的写入一项也不会被提交。BaseApp 在内部使用缓存存储来实现这一点。在消息执行开始之前,AnteHandler 的副作用可能已经生效。
如果链启用了无序交易,则会绕过常规的序列号检查,重放保护将使用超时时间戳加无序 nonce 跟踪。关于面向客户端的流程,参见 生成无序交易。
EndBlock
EndBlock 在区块中的所有交易执行完毕后运行。它用于依赖区块累计状态的逻辑,例如在所有投票交易都处理完之后统计治理投票,或者在区块中的所有委托变更完成后重新计算验证者权重。模块通过 x/<module>/module.go 中的 EndBlock 函数实现这一点。
Commit
在FinalizeBlock 返回之后,CometBFT 会调用 Commit。这会将状态变更持久化到节点的本地磁盘。
确定性执行
在所有验证者之间,区块执行必须是确定性的。区块必须包含相同且顺序一致的交易,交易也必须使用规范的 protobuf 二进制编码。状态转换必须是确定性的,这可确保每个验证者在FinalizeBlock 期间计算出相同的 app hash,从而保证共识安全性。如果持有超过三分之一投票权的验证者在 app hash 上存在分歧,共识就会停止。
完整生命周期概览
在每个阶段运行的钩子(
AnteHandler、BeginBlocker、EndBlocker 和 InitChainer)会在任何区块执行之前,于你的链的 app.go 中完成注册。app.go 是配置层,用于将各个模块接入 BaseApp。BaseApp 实现了 ABCI,并负责协调执行流程。
- 交易在进入内存池之前,会先在
CheckTx中完成校验 PrepareProposal在提议者节点上运行,用于为区块构建最终交易集合ProcessProposal在所有验证者上运行,用于接受或拒绝提议的区块- 每个区块都在一次
FinalizeBlock调用中执行 - 在
FinalizeBlock内部:PreBlock→BeginBlock→ 交易 →EndBlock - 每笔交易都会经过
AnteHandler→ 消息路由 → 消息执行 - 单笔交易中的消息执行具有原子性:要么全部提交,要么全部不提交
FinalizeBlock会计算并返回应用哈希;Commit会将状态持久化到磁盘
In the Transactions, Messages, and Queries page, you learned that transactions are the actual mechanism that authorizes and executes logic on the chain. This page explains how transactions are validated, executed, and committed in the Cosmos SDK. Before building with the Cosmos SDK, it’s important to connect the high-level architecture from SDK Application Architecture with how blocks and transactions actually execute in code. The following components are essential for understanding the lifecycle of a transaction in the Cosmos SDK:
- CometBFT (consensus engine) — orders and proposes blocks
- ABCI (Application-Blockchain Interface) — the protocol CometBFT uses to talk to the Cosmos SDK application
- SDK application (
BaseApp+ modules) — the deterministic state machine that executes transactions - Protobuf schemas — define transactions, messages, state, and query types
ABCI overview
CometBFT and the SDK application are two separate processes with distinct responsibilities.- CometBFT handles consensus: ordering transactions, managing validators, and driving block production.
- The SDK application handles state: executing transactions and updating the chain’s data.
BaseApp is the SDK’s implementation of the ABCI interface. It receives these calls from CometBFT and orchestrates execution across modules. Modules plug into BaseApp and execute their logic during the appropriate phases.
InitChain (genesis only)
InitChain runs once when the chain starts for the first time. BaseApp loads genesis.json, which defines the chain’s initial state, and calls each module’s InitGenesis to populate its store. The initial validator set is established. Genesis runs before the first block begins.
For how genesis.json becomes module state, see Genesis and chain initialization.
CheckTx and the mempool
Before a transaction can enter a block, it goes throughCheckTx:
CheckTx, the SDK application’s BaseApp decodes the transaction, verifies signatures and sequences, validates fees and gas, and performs basic message validation.
For the account sequence model, see Accounts. For gas metering and fee-related execution details, see Execution Context, Gas, and Events.
If validation fails, the transaction is rejected. If it passes, it enters the mempool. The mempool is a node’s in-memory pool of validated transactions waiting to be included in a block.
Validated transactions wait in the mempool until CometBFT selects a block proposer for the next round.
PrepareProposal
Each round, CometBFT selects one validator to propose a block.PrepareProposal is called on that validator only. BaseApp selects transactions from the mempool respecting the block’s MaxTxBytes and MaxGas limits and returns the final transaction list. For where this handler is configured, see Block proposal and vote extensions.
ProcessProposal
Once the other validators receive the proposed block, CometBFT callsProcessProposal. BaseApp verifies each transaction and returns ACCEPT or REJECT. No state is written. Once more than two-thirds of voting power accepts the block and consensus is reached, CometBFT calls FinalizeBlock. For the execution-model view of these handlers, see Block proposal and vote extensions.
FinalizeBlock
CometBFT callsFinalizeBlock once per block. Inside FinalizeBlock, BaseApp runs these phases in order:
PreBlock
PreBlock runs before BeginBlock and is generally used for logic that must affect consensus-critical state before the block begins, such as activating a chain upgrade or modifying consensus parameters. Because these changes need to take effect before any block logic runs, they cannot happen inside BeginBlock. Modules may implement this via the HasPreBlocker extension interface on their AppModule (typically in x/<module>/module.go), and the application’s ModuleManager invokes all registered PreBlockers during FinalizeBlock.
If a PreBlocker modifies consensus parameters, it signals this by returning ConsensusParamsChanged=true in its ResponsePreBlock. BaseApp then refreshes the consensus params in the current context before proceeding to BeginBlock:
BeginBlock
BeginBlock runs after PreBlock and handles per-block housekeeping that must happen before any transactions execute, regardless of the transactions in the block. Common uses include minting inflation rewards, distributing staking rewards, and resetting per-block state. Modules implement this via the BeginBlock function in x/<module>/module.go. Because BeginBlock and EndBlock run on every block, complex or expensive logic in these hooks can slow block execution; keep their work lightweight.
Transaction execution
AfterBeginBlock, BaseApp iterates over each transaction in the block and runs it through a fixed pipeline.
Step 1: AnteHandler
Configured in a Cosmos SDK chain’s app.go, the AnteHandler runs first for every transaction. For standard ordered transactions, it verifies signatures, checks sequence numbers, deducts fees, and meters gas. See BaseApp for the full middleware model.
If the AnteHandler fails, the transaction aborts and its messages do not execute.
Step 2: Message routing and execution
Each message in a transaction is routed viaBaseApp’s MsgServiceRouter to the appropriate module’s protobuf Msg service. Messages are module-specific and typically defined in a module’s tx.proto. BaseApp routes these messages to the module’s registered protobuf Msg service handler, which calls the module’s MsgServer implementation. See Message routing for the router’s role in the execution pipeline.
The MsgServer contains the execution logic for that message type. It validates the message content, applies business rules, and updates state. State is read and written through the module’s keeper, which manages access to the module’s KV store and encapsulates its storage keys. Intro to Modules explains how MsgServer and Keeper divide responsibilities.
Messages execute sequentially in the order they appear in the transaction.
Step 3: Atomicity
Message execution is atomic: all messages succeed or none of the message execution writes are committed.BaseApp uses cached stores internally to implement this. AnteHandler side effects may already have been applied before message execution begins.
If the chain enables unordered transactions, the normal sequence check is bypassed and replay protection uses a timeout timestamp plus unordered nonce tracking. For the client-facing flow, see Generating an Unordered Transaction.
EndBlock
EndBlock runs after all transactions in the block have executed. It is used for logic that depends on the block’s cumulative state, like tallying governance votes after all vote transactions have been processed, or recalculating validator power after all delegation changes in the block. Modules implement this via the EndBlock function in x/<module>/module.go.
Commit
AfterFinalizeBlock returns, CometBFT calls Commit. This persists the state changes to the node’s local disk.
Deterministic execution
Across all validators, the block execution is deterministic. Blocks must contain the same ordered transactions, and transactions must use canonical protobuf binary encoding. State transitions must be deterministic, which ensures that every validator computes the same app hash duringFinalizeBlock, which guarantees consensus safety. If validators holding more than 1/3 of voting power disagree on the app hash, consensus halts.
Complete lifecycle overview
The hooks that run at each phase (the
AnteHandler, BeginBlocker, EndBlocker, and InitChainer) are registered in your chain’s app.go before any block executes. app.go is the configuration layer that wires modules into BaseApp.BaseApp implements ABCI and orchestrates execution.
- Transactions are validated in
CheckTxbefore entering the mempool PrepareProposalruns on the proposer to build the final tx set for the blockProcessProposalruns on all validators to accept or reject the proposed block- Each block is executed inside a single
FinalizeBlockcall - Within
FinalizeBlock:PreBlock→BeginBlock→ transactions →EndBlock - Each transaction runs through
AnteHandler→ message routing → message execution - Message execution within a transaction is atomic: all messages commit or none do
FinalizeBlockcomputes and returns the app hash;Commitpersists state to disk