BaseApp is the execution engine of every Cosmos SDK chain. It implements ABCI (Application Blockchain Interface), the protocol CometBFT uses to communicate with the application, and translates those calls into module execution, transaction processing, and state transitions.
Every Cosmos SDK chain embeds BaseApp. Your app.go creates a BaseApp instance, configures it with modules, keepers, and middleware, and the resulting struct is what CometBFT communicates with directly. BaseApp provides the base layer of execution infrastructure to your blockchain application. Without it, every chain would need to independently implement ABCI handling, signature verification, gas metering, message routing, block hook orchestration, and state commitment.
Architectural position
BaseApp sits between CometBFT and the modules:
BaseApp. BaseApp handles each call, delegating to registered lifecycle hooks and routing messages to the appropriate module handlers. Modules contain the business logic, and KVStores hold the resulting state.
Key fields
BaseApp is defined in baseapp/baseapp.go. It holds references to everything needed to run a chain:
BaseApp struct definition.
cms(CommitMultiStore): the root state store. All module substores are mounted here, and all state reads and writes during block execution pass through it.storeLoader: a function that opens and mounts the individual module stores at application startup.grpcQueryRouter: routes incoming gRPC queries to the correct module’s query handler.msgServiceRouter: routes each message in a transaction to the correct module’sMsgServerhandler.txDecoder: decodes raw transaction bytes from CometBFT into ansdk.Tx.anteHandler: runs before message execution to handle cross-cutting concerns: signature verification, sequence validation, and fee deduction.postHandler: optional middleware that runs after message execution — used for tasks such as tipping or post-execution state adjustments.sealed: set totrueafterLoadLatestVersionis called. Setter methods panic if called after sealing.
Initialization and sealing
BaseApp enforces a configuration lifecycle: setter methods must be called before LoadLatestVersion is invoked. When LoadLatestVersion runs, it validates required components, initializes the check state, and sets sealed to true. Any setter called after sealing panics. On first launch, CometBFT calls InitChain. It stores ConsensusParams from the genesis file — block gas limit, max block size, evidence rules — in the ParamStore, where they can later be adjusted via on-chain governance. It initializes all volatile states by branching the root store, sets the block gas meter to infinite so genesis transactions are not gas-constrained, and calls the application’s initChainer, which runs each module’s InitGenesis to populate initial state. How this shapes the structure of app.go is covered in the next section.
Transaction decoding
Transactions arrive from CometBFT as raw bytes. BeforeBaseApp can validate or execute them, it must decode them into the SDK’s transaction type using the TxDecoder:
BaseApp cannot inspect messages, run the AnteHandler, or route execution to the correct module.
Execution modes
BaseApp does not execute everything against the same mutable state. It maintains branched, copy-on-write views of the committed root state for different execution contexts:
CheckTx(ExecModeCheck): validates a transaction before it enters the mempool, without committing state.FinalizeBlock(ExecModeFinalize): executes transactions in a proposed block against a branched state that is committed at the end.PrepareProposal(ExecModePrepareProposal): runs when the node is the block proposer, assembling a candidate block. Executes against a branched state that is never committed.ProcessProposal(ExecModeProcessProposal): runs on every validator to validate an incoming proposal. Also executes against a branched state that is never committed.Simulate(ExecModeSimulate): runs a transaction for gas estimation without committing state.
The transaction execution pipeline
WhenBaseApp processes a transaction, it runs through a structured pipeline:
AnteHandler fails, message execution does not begin. If any message fails, message execution reverts atomically; all message writes commit or none do.
AnteHandler
The AnteHandler is middleware that runs before any message in a transaction executes. It verifies cryptographic signatures, validates and increments the account sequence number, deducts transaction fees, and sets up the gas meter for the transaction.
For the application wiring side, including SetAnteHandler, HandlerOptions, and constructor ordering, see Mounting stores and setting hooks in app.go.
If the AnteHandler fails, the transaction is rejected and its messages never execute. If the AnteHandler succeeds but a message later fails, the AnteHandler’s state writes, such as fee deduction and sequence increment for ordered transactions, are already flushed to finalizeBlockState and will be committed with the block. Fees are charged even for transactions whose messages fail.
Message routing
When a transaction contains messages,BaseApp routes each one to the appropriate module handler using the MsgServiceRouter.
- Registration: During app startup, each module calls
RegisterService, which registers its message handlers keyed by message type URL (e.g.,/cosmos.bank.v1beta1.MsgSend). - Lookup: At execution time,
Handlerlooks up the registered handler for the incoming message’s type URL. - Execution: The retrieved handler invokes the module’s
MsgServerimplementation, which validates inputs, applies business rules, and updates state through the keeper.
BaseApp is the neutral coordinator.
Queries
For read-only access to application state,BaseApp uses the GRPCQueryRouter to route incoming gRPC queries to the correct module query service. Queries bypass the transaction execution pipeline and directly read committed state. They do not go through the AnteHandler, do not consume gas in the same way, and do not mutate state.
Store management
BaseApp owns the CommitMultiStore that holds all module state. At app startup, each module registers its store key, and BaseApp mounts the corresponding store:
BaseApp creates a cached, copy-on-write view of the multistore. All writes during that transaction occur in the cache. If the transaction succeeds, the cache is flushed to the underlying store. If the transaction fails at any point, the cache is discarded and no state changes are applied.
CheckTx and mempool validation
Before a transaction reaches block execution, it goes throughCheckTx. BaseApp runs the AnteHandler in CheckTx mode to validate signatures, check sequence numbers, and verify fees. Each validator also enforces a configurable minGasPrices floor, and transactions offering less than the minimum gas price are rejected here as a spam protection measure. Transactions that fail CheckTx are rejected and do not enter the mempool.
CheckTx does not execute messages. It does run the AnteHandler, and if ante succeeds the resulting writes are persisted to BaseApp’s internal CheckTx state rather than to committed chain state. This is how the mempool tracks transaction validity before block execution. After each block commits, CometBFT triggers a recheck pass (ReCheckTx) that re-validates all pending mempool transactions against the new state, and any transactions that became invalid (for example, because their sequence number was consumed by a competing transaction) are evicted at this point.
Coordinating block execution
When CometBFT callsFinalizeBlock, BaseApp runs the full block execution pipeline in order:
PreBlock runs before any block logic. It handles changes that must take effect before the block begins, such as activating a chain upgrade or modifying consensus parameters.
BeginBlock runs after PreBlock and handles per-block housekeeping: minting inflation rewards, distributing staking rewards, resetting per-block counters.
Transactions execute sequentially in block order. Message execution for each transaction is atomic: if any message fails, the message execution branch reverts. AnteHandler side effects may already have been applied.
EndBlock runs after all transactions. It handles logic that depends on the block’s cumulative state — for example, tallying governance votes after all vote messages have been processed, or updating validator power after all delegation changes.
After FinalizeBlock completes, BaseApp computes and returns the app hash — the Merkle root of all committed state. See App hash for how it relates to the multistore and deterministic execution. When CometBFT subsequently calls Commit, BaseApp writes finalizeBlockState to the root store, resets checkState to the newly committed state, and clears finalizeBlockState to nil in preparation for the next block.
Module Manager
BaseApp exposes PreBlock, BeginBlock, and EndBlock as lifecycle hook points. Every standard SDK application wires these to a ModuleManager, which holds the full set of registered modules and their execution ordering. When a hook fires, ModuleManager iterates its ordered module list and calls each module’s corresponding hook in sequence. Ordering matters: some modules depend on others having already updated state before they run.
The app.go page shows how the application constructs the ModuleManager, wires it into BaseApp, and configures ordering in practice. See Module Manager in app.go.
Block proposal and vote extensions
ABCI 2.0 added a proposal phase that runs during consensus rounds, beforeFinalizeBlock executes. BaseApp exposes four handlers for this phase, with default implementations wired at construction:
PrepareProposal: called on the current block proposer to assemble a block from the mempool. The default selects transactions up to the block gas limit. Chains can override this to implement custom ordering, filtering, or injection of protocol-level transactions.ProcessProposal: called on every validator to validate an incoming proposal. The default accepts any structurally valid proposal. Chains that usePrepareProposalto inject data typically also override this to verify that data is present and valid.ExtendVote/VerifyVoteExtension: allow validators to attach arbitrary data to their precommit votes and verify other validators’ extensions. One major use case is oracle price feeds: validators inject off-chain data into consensus so it becomes available on-chain at block start.
app.go via SetPrepareProposal, SetProcessProposal, SetExtendVoteHandler, and SetVerifyVoteExtensionHandler. Chains that do not need custom behavior can leave the defaults in place.
Putting it all together
BaseApp is the execution engine of a Cosmos SDK chain:
PreBlock → BeginBlock → transactions → EndBlock), routes messages to module handlers via the MsgServiceRouter, routes queries via the GRPCQueryRouter, runs the AnteHandler before each transaction, and manages the multistore with copy-on-write caching for atomicity. State changes are committed at block end; validation and simulation run against branched state and never touch committed data.
The next section, app.go Overview, explains how BaseApp is instantiated, configured, and wired with modules to produce a complete, running chain.