From 7a178aa7d57030653db64d83e2115224da7a6d07 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Thu, 26 Feb 2026 12:23:55 -0500 Subject: [PATCH 01/36] Create lifecycle.mdx --- sdk/v0.53/learn/concepts/lifecycle.mdx | 245 +++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 sdk/v0.53/learn/concepts/lifecycle.mdx diff --git a/sdk/v0.53/learn/concepts/lifecycle.mdx b/sdk/v0.53/learn/concepts/lifecycle.mdx new file mode 100644 index 00000000..7f01e3f5 --- /dev/null +++ b/sdk/v0.53/learn/concepts/lifecycle.mdx @@ -0,0 +1,245 @@ +--- +title: Lifecycle of a Block and Transaction +--- + +Before building with the Cosmos SDK, it's important to connect the high-level architecture from [SDK Application Architecture](/sdk/v0.53/learn/intro/sdk-app-architecture) with how blocks and transactions actually execute in code. + +A Cosmos SDK application can be conceptually split into layers: + +- **CometBFT (consensus engine)** — orders and proposes blocks +- **ABCI (Application-Blockchain Interface)** — the protocol CometBFT uses to talk to the application +- **SDK application (BaseApp + modules)** — the deterministic state machine that executes transactions +- **Protobuf schemas** — define transactions, messages, state, and query types + +This page maps the block and transaction lifecycle back to those layers. + +## ABCI overview + +**BaseApp** is a struct in the Cosmos SDK that implements the ABCI interface. Modules plug into BaseApp and execute logic during these phases. + +``` +CometBFT + ↓ +ABCI: InitChain (once at genesis) + +For each submitted transaction (async, independent of blocks): + ↓ +ABCI: CheckTx → Mempool + +For every block: + ↓ +ABCI: FinalizeBlock + → PreBlock + → BeginBlock + → Execute transactions + → EndBlock + ↓ +ABCI: Commit +``` + +--- + +## InitChain (genesis only) + +`InitChain` runs once when the chain starts for the first time, loading the **genesis file** — a JSON document that defines the chain's initial state (balances, validators, module parameters, and other bootstrapping data). + +During this phase: + +- A Cosmos SDK chain's BaseApp initializes stores and in-memory resources +- Modules load state from the genesis file via `InitGenesis` +- The initial validator set is established + +Modules define genesis logic in: + +``` +x//module.go +``` + +Specifically: + +``` +func (am AppModule) InitGenesis(...) +func (am AppModule) ExportGenesis(...) +``` + +Genesis runs before the first block begins. + +--- + +## CheckTx and the mempool + +Before a transaction can enter a block, it goes through `CheckTx`: + +``` +User + ↓ +Node + ↓ +ABCI: CheckTx + ↓ +Mempool +``` + +Transactions are sent as raw **protobuf-encoded bytes**. + +During `CheckTx`, BaseApp: + +- Decodes the transaction +- Verifies signatures and sequences +- Validates fees and gas +- Performs basic message validation + +No state changes are committed during `CheckTx`. If validation fails, the transaction is rejected. If it passes, it enters the mempool. + +When a validator proposes a block, CometBFT selects transactions from the mempool, orders them, and reaches consensus. Once the block is finalized, CometBFT sends it to the application via `FinalizeBlock`. + +--- + +## FinalizeBlock + +CometBFT calls `FinalizeBlock` once per block. This is the only ABCI call that drives block execution. + +Inside `FinalizeBlock`, BaseApp runs these phases in order: + +### PreBlock and BeginBlock + +Before transactions execute, BaseApp calls two module-level hooks: + +1. **PreBlock** — optional, runs before BeginBlock (e.g., upgrade plan checks) +2. **BeginBlock** — start-of-block logic across all modules (inflation, rewards, bookkeeping) + +Modules implement these hooks in `x//module.go`. + +### Transaction execution + +After BeginBlock, 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` file, the AnteHandler runs first for every transaction: + +- Signature verification +- Sequence increment +- Fee deduction +- Gas metering + +``` +func AnteHandler(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) +``` + +If the AnteHandler fails, the transaction aborts and its messages do not execute. + +#### Step 2: Message routing and execution + +Each message in the transaction is routed to its module handler. Messages are defined in: + +``` +proto/cosmos//v1beta1/tx.proto +``` + +BaseApp routes them to handlers in: + +``` +x//keeper/msg_server.go +``` + +For example: + +``` +type MsgServer interface { + Send(context.Context, *MsgSend) (*MsgSendResponse, error) +} +``` + +Messages execute sequentially in the order they appear in the transaction. + +#### Step 3: Atomicity + +Transactions are atomic — all messages succeed or none are committed: + +``` +Tx + ├─ Msg 1 + ├─ Msg 2 + └─ Msg 3 +``` + +If any message fails: + +- All state changes from that transaction are discarded +- The transaction returns an error +- The next transaction in the block still executes + +BaseApp uses cached stores internally to implement this. + +### EndBlock + +After all transactions execute, BaseApp calls each module's EndBlock hook for end-of-block logic: validator set updates, governance tallying, and similar work. + +--- + +## Commit + +After `FinalizeBlock` returns, CometBFT calls `Commit`. + +BaseApp: + +- Persists KVStore changes +- Computes a new **app hash** +- Returns the app hash to CometBFT + +All validators must compute the same app hash. If they do not, consensus halts. + +--- + +## Deterministic execution + +Across all validators: + +- Blocks contain the same ordered transactions +- Transactions use canonical protobuf binary encoding +- State transitions are deterministic + +Determinism ensures that every validator computes the same app hash during `Commit`, which guarantees consensus safety. + +--- + +## Complete lifecycle overview + +``` +CometBFT + ↓ ABCI InitChain +BaseApp → x//InitGenesis + +For each submitted transaction (async): + ↓ ABCI CheckTx + → decode, verify, validate + → insert into mempool + +For every block: + ↓ ABCI FinalizeBlock + → x//PreBlock + → x//BeginBlock + For each tx (from mempool): + → AnteHandler + → Message routing + → Message execution (atomic) + → x//EndBlock + ↓ ABCI Commit +BaseApp commits KVStores and returns app hash +``` + +--- + +## Summary + +CometBFT drives block processing through ABCI. BaseApp implements ABCI and orchestrates execution. + +- Transactions are validated in `CheckTx` before entering the mempool +- Each block is executed inside a single `FinalizeBlock` call +- Within `FinalizeBlock`: PreBlock → BeginBlock → transactions → EndBlock +- Each transaction runs through AnteHandler → message routing → message execution +- Transactions are atomic: all messages commit or none do +- `Commit` persists state and returns the app hash + +Protobuf ensures canonical encoding so all validators interpret transactions identically. From a295e60ad2d37735247e778030b59cc62b61a49f Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:11:45 -0500 Subject: [PATCH 02/36] Update lifecycle.mdx --- sdk/v0.53/learn/concepts/lifecycle.mdx | 134 ++++++------------------- 1 file changed, 31 insertions(+), 103 deletions(-) diff --git a/sdk/v0.53/learn/concepts/lifecycle.mdx b/sdk/v0.53/learn/concepts/lifecycle.mdx index 7f01e3f5..3792f752 100644 --- a/sdk/v0.53/learn/concepts/lifecycle.mdx +++ b/sdk/v0.53/learn/concepts/lifecycle.mdx @@ -1,13 +1,15 @@ --- -title: Lifecycle of a Block and Transaction +title: Transaction Lifecycle --- -Before building with the Cosmos SDK, it's important to connect the high-level architecture from [SDK Application Architecture](/sdk/v0.53/learn/intro/sdk-app-architecture) with how blocks and transactions actually execute in code. +In the [Transactions, Messages, and Queries](/sdk/v0.53/learn/concepts/transactions) 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](/sdk/v0.53/learn/intro/sdk-app-architecture) with how blocks and transactions actually execute in code. A Cosmos SDK application can be conceptually split into layers: -- **CometBFT (consensus engine)** — orders and proposes blocks -- **ABCI (Application-Blockchain Interface)** — the protocol CometBFT uses to talk to the application +- **[CometBFT](/cometbft) (consensus engine)** — orders and proposes blocks +- **[ABCI](/sdk/v0.53/learn/intro/sdk-app-architecture#abci-application-blockchain-interface) (Application-Blockchain Interface)** — the protocol CometBFT uses to talk to the application - **SDK application (BaseApp + modules)** — the deterministic state machine that executes transactions - **Protobuf schemas** — define transactions, messages, state, and query types @@ -17,7 +19,7 @@ This page maps the block and transaction lifecycle back to those layers. **BaseApp** is a struct in the Cosmos SDK that implements the ABCI interface. Modules plug into BaseApp and execute logic during these phases. -``` +```go title="ABCI Flow" CometBFT ↓ ABCI: InitChain (once at genesis) @@ -37,35 +39,18 @@ ABCI: FinalizeBlock ABCI: Commit ``` ---- - ## InitChain (genesis only) -`InitChain` runs once when the chain starts for the first time, loading the **genesis file** — a JSON document that defines the chain's initial state (balances, validators, module parameters, and other bootstrapping data). +`InitChain` runs once when the chain starts for the first time. This involves loading a `genesis.json` file, which defines the chain's initial state (balances, validators, module parameters, and other bootstrapping data). Modules define genesis logic in their `module.go` file. Specifically, they implement the `InitGenesis` and `ExportGenesis` functions. During this phase: -- A Cosmos SDK chain's BaseApp initializes stores and in-memory resources +- BaseApp initializes stores and in-memory resources - Modules load state from the genesis file via `InitGenesis` - The initial validator set is established -Modules define genesis logic in: - -``` -x//module.go -``` - -Specifically: - -``` -func (am AppModule) InitGenesis(...) -func (am AppModule) ExportGenesis(...) -``` - Genesis runs before the first block begins. ---- - ## CheckTx and the mempool Before a transaction can enter a block, it goes through `CheckTx`: @@ -82,33 +67,25 @@ Mempool Transactions are sent as raw **protobuf-encoded bytes**. -During `CheckTx`, BaseApp: - -- Decodes the transaction -- Verifies signatures and sequences -- Validates fees and gas -- Performs basic message validation +{/* todo: link to encoding section above */} -No state changes are committed during `CheckTx`. If validation fails, the transaction is rejected. If it passes, it enters the mempool. +During `CheckTx`, BaseApp decodes the transaction, verifies signatures and sequences, validates fees and gas, and performs basic message validation. -When a validator proposes a block, CometBFT selects transactions from the mempool, orders them, and reaches consensus. Once the block is finalized, CometBFT sends it to the application via `FinalizeBlock`. +No state changes are committed during `CheckTx`. 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. ---- +When a validator proposes a block, CometBFT selects transactions from the mempool, orders them, and reaches consensus on the block. Once the block is finalized, CometBFT sends it to the application via `FinalizeBlock`. ## FinalizeBlock -CometBFT calls `FinalizeBlock` once per block. This is the only ABCI call that drives block execution. - -Inside `FinalizeBlock`, BaseApp runs these phases in order: +CometBFT calls `FinalizeBlock` once per block. Inside `FinalizeBlock`, BaseApp runs these phases in order: `PreBlock` → `BeginBlock` → transaction execution → `EndBlock`. -### PreBlock and BeginBlock +### PreBlock -Before transactions execute, BaseApp calls two module-level hooks: +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 implement this via the `PreBlock` function in `x//module.go`. -1. **PreBlock** — optional, runs before BeginBlock (e.g., upgrade plan checks) -2. **BeginBlock** — start-of-block logic across all modules (inflation, rewards, bookkeeping) +### BeginBlock -Modules implement these hooks in `x//module.go`. +BeginBlock runs after PreBlock and handles per-block housekeeping that must happen before any transactions execute, regardless of what transactions are 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.go`. ### Transaction execution @@ -116,40 +93,15 @@ After BeginBlock, BaseApp iterates over each transaction in the block and runs i #### Step 1: AnteHandler -Configured in a Cosmos SDK chain's `app.go` file, the AnteHandler runs first for every transaction: - -- Signature verification -- Sequence increment -- Fee deduction -- Gas metering - -``` -func AnteHandler(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) -``` +Configured in a Cosmos SDK chain's `app.go` file, the AnteHandler runs first for every transaction. It verifies the transaction's signature, increments the sequence number, deducts the fee, and meters the gas. If the AnteHandler fails, the transaction aborts and its messages do not execute. #### Step 2: Message routing and execution -Each message in the transaction is routed to its module handler. Messages are defined in: - -``` -proto/cosmos//v1beta1/tx.proto -``` +Each message in the transaction is routed to its module handler. Messages are module-specific and defined in the `proto/cosmos//v1beta1/tx.proto` file. BaseApp routes these messages to handlers in `x//keeper/msg_server.go`. -BaseApp routes them to handlers in: - -``` -x//keeper/msg_server.go -``` - -For example: - -``` -type MsgServer interface { - Send(context.Context, *MsgSend) (*MsgSendResponse, error) -} -``` +The message handler 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 is the object that owns exclusive access to the module's KV store. For example, when `x/bank` processes a `MsgSend`, the message handler calls the bank keeper to debit the sender and credit the recipient. Messages execute sequentially in the order they appear in the transaction. @@ -164,45 +116,19 @@ Tx └─ Msg 3 ``` -If any message fails: - -- All state changes from that transaction are discarded -- The transaction returns an error -- The next transaction in the block still executes - -BaseApp uses cached stores internally to implement this. +If any message fails, all state changes from that transaction are discarded and the transaction returns an error. Thenext transaction in the block still executes. BaseApp uses cached stores internally to implement this. ### EndBlock -After all transactions execute, BaseApp calls each module's EndBlock hook for end-of-block logic: validator set updates, governance tallying, and similar work. - ---- +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.go`. ## Commit -After `FinalizeBlock` returns, CometBFT calls `Commit`. - -BaseApp: - -- Persists KVStore changes -- Computes a new **app hash** -- Returns the app hash to CometBFT - -All validators must compute the same app hash. If they do not, consensus halts. - ---- +After `FinalizeBlock` returns, CometBFT calls `Commit`. This persists the state changes to the node's local disk and returns the app hash to CometBFT. All validators must compute the same app hash. If they do not, consensus halts. ## Deterministic execution -Across all validators: - -- Blocks contain the same ordered transactions -- Transactions use canonical protobuf binary encoding -- State transitions are deterministic - -Determinism ensures that every validator computes the same app hash during `Commit`, which guarantees consensus safety. - ---- +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 during `Commit`, which guarantees consensus safety. This is critical for the safety and liveness of the consensus protocol. ## Complete lifecycle overview @@ -218,7 +144,7 @@ For each submitted transaction (async): For every block: ↓ ABCI FinalizeBlock - → x//PreBlock + → PreBlock → x//BeginBlock For each tx (from mempool): → AnteHandler @@ -229,9 +155,10 @@ For every block: BaseApp commits KVStores and returns app hash ``` ---- - -## Summary + +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. Both are covered in detail in later sections. +{/* todo: add links to app.go and BaseApp sections */} + CometBFT drives block processing through ABCI. BaseApp implements ABCI and orchestrates execution. @@ -243,3 +170,4 @@ CometBFT drives block processing through ABCI. BaseApp implements ABCI and orche - `Commit` persists state and returns the app hash Protobuf ensures canonical encoding so all validators interpret transactions identically. + From d2ed14d4fa82f90c70144ddded1cd5f0c47d84b0 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:41:03 -0500 Subject: [PATCH 03/36] Update lifecycle.mdx --- sdk/v0.53/learn/concepts/lifecycle.mdx | 48 +++++++++++++------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/sdk/v0.53/learn/concepts/lifecycle.mdx b/sdk/v0.53/learn/concepts/lifecycle.mdx index 3792f752..49d06853 100644 --- a/sdk/v0.53/learn/concepts/lifecycle.mdx +++ b/sdk/v0.53/learn/concepts/lifecycle.mdx @@ -4,7 +4,7 @@ title: Transaction Lifecycle In the [Transactions, Messages, and Queries](/sdk/v0.53/learn/concepts/transactions) 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](/sdk/v0.53/learn/intro/sdk-app-architecture) with how blocks and transactions actually execute in code. +Before building with the Cosmos SDK, it's important to connect the high-level architecture from [SDK Application Architecture](/sdk/v0.53/learn/intro/sdk-app-architecture) with how blocks and transactions actually execute in code. A Cosmos SDK application can be conceptually split into layers: @@ -17,7 +17,7 @@ This page maps the block and transaction lifecycle back to those layers. ## ABCI overview -**BaseApp** is a struct in the Cosmos SDK that implements the ABCI interface. Modules plug into BaseApp and execute logic during these phases. +**BaseApp** is a struct in the Cosmos SDK that implements the ABCI interface. {/* cosmos/cosmos-sdk baseapp/baseapp.go:63 */} Modules plug into BaseApp and execute logic during these phases. ```go title="ABCI Flow" CometBFT @@ -41,12 +41,12 @@ ABCI: Commit ## InitChain (genesis only) -`InitChain` runs once when the chain starts for the first time. This involves loading a `genesis.json` file, which defines the chain's initial state (balances, validators, module parameters, and other bootstrapping data). Modules define genesis logic in their `module.go` file. Specifically, they implement the `InitGenesis` and `ExportGenesis` functions. +`InitChain` runs once when the chain starts for the first time. {/* cosmos/cosmos-sdk baseapp/abci.go:43 */} This involves loading a `genesis.json` file, which defines the chain's initial state (balances, validators, module parameters, and other bootstrapping data). Modules define genesis logic in their `module.go` file. Specifically, they implement the `InitGenesis` {/* cosmos/cosmos-sdk x/bank/module.go:152 */} and `ExportGenesis` {/* cosmos/cosmos-sdk x/bank/module.go:161 */} functions. During this phase: - BaseApp initializes stores and in-memory resources -- Modules load state from the genesis file via `InitGenesis` +- Modules load state from the genesis file via `InitGenesis` {/* cosmos/cosmos-sdk baseapp/abci.go:114 — InitChainer dispatches to each module's InitGenesis */} - The initial validator set is established Genesis runs before the first block begins. @@ -69,45 +69,45 @@ Transactions are sent as raw **protobuf-encoded bytes**. {/* todo: link to encoding section above */} -During `CheckTx`, BaseApp decodes the transaction, verifies signatures and sequences, validates fees and gas, and performs basic message validation. +During `CheckTx`, BaseApp decodes the transaction, verifies signatures and sequences, validates fees and gas, and performs basic message validation. {/* cosmos/cosmos-sdk baseapp/abci.go:375 — CheckTx calls RunTx, which orchestrates decode and ante handler execution */} -No state changes are committed during `CheckTx`. 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. +No state changes are committed during `CheckTx`. {/* cosmos/cosmos-sdk baseapp/baseapp.go:985 — message msCache.Write() is gated on execModeFinalize; messages don't execute in check mode (baseapp/baseapp.go:1017) */} 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. When a validator proposes a block, CometBFT selects transactions from the mempool, orders them, and reaches consensus on the block. Once the block is finalized, CometBFT sends it to the application via `FinalizeBlock`. ## FinalizeBlock -CometBFT calls `FinalizeBlock` once per block. Inside `FinalizeBlock`, BaseApp runs these phases in order: `PreBlock` → `BeginBlock` → transaction execution → `EndBlock`. +CometBFT calls `FinalizeBlock` once per block. {/* cosmos/cosmos-sdk baseapp/abci.go:943 */} Inside `FinalizeBlock`, BaseApp runs these phases in order: `PreBlock` → `BeginBlock` → transaction execution → `EndBlock`. {/* cosmos/cosmos-sdk baseapp/abci.go:766 — internalFinalizeBlock calls preBlock:835, beginBlock:842, executeTxsWithExecutor:870, endBlock:899 */} ### 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 implement this via the `PreBlock` function in `x//module.go`. +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 {/* cosmos/cosmos-sdk x/upgrade/module.go:162 */} or modifying consensus parameters. Because these changes need to take effect before any block logic runs, they cannot happen inside BeginBlock. Modules implement this via the `PreBlock` function in `x//module.go`. {/* cosmos/cosmos-sdk x/upgrade/module.go:162 — PreBlock implementation in upgrade module */} ### BeginBlock -BeginBlock runs after PreBlock and handles per-block housekeeping that must happen before any transactions execute, regardless of what transactions are 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.go`. +BeginBlock runs after PreBlock and handles per-block housekeeping that must happen before any transactions execute, regardless of what transactions are in the block. Common uses include minting inflation rewards, {/* cosmos/cosmos-sdk x/mint/module.go:157 */} distributing staking rewards, {/* cosmos/cosmos-sdk x/distribution/module.go:164 */} and resetting per-block state. Modules implement this via the `BeginBlock` function in `x//module.go`. {/* cosmos/cosmos-sdk x/mint/module.go:157 — BeginBlock implementation in mint module */} ### Transaction execution -After BeginBlock, BaseApp iterates over each transaction in the block and runs it through a fixed pipeline: +After BeginBlock, BaseApp iterates over each transaction in the block and runs it through a fixed pipeline. {/* cosmos/cosmos-sdk baseapp/abci.go:870 — executeTxsWithExecutor processes all transactions after beginBlock */} #### Step 1: AnteHandler -Configured in a Cosmos SDK chain's `app.go` file, the AnteHandler runs first for every transaction. It verifies the transaction's signature, increments the sequence number, deducts the fee, and meters the gas. +Configured in a Cosmos SDK chain's `app.go` file, the AnteHandler runs first for every transaction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:899 */} It verifies the transaction's signature, {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:400 */} increments the sequence number, {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:537 */} deducts the fee, {/* cosmos/cosmos-sdk x/auth/ante/fee.go:111 */} and meters the gas. {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:177 */} -If the AnteHandler fails, the transaction aborts and its messages do not execute. +If the AnteHandler fails, the transaction aborts and its messages do not execute. {/* cosmos/cosmos-sdk baseapp/baseapp.go:924 — returns error before reaching runMsgs if ante fails */} #### Step 2: Message routing and execution -Each message in the transaction is routed to its module handler. Messages are module-specific and defined in the `proto/cosmos//v1beta1/tx.proto` file. BaseApp routes these messages to handlers in `x//keeper/msg_server.go`. +Each message in the transaction is routed to its module handler. Messages are module-specific and defined in the `proto/cosmos//v1beta1/tx.proto` file. BaseApp routes these messages to handlers in `x//keeper/msg_server.go`. {/* cosmos/cosmos-sdk x/bank/keeper/msg_server.go:29 — MsgSend handler as representative example */} -The message handler 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 is the object that owns exclusive access to the module's KV store. For example, when `x/bank` processes a `MsgSend`, the message handler calls the bank keeper to debit the sender and credit the recipient. +The message handler 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 is the object that owns exclusive access to the module's KV store. For example, when `x/bank` processes a `MsgSend`, the message handler calls the bank keeper to debit the sender {/* cosmos/cosmos-sdk x/bank/keeper/send.go:240 — subUnlockedCoins debits fromAddr */} and credit the recipient. {/* cosmos/cosmos-sdk x/bank/keeper/send.go:245 — addCoins credits toAddr */} -Messages execute sequentially in the order they appear in the transaction. +Messages execute sequentially in the order they appear in the transaction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:1016 */} #### Step 3: Atomicity -Transactions are atomic — all messages succeed or none are committed: +Transactions are atomic — all messages succeed or none are committed: {/* cosmos/cosmos-sdk baseapp/baseapp.go:989 — msCache.Write() only called when err == nil and mode == execModeFinalize */} ``` Tx @@ -116,19 +116,19 @@ Tx └─ Msg 3 ``` -If any message fails, all state changes from that transaction are discarded and the transaction returns an error. Thenext transaction in the block still executes. BaseApp uses cached stores internally to implement this. +If any message fails, all state changes from that transaction are discarded and the transaction returns an error. Thenext transaction in the block still executes. BaseApp uses cached stores internally to implement this. {/* cosmos/cosmos-sdk baseapp/baseapp.go:664 — cacheTxContext creates a branched CacheMultiStore per 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.go`. +EndBlock runs after all transactions in the block have executed. {/* cosmos/cosmos-sdk baseapp/abci.go:899 */} 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. {/* cosmos/cosmos-sdk x/staking/module.go:181 */} Modules implement this via the `EndBlock` function in `x//module.go`. {/* cosmos/cosmos-sdk x/staking/module.go:181 — EndBlock implementation in staking module */} ## Commit -After `FinalizeBlock` returns, CometBFT calls `Commit`. This persists the state changes to the node's local disk and returns the app hash to CometBFT. All validators must compute the same app hash. If they do not, consensus halts. +After `FinalizeBlock` returns, CometBFT calls `Commit`. {/* cosmos/cosmos-sdk baseapp/abci.go:1044 */} This persists the state changes to the node's local disk. {/* cosmos/cosmos-sdk baseapp/abci.go:1062 — cms.Commit() writes all KV stores to disk */} All validators must compute the same app hash. If they do not, consensus halts. ## 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 during `Commit`, which guarantees consensus safety. This is critical for the safety and liveness of the consensus protocol. +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 during `FinalizeBlock`, {/* cosmos/cosmos-sdk baseapp/abci.go:1013 — res.AppHash = app.workingHash() set on ResponseFinalizeBlock, not ResponseCommit */} which guarantees consensus safety. This is critical for the safety and liveness of the consensus protocol. ## Complete lifecycle overview @@ -146,17 +146,17 @@ For every block: ↓ ABCI FinalizeBlock → PreBlock → x//BeginBlock - For each tx (from mempool): + For each tx (in the block): → AnteHandler → Message routing → Message execution (atomic) → x//EndBlock ↓ ABCI Commit -BaseApp commits KVStores and returns app hash +BaseApp commits KVStores ``` -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. Both are covered in detail in later sections. +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. Both are covered in detail in later sections. {/* todo: add links to app.go and BaseApp sections */} @@ -167,7 +167,7 @@ CometBFT drives block processing through ABCI. BaseApp implements ABCI and orche - Within `FinalizeBlock`: PreBlock → BeginBlock → transactions → EndBlock - Each transaction runs through AnteHandler → message routing → message execution - Transactions are atomic: all messages commit or none do -- `Commit` persists state and returns the app hash +- `FinalizeBlock` computes and returns the app hash; `Commit` persists state to disk Protobuf ensures canonical encoding so all validators interpret transactions identically. From e207b0d91ab5b98ee0f47d3b1ad861311dee243c Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:11:17 -0500 Subject: [PATCH 04/36] Update lifecycle.mdx --- sdk/v0.53/learn/concepts/lifecycle.mdx | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/sdk/v0.53/learn/concepts/lifecycle.mdx b/sdk/v0.53/learn/concepts/lifecycle.mdx index 49d06853..9b2503b7 100644 --- a/sdk/v0.53/learn/concepts/lifecycle.mdx +++ b/sdk/v0.53/learn/concepts/lifecycle.mdx @@ -41,13 +41,13 @@ ABCI: Commit ## InitChain (genesis only) -`InitChain` runs once when the chain starts for the first time. {/* cosmos/cosmos-sdk baseapp/abci.go:43 */} This involves loading a `genesis.json` file, which defines the chain's initial state (balances, validators, module parameters, and other bootstrapping data). Modules define genesis logic in their `module.go` file. Specifically, they implement the `InitGenesis` {/* cosmos/cosmos-sdk x/bank/module.go:152 */} and `ExportGenesis` {/* cosmos/cosmos-sdk x/bank/module.go:161 */} functions. +`InitChain` runs once when the chain starts for the first time. {/* cosmos/cosmos-sdk baseapp/abci.go:53 */} This involves loading a `genesis.json` file, which defines the chain's initial state (balances, validators, module parameters, and other bootstrapping data). Modules define genesis logic in their `module.go` file. Specifically, they implement the `InitGenesis` {/* cosmos/cosmos-sdk x/bank/module.go:152 */} and `ExportGenesis` {/* cosmos/cosmos-sdk x/bank/module.go:161 */} functions. During this phase: -- BaseApp initializes stores and in-memory resources -- Modules load state from the genesis file via `InitGenesis` {/* cosmos/cosmos-sdk baseapp/abci.go:114 — InitChainer dispatches to each module's InitGenesis */} -- The initial validator set is established +- BaseApp initializes stores and in-memory resources {/* cosmos/cosmos-sdk baseapp/abci.go:82 — app.stateManager.SetState initializes in-memory execution contexts for finalize and check modes */} +- Modules load state from the genesis file via `InitGenesis` {/* cosmos/cosmos-sdk baseapp/abci.go:124 — app.abciHandlers.InitChainer dispatches to each module's InitGenesis */} +- The initial validator set is established {/* cosmos/cosmos-sdk baseapp/abci.go:149 — ResponseInitChain returns validators to CometBFT; validator state is written by staking InitGenesis via initChainer */} Genesis runs before the first block begins. @@ -65,19 +65,19 @@ ABCI: CheckTx Mempool ``` -Transactions are sent as raw **protobuf-encoded bytes**. +Transactions are sent as raw **protobuf-encoded bytes**. {/* cosmos/cosmos-sdk baseapp/baseapp.go:97 — txDecoder sdk.TxDecoder field; req.Tx is []byte decoded at baseapp/baseapp.go:865 */} {/* todo: link to encoding section above */} -During `CheckTx`, BaseApp decodes the transaction, verifies signatures and sequences, validates fees and gas, and performs basic message validation. {/* cosmos/cosmos-sdk baseapp/abci.go:375 — CheckTx calls RunTx, which orchestrates decode and ante handler execution */} +During `CheckTx`, BaseApp decodes the transaction, verifies signatures and sequences, validates fees and gas, and performs basic message validation. {/* cosmos/cosmos-sdk baseapp/abci.go:367 — CheckTx calls RunTx, which orchestrates decode and ante handler execution */} -No state changes are committed during `CheckTx`. {/* cosmos/cosmos-sdk baseapp/baseapp.go:985 — message msCache.Write() is gated on execModeFinalize; messages don't execute in check mode (baseapp/baseapp.go:1017) */} 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. +No state changes are committed during `CheckTx`. {/* cosmos/cosmos-sdk baseapp/baseapp.go:990 — msCache.Write() at line 994 only executes when mode == execModeFinalize, not in check mode */} 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. When a validator proposes a block, CometBFT selects transactions from the mempool, orders them, and reaches consensus on the block. Once the block is finalized, CometBFT sends it to the application via `FinalizeBlock`. ## FinalizeBlock -CometBFT calls `FinalizeBlock` once per block. {/* cosmos/cosmos-sdk baseapp/abci.go:943 */} Inside `FinalizeBlock`, BaseApp runs these phases in order: `PreBlock` → `BeginBlock` → transaction execution → `EndBlock`. {/* cosmos/cosmos-sdk baseapp/abci.go:766 — internalFinalizeBlock calls preBlock:835, beginBlock:842, executeTxsWithExecutor:870, endBlock:899 */} +CometBFT calls `FinalizeBlock` once per block. {/* cosmos/cosmos-sdk baseapp/abci.go:973 */} Inside `FinalizeBlock`, BaseApp runs these phases in order: `PreBlock` → `BeginBlock` → transaction execution → `EndBlock`. {/* cosmos/cosmos-sdk baseapp/abci.go:792 — internalFinalizeBlock; preBlock:865, beginBlock:872, tx loop:900, endBlock:929 */} ### PreBlock @@ -89,25 +89,25 @@ BeginBlock runs after PreBlock and handles per-block housekeeping that must happ ### Transaction execution -After BeginBlock, BaseApp iterates over each transaction in the block and runs it through a fixed pipeline. {/* cosmos/cosmos-sdk baseapp/abci.go:870 — executeTxsWithExecutor processes all transactions after beginBlock */} +After BeginBlock, BaseApp iterates over each transaction in the block and runs it through a fixed pipeline. {/* cosmos/cosmos-sdk baseapp/abci.go:900 — executeTxsWithExecutor call in internalFinalizeBlock processes all transactions after beginBlock */} #### Step 1: AnteHandler -Configured in a Cosmos SDK chain's `app.go` file, the AnteHandler runs first for every transaction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:899 */} It verifies the transaction's signature, {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:400 */} increments the sequence number, {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:537 */} deducts the fee, {/* cosmos/cosmos-sdk x/auth/ante/fee.go:111 */} and meters the gas. {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:177 */} +Configured in a Cosmos SDK chain's `app.go` file, the AnteHandler runs first for every transaction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:904 */} It verifies the transaction's signature, {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:400 */} increments the sequence number, {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:537 */} deducts the fee, {/* cosmos/cosmos-sdk x/auth/ante/fee.go:111 */} and meters the gas. {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:219 */} -If the AnteHandler fails, the transaction aborts and its messages do not execute. {/* cosmos/cosmos-sdk baseapp/baseapp.go:924 — returns error before reaching runMsgs if ante fails */} +If the AnteHandler fails, the transaction aborts and its messages do not execute. {/* cosmos/cosmos-sdk baseapp/baseapp.go:929 — returns error before reaching runMsgs if ante fails */} #### Step 2: Message routing and execution -Each message in the transaction is routed to its module handler. Messages are module-specific and defined in the `proto/cosmos//v1beta1/tx.proto` file. BaseApp routes these messages to handlers in `x//keeper/msg_server.go`. {/* cosmos/cosmos-sdk x/bank/keeper/msg_server.go:29 — MsgSend handler as representative example */} +Each message in a transaction is routed via BaseApp's `MsgServiceRouter` to the appropriate module's protobuf `Msg` service. Messages are module-specific and typically defined in `proto/cosmos//v1beta1/tx.proto`. BaseApp routes these messages to the module's `MsgServer` implementation (commonly located at `x//keeper/msg_server.go`). {/* cosmos/cosmos-sdk x/bank/keeper/msg_server.go:29 — MsgSend handler as representative example */} -The message handler 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 is the object that owns exclusive access to the module's KV store. For example, when `x/bank` processes a `MsgSend`, the message handler calls the bank keeper to debit the sender {/* cosmos/cosmos-sdk x/bank/keeper/send.go:240 — subUnlockedCoins debits fromAddr */} and credit the recipient. {/* cosmos/cosmos-sdk x/bank/keeper/send.go:245 — addCoins credits toAddr */} +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. -Messages execute sequentially in the order they appear in the transaction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:1016 */} +Messages execute sequentially in the order they appear in the transaction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:1021 — for i, msg := range msgs loop in runMsgs */} #### Step 3: Atomicity -Transactions are atomic — all messages succeed or none are committed: {/* cosmos/cosmos-sdk baseapp/baseapp.go:989 — msCache.Write() only called when err == nil and mode == execModeFinalize */} +Transactions are atomic — all messages succeed or none are committed: {/* cosmos/cosmos-sdk baseapp/baseapp.go:994 — msCache.Write() only called when err == nil (line 989) and mode == execModeFinalize (line 990) */} ``` Tx @@ -116,11 +116,11 @@ Tx └─ Msg 3 ``` -If any message fails, all state changes from that transaction are discarded and the transaction returns an error. Thenext transaction in the block still executes. BaseApp uses cached stores internally to implement this. {/* cosmos/cosmos-sdk baseapp/baseapp.go:664 — cacheTxContext creates a branched CacheMultiStore per transaction */} +If any message fails, all state changes from that transaction are discarded and the transaction returns an error. Thenext transaction in the block still executes. BaseApp uses cached stores internally to implement this. {/* cosmos/cosmos-sdk baseapp/baseapp.go:662 — cacheTxContext creates a branched CacheMultiStore per transaction */} ### EndBlock -EndBlock runs after all transactions in the block have executed. {/* cosmos/cosmos-sdk baseapp/abci.go:899 */} 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. {/* cosmos/cosmos-sdk x/staking/module.go:181 */} Modules implement this via the `EndBlock` function in `x//module.go`. {/* cosmos/cosmos-sdk x/staking/module.go:181 — EndBlock implementation in staking module */} +EndBlock runs after all transactions in the block have executed. {/* cosmos/cosmos-sdk baseapp/abci.go:929 */} It is used for logic that depends on the block's cumulative state, like tallying governance votes after all vote transactions have been processed, {/* cosmos/cosmos-sdk x/gov/module.go:197 — EndBlock tallies proposals whose voting period has ended */} or recalculating validator power after all delegation changes in the block. {/* cosmos/cosmos-sdk x/staking/module.go:181 */} Modules implement this via the `EndBlock` function in `x//module.go`. {/* cosmos/cosmos-sdk x/staking/module.go:181 — EndBlock implementation in staking module */} ## Commit @@ -156,7 +156,7 @@ BaseApp commits KVStores ``` -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. Both are covered in detail in later sections. +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. Both are covered in detail in later sections. {/* todo: add links to app.go and BaseApp sections */} From 20aa36012d5833ac64718a7aafca6ae9f2f9812e Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:14:42 -0500 Subject: [PATCH 05/36] Update lifecycle.mdx --- sdk/v0.53/learn/concepts/lifecycle.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/v0.53/learn/concepts/lifecycle.mdx b/sdk/v0.53/learn/concepts/lifecycle.mdx index 9b2503b7..1438596b 100644 --- a/sdk/v0.53/learn/concepts/lifecycle.mdx +++ b/sdk/v0.53/learn/concepts/lifecycle.mdx @@ -93,7 +93,7 @@ After BeginBlock, BaseApp iterates over each transaction in the block and runs i #### Step 1: AnteHandler -Configured in a Cosmos SDK chain's `app.go` file, the AnteHandler runs first for every transaction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:904 */} It verifies the transaction's signature, {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:400 */} increments the sequence number, {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:537 */} deducts the fee, {/* cosmos/cosmos-sdk x/auth/ante/fee.go:111 */} and meters the gas. {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:219 */} +Configured in a Cosmos SDK chain's `app.go` file, the AnteHandler runs first for every transaction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:904 */} It verifies the transaction's signature, {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:400 */} increments the sequence number, {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:537 */} deducts the fee, {/* cosmos/cosmos-sdk x/auth/ante/fee.go:111 */} and meters the gas. {/* cosmos/cosmos-sdk x/auth/ante/setup.go:40 — SetUpContextDecorator sets the gas meter from the tx's declared gas limit */} If the AnteHandler fails, the transaction aborts and its messages do not execute. {/* cosmos/cosmos-sdk baseapp/baseapp.go:929 — returns error before reaching runMsgs if ante fails */} @@ -107,7 +107,7 @@ Messages execute sequentially in the order they appear in the transaction. {/* c #### Step 3: Atomicity -Transactions are atomic — all messages succeed or none are committed: {/* cosmos/cosmos-sdk baseapp/baseapp.go:994 — msCache.Write() only called when err == nil (line 989) and mode == execModeFinalize (line 990) */} +Transactions are atomic: all messages succeed or none are committed. {/* cosmos/cosmos-sdk baseapp/baseapp.go:994 — msCache.Write() only called when err == nil (line 989) and mode == execModeFinalize (line 990) */} ``` Tx @@ -116,7 +116,7 @@ Tx └─ Msg 3 ``` -If any message fails, all state changes from that transaction are discarded and the transaction returns an error. Thenext transaction in the block still executes. BaseApp uses cached stores internally to implement this. {/* cosmos/cosmos-sdk baseapp/baseapp.go:662 — cacheTxContext creates a branched CacheMultiStore per transaction */} +If any message fails, all state changes from that transaction are discarded and the transaction returns an error. The next transaction in the block is then executed. BaseApp uses cached stores internally to implement this. {/* cosmos/cosmos-sdk baseapp/baseapp.go:662 — cacheTxContext creates a branched CacheMultiStore per transaction */} ### EndBlock From 377c95a97811b462aa7ee702c372a4af72911f2d Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Fri, 27 Feb 2026 12:22:49 -0500 Subject: [PATCH 06/36] Update lifecycle.mdx --- sdk/v0.53/learn/concepts/lifecycle.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/v0.53/learn/concepts/lifecycle.mdx b/sdk/v0.53/learn/concepts/lifecycle.mdx index 1438596b..0c361082 100644 --- a/sdk/v0.53/learn/concepts/lifecycle.mdx +++ b/sdk/v0.53/learn/concepts/lifecycle.mdx @@ -69,7 +69,7 @@ Transactions are sent as raw **protobuf-encoded bytes**. {/* cosmos/cosmos-sdk b {/* todo: link to encoding section above */} -During `CheckTx`, BaseApp decodes the transaction, verifies signatures and sequences, validates fees and gas, and performs basic message validation. {/* cosmos/cosmos-sdk baseapp/abci.go:367 — CheckTx calls RunTx, which orchestrates decode and ante handler execution */} +During `CheckTx`, BaseApp decodes the transaction, verifies signatures and sequences, validates fees and gas, and performs basic message validation. {/* cosmos/cosmos-sdk baseapp/abci.go:367 — CheckTx calls RunTx, which orchestrates decode and ante handler execution */} `MsgServer` methods are not executed during `CheckTx`; only the AnteHandler and basic structural checks run. {/* cosmos/cosmos-sdk baseapp/baseapp.go:1022 — runMsgs breaks early when mode != execModeFinalize && mode != execModeSimulate */} No state changes are committed during `CheckTx`. {/* cosmos/cosmos-sdk baseapp/baseapp.go:990 — msCache.Write() at line 994 only executes when mode == execModeFinalize, not in check mode */} 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. @@ -81,7 +81,7 @@ CometBFT calls `FinalizeBlock` once per block. {/* cosmos/cosmos-sdk baseapp/abc ### 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 {/* cosmos/cosmos-sdk x/upgrade/module.go:162 */} or modifying consensus parameters. Because these changes need to take effect before any block logic runs, they cannot happen inside BeginBlock. Modules implement this via the `PreBlock` function in `x//module.go`. {/* cosmos/cosmos-sdk x/upgrade/module.go:162 — PreBlock implementation in upgrade module */} +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 {/* cosmos/cosmos-sdk x/upgrade/module.go:162 */} 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.go`), and the application's `ModuleManager` invokes all registered PreBlockers during `FinalizeBlock`. {/* cosmos/cosmos-sdk x/upgrade/module.go:162 — PreBlock implementation in upgrade module */} ### BeginBlock @@ -99,7 +99,7 @@ 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 via BaseApp's `MsgServiceRouter` to the appropriate module's protobuf `Msg` service. Messages are module-specific and typically defined in `proto/cosmos//v1beta1/tx.proto`. BaseApp routes these messages to the module's `MsgServer` implementation (commonly located at `x//keeper/msg_server.go`). {/* cosmos/cosmos-sdk x/bank/keeper/msg_server.go:29 — MsgSend handler as representative example */} +Each message in a transaction is routed via BaseApp's `MsgServiceRouter` to the appropriate module's protobuf `Msg` service. Messages are module-specific and typically defined in a module's `tx.proto` (often under `proto/cosmos//...`). BaseApp routes these messages to the module's registered protobuf `Msg` service handler, which calls the module's `MsgServer` implementation (commonly located at `x//keeper/msg_server.go`). {/* cosmos/cosmos-sdk x/bank/keeper/msg_server.go:29 — MsgSend handler as representative example */} 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. From 04e6c6fc55a359ccdef49c61eaba3a691e165e8a Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Fri, 27 Feb 2026 12:34:25 -0500 Subject: [PATCH 07/36] Update lifecycle.mdx --- sdk/v0.53/learn/concepts/lifecycle.mdx | 47 +++++++++++++------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/sdk/v0.53/learn/concepts/lifecycle.mdx b/sdk/v0.53/learn/concepts/lifecycle.mdx index 0c361082..88d9b953 100644 --- a/sdk/v0.53/learn/concepts/lifecycle.mdx +++ b/sdk/v0.53/learn/concepts/lifecycle.mdx @@ -17,7 +17,7 @@ This page maps the block and transaction lifecycle back to those layers. ## ABCI overview -**BaseApp** is a struct in the Cosmos SDK that implements the ABCI interface. {/* cosmos/cosmos-sdk baseapp/baseapp.go:63 */} Modules plug into BaseApp and execute logic during these phases. +**BaseApp** is a struct in the Cosmos SDK that implements the ABCI interface. Modules plug into BaseApp and execute logic during these phases. ```go title="ABCI Flow" CometBFT @@ -41,13 +41,13 @@ ABCI: Commit ## InitChain (genesis only) -`InitChain` runs once when the chain starts for the first time. {/* cosmos/cosmos-sdk baseapp/abci.go:53 */} This involves loading a `genesis.json` file, which defines the chain's initial state (balances, validators, module parameters, and other bootstrapping data). Modules define genesis logic in their `module.go` file. Specifically, they implement the `InitGenesis` {/* cosmos/cosmos-sdk x/bank/module.go:152 */} and `ExportGenesis` {/* cosmos/cosmos-sdk x/bank/module.go:161 */} functions. +`InitChain` runs once when the chain starts for the first time. This involves loading a `genesis.json` file, which defines the chain's initial state (balances, validators, module parameters, and other bootstrapping data). Modules define genesis logic in their `module.go` file. Specifically, they implement the `InitGenesis` and `ExportGenesis` functions. During this phase: -- BaseApp initializes stores and in-memory resources {/* cosmos/cosmos-sdk baseapp/abci.go:82 — app.stateManager.SetState initializes in-memory execution contexts for finalize and check modes */} -- Modules load state from the genesis file via `InitGenesis` {/* cosmos/cosmos-sdk baseapp/abci.go:124 — app.abciHandlers.InitChainer dispatches to each module's InitGenesis */} -- The initial validator set is established {/* cosmos/cosmos-sdk baseapp/abci.go:149 — ResponseInitChain returns validators to CometBFT; validator state is written by staking InitGenesis via initChainer */} +- BaseApp initializes stores and in-memory resources +- Modules load state from the genesis file via `InitGenesis` +- The initial validator set is established Genesis runs before the first block begins. @@ -65,49 +65,49 @@ ABCI: CheckTx Mempool ``` -Transactions are sent as raw **protobuf-encoded bytes**. {/* cosmos/cosmos-sdk baseapp/baseapp.go:97 — txDecoder sdk.TxDecoder field; req.Tx is []byte decoded at baseapp/baseapp.go:865 */} +Transactions are sent as raw protobuf-encoded bytes. {/* todo: link to encoding section above */} -During `CheckTx`, BaseApp decodes the transaction, verifies signatures and sequences, validates fees and gas, and performs basic message validation. {/* cosmos/cosmos-sdk baseapp/abci.go:367 — CheckTx calls RunTx, which orchestrates decode and ante handler execution */} `MsgServer` methods are not executed during `CheckTx`; only the AnteHandler and basic structural checks run. {/* cosmos/cosmos-sdk baseapp/baseapp.go:1022 — runMsgs breaks early when mode != execModeFinalize && mode != execModeSimulate */} +During `CheckTx`, BaseApp decodes the transaction, verifies signatures and sequences, validates fees and gas, and performs basic message validation. -No state changes are committed during `CheckTx`. {/* cosmos/cosmos-sdk baseapp/baseapp.go:990 — msCache.Write() at line 994 only executes when mode == execModeFinalize, not in check mode */} 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. +No state changes are committed during `CheckTx`. 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. -When a validator proposes a block, CometBFT selects transactions from the mempool, orders them, and reaches consensus on the block. Once the block is finalized, CometBFT sends it to the application via `FinalizeBlock`. +When a validator proposes a block, CometBFT selects the transactions from the mempool, orders them, and reaches consensus on the block. Once the block is finalized, CometBFT sends it to the application via `FinalizeBlock`. ## FinalizeBlock -CometBFT calls `FinalizeBlock` once per block. {/* cosmos/cosmos-sdk baseapp/abci.go:973 */} Inside `FinalizeBlock`, BaseApp runs these phases in order: `PreBlock` → `BeginBlock` → transaction execution → `EndBlock`. {/* cosmos/cosmos-sdk baseapp/abci.go:792 — internalFinalizeBlock; preBlock:865, beginBlock:872, tx loop:900, endBlock:929 */} +CometBFT calls `FinalizeBlock` once per block. Inside `FinalizeBlock`, BaseApp runs these phases in order: `PreBlock` → `BeginBlock` → transaction execution → `EndBlock`. ### 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 {/* cosmos/cosmos-sdk x/upgrade/module.go:162 */} 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.go`), and the application's `ModuleManager` invokes all registered PreBlockers during `FinalizeBlock`. {/* cosmos/cosmos-sdk x/upgrade/module.go:162 — PreBlock implementation in upgrade module */} +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.go`), and the application's `ModuleManager` invokes all registered PreBlockers during `FinalizeBlock`. ### BeginBlock -BeginBlock runs after PreBlock and handles per-block housekeeping that must happen before any transactions execute, regardless of what transactions are in the block. Common uses include minting inflation rewards, {/* cosmos/cosmos-sdk x/mint/module.go:157 */} distributing staking rewards, {/* cosmos/cosmos-sdk x/distribution/module.go:164 */} and resetting per-block state. Modules implement this via the `BeginBlock` function in `x//module.go`. {/* cosmos/cosmos-sdk x/mint/module.go:157 — BeginBlock implementation in mint module */} +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.go`. ### Transaction execution -After BeginBlock, BaseApp iterates over each transaction in the block and runs it through a fixed pipeline. {/* cosmos/cosmos-sdk baseapp/abci.go:900 — executeTxsWithExecutor call in internalFinalizeBlock processes all transactions after beginBlock */} +After BeginBlock, 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` file, the AnteHandler runs first for every transaction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:904 */} It verifies the transaction's signature, {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:400 */} increments the sequence number, {/* cosmos/cosmos-sdk x/auth/ante/sigverify.go:537 */} deducts the fee, {/* cosmos/cosmos-sdk x/auth/ante/fee.go:111 */} and meters the gas. {/* cosmos/cosmos-sdk x/auth/ante/setup.go:40 — SetUpContextDecorator sets the gas meter from the tx's declared gas limit */} +Configured in a Cosmos SDK chain's `app.go` file, the `AnteHandler` runs first for every transaction. It verifies the transaction's signature, increments the sequence number, deducts the fee, and meters the gas. -If the AnteHandler fails, the transaction aborts and its messages do not execute. {/* cosmos/cosmos-sdk baseapp/baseapp.go:929 — returns error before reaching runMsgs if ante fails */} +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 via BaseApp's `MsgServiceRouter` to the appropriate module's protobuf `Msg` service. Messages are module-specific and typically defined in a module's `tx.proto` (often under `proto/cosmos//...`). BaseApp routes these messages to the module's registered protobuf `Msg` service handler, which calls the module's `MsgServer` implementation (commonly located at `x//keeper/msg_server.go`). {/* cosmos/cosmos-sdk x/bank/keeper/msg_server.go:29 — MsgSend handler as representative example */} +Each message in a transaction is routed via BaseApp'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. 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. -Messages execute sequentially in the order they appear in the transaction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:1021 — for i, msg := range msgs loop in runMsgs */} +Messages execute sequentially in the order they appear in the transaction. #### Step 3: Atomicity -Transactions are atomic: all messages succeed or none are committed. {/* cosmos/cosmos-sdk baseapp/baseapp.go:994 — msCache.Write() only called when err == nil (line 989) and mode == execModeFinalize (line 990) */} +Transactions are atomic: all messages succeed or none are committed. ``` Tx @@ -116,19 +116,21 @@ Tx └─ Msg 3 ``` -If any message fails, all state changes from that transaction are discarded and the transaction returns an error. The next transaction in the block is then executed. BaseApp uses cached stores internally to implement this. {/* cosmos/cosmos-sdk baseapp/baseapp.go:662 — cacheTxContext creates a branched CacheMultiStore per transaction */} +If any message fails, all state changes from that transaction are discarded and the transaction returns an error. The next transaction in the block is then executed. BaseApp uses cached stores internally to implement this. + +{/* todo: add section linking to unordered tx */} ### EndBlock -EndBlock runs after all transactions in the block have executed. {/* cosmos/cosmos-sdk baseapp/abci.go:929 */} It is used for logic that depends on the block's cumulative state, like tallying governance votes after all vote transactions have been processed, {/* cosmos/cosmos-sdk x/gov/module.go:197 — EndBlock tallies proposals whose voting period has ended */} or recalculating validator power after all delegation changes in the block. {/* cosmos/cosmos-sdk x/staking/module.go:181 */} Modules implement this via the `EndBlock` function in `x//module.go`. {/* cosmos/cosmos-sdk x/staking/module.go:181 — EndBlock implementation in staking module */} +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.go`. ## Commit -After `FinalizeBlock` returns, CometBFT calls `Commit`. {/* cosmos/cosmos-sdk baseapp/abci.go:1044 */} This persists the state changes to the node's local disk. {/* cosmos/cosmos-sdk baseapp/abci.go:1062 — cms.Commit() writes all KV stores to disk */} All validators must compute the same app hash. If they do not, consensus halts. +After `FinalizeBlock` 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 during `FinalizeBlock`, {/* cosmos/cosmos-sdk baseapp/abci.go:1013 — res.AppHash = app.workingHash() set on ResponseFinalizeBlock, not ResponseCommit */} which guarantees consensus safety. This is critical for the safety and liveness of the consensus protocol. +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 during `FinalizeBlock`, which guarantees consensus safety. If validators holding more than 1/3 of voting power disagree on the app hash, consensus halts. ## Complete lifecycle overview @@ -170,4 +172,3 @@ CometBFT drives block processing through ABCI. BaseApp implements ABCI and orche - `FinalizeBlock` computes and returns the app hash; `Commit` persists state to disk Protobuf ensures canonical encoding so all validators interpret transactions identically. - From 1a7277bde848be8c70eec7baf7e16ca2f6989f2e Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Fri, 27 Feb 2026 13:26:11 -0500 Subject: [PATCH 08/36] Create modules.mdx --- sdk/v0.53/learn/concepts/modules.mdx | 256 +++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 sdk/v0.53/learn/concepts/modules.mdx diff --git a/sdk/v0.53/learn/concepts/modules.mdx b/sdk/v0.53/learn/concepts/modules.mdx new file mode 100644 index 00000000..502925cd --- /dev/null +++ b/sdk/v0.53/learn/concepts/modules.mdx @@ -0,0 +1,256 @@ +--- +title: Intro to Modules +--- + +In the previous section, you saw how blocks and transactions are processed. But where does the actual application logic live? + +In the Cosmos SDK, **modules** define that logic. + +Modules are the fundamental building blocks of a Cosmos SDK application. Each module encapsulates a specific piece of functionality, such as accounts, token transfers, validator management, governance, or any custom logic you define. + +## Why modules exist + +A blockchain application needs to manage many independent concerns (accounts, balances, validator management, etc). Instead of placing all logic in a single monolithic state machine, the Cosmos SDK divides the application into modules. The SDK provides a base layer that for these modules to operate together as a cohesive blockchain. + +Each module owns a slice of state, defines its messages and queries, implements its business rules, and hooks into the block lifecycle and genesis as needed. This keeps the application organized, composable, and easier to reason about. + +## What a module defines + +A module is a self-contained unit of state and logic. + +At a high level, a module defines: + +state: a kv store namespace that contains the module's state +messages: the actions the module allows +message server: the module's handler for messages +keeper: the module's interface to its state + +### 1. State + +Each module owns its own part of the blockchain state. For example: + +- The `x/auth` module stores account metadata. +- The `x/bank` module stores account balances. + +Modules do not share storage directly. Each module has its own key-value store namespace located in a `multistore`. + + +### 2. Messages + +As you learned in the [Transactions, Messages, and Queries](/sdk/v0.53/learn/concepts/transactions) section, each module defines the actions it allows via messages (`sdk.Msg`). + +Messages are defined in the module’s `tx.proto` file and implemented by that module’s `MsgServer`. Here is a simplified example of a message definition from the bank module: + +``` +message MsgSend { + string from_address = 1; + string to_address = 2; + repeated cosmos.base.v1beta1.Coin amount = 3; +} +``` + +This message represents a request to transfer tokens from one account to another. +When included in a transaction and executed: + +1. The sender’s balance is checked. +2. The amount is deducted from `from_address`. +3. The amount is credited to `to_address`. + +The message defines the intent and required data. The `MsgServer` orchestrates execution, delegating state reads and writes to the module's keeper. + +When a transaction is executed, the SDK's `BaseApp` struct routes each message to the module that defines it. + +### Message execution (`MsgServer`) + +Each module implements a **MsgServer**: the handler BaseApp calls when a message is routed to that module. The `MsgServer` validates the message, applies business rules, reads or writes state via the keeper, emits events, and returns a response. It is where a module’s state transitions are defined. + +### 3. Queries + +Modules expose read-only access to their state through query services, defined in `query.proto`. + +Queries do not modify state and do not go through block execution. + +### 4. Business logic + +The core rules of the application live inside modules. + +Business logic is typically implemented in: + +- The module’s `MsgServer` as the entry point for message handling — validating inputs, enforcing rules, and coordinating state changes +- The module’s `Keeper` as the internal API for state — reading and writing to the module’s store and encapsulating the core business logic +--- + +### 5. Block hooks + +Modules may execute logic at specific points in the block lifecycle by implementing optional hook interfaces: + +- `HasBeginBlocker` — runs logic at the start of each block +- `HasEndBlocker` — runs logic at the end of each block +- `HasPreBlocker` — runs logic before BeginBlock, used for consensus parameter changes + +Modules only implement the hooks they need. Most modules implement none. These hooks are invoked during block execution, as described in the lifecycle section. + +--- + +### 6. Genesis initialization + +Modules define how their state is initialized when the chain starts. + +Each module implements: + +- `DefaultGenesis` +- `ValidateGenesis` +- `InitGenesis` +- `ExportGenesis` + +During `InitChain`, BaseApp calls each module’s `InitGenesis` to populate its state from `genesis.json`. + +--- + +## The role of the keeper + +A **keeper** is the module’s interface to its state. + +It: + +- Reads and writes to the module’s KV store +- Encapsulates storage access +- Provides reusable internal methods + +The keeper is used by: + +- Message handlers (`MsgServer`) +- Query handlers +- BeginBlock / EndBlock logic +- Other modules (via well-defined interfaces) + +You can think of the keeper as the module’s state manager and internal API. + +--- + +## Inter-module access + +Modules are isolated by default. + +Each module owns its state, and direct access to that state is restricted through the module’s keeper. Other modules cannot arbitrarily mutate another module’s storage. + +Instead, modules interact through explicitly defined keeper interfaces. + +For example: + +- The validator module calls methods on the bank keeper to transfer tokens. +- The governance module calls parameter update methods on other modules. + +Each module defines an `expected_keepers.go` file that declares the interfaces it requires from other modules. This makes cross-module dependencies explicit: a module can only call methods the other module has chosen to expose. + +This design keeps dependencies auditable and prevents accidental or unsafe cross-module state mutation. + +--- + +## Governance authority and privileged actions + +Not all messages are equal. Actions like parameter updates, software upgrades, and certain administrative changes are defined as messages but may only be executed by a caller with the correct authority address, typically the governance module. This lets modules enforce privileged behavior while still using the same message execution pipeline. + +--- + +## Built-in and custom modules + +The Cosmos SDK provides many built-in modules, including: + +- `x/auth` +- `x/bank` +- `x/gov` +- `x/distribution` + +Applications can include any subset of these modules and can define entirely new custom modules. + +A Cosmos SDK blockchain is ultimately just a collection of modules assembled into a single application. + +--- + +## Anatomy of a module (high-level) + +Modules live under the `x/` directory of an SDK application: + +``` +x/ +├── auth/ # Accounts and authentication +├── bank/ # Token balances and transfers +├── poa/ # Proof-of-authority validator management +├── gov/ # Governance system +└── mymodule/ # Your custom module +``` + +Each subdirectory under `x/` is a self-contained module. +An application composes multiple modules together to form a complete blockchain. + +--- + +Inside a module, you will typically see a structure like this: + +``` +x/mymodule/ +├── keeper/ +│ ├── keeper.go # Keeper struct and state access methods +│ ├── msg_server.go # MsgServer implementation +│ └── query_server.go # QueryServer implementation +├── types/ +│ ├── expected_keepers.go # Interfaces for other modules’ keepers +│ ├── keys.go # Store key definitions +│ └── *.pb.go # Generated from proto definitions +└── module.go # AppModule implementation and hook registration +``` + +Proto files live separately at the repository root, not inside `x/`: + +``` +proto/myapp/mymodule/v1/ +├── tx.proto # Message definitions +├── query.proto # Query service definitions +├── state.proto # On-chain state types +└── genesis.proto # Genesis state definition +``` + +- **`keeper/`** + Contains the `Keeper` struct (state access) and implementations of the `MsgServer` and `QueryServer` interfaces. + +- **`types/`** + Defines the module’s public types: generated protobuf structs, store keys, and the `expected_keepers.go` interfaces that declare what this module needs from other modules. + +- **`module.go`** + Connects the module to the application and registers genesis handlers, block hooks, and message and query services. + +- **`.proto` files** + Define messages, queries, state schemas, and genesis state. Go code is generated from these files and used throughout the module. + +## Modules in context + +Putting everything together: + +- **Accounts** authorize transactions. +- **Transactions** carry messages. +- **Blocks** determine when execution happens. +- **Modules** define what execution means. +- **MsgServer** executes state transitions. +- **Keeper** manages storage. +- **State** records the result. + +Conceptually: + +``` +Transaction + ↓ +Message + ↓ +Module + ↓ +MsgServer + ↓ +Keeper + ↓ +State +``` + +Modules are where the blockchain’s rules live. + +In the next section, you will look more closely at how module state is stored, how genesis initializes it, and how the application commits deterministic state transitions. \ No newline at end of file From 4b27c06df9fb336bb4d5c37de27fda8b168a0e8e Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:46:27 -0500 Subject: [PATCH 09/36] Update modules.mdx --- sdk/v0.53/learn/concepts/modules.mdx | 164 ++++++++++++++++----------- 1 file changed, 97 insertions(+), 67 deletions(-) diff --git a/sdk/v0.53/learn/concepts/modules.mdx b/sdk/v0.53/learn/concepts/modules.mdx index 502925cd..d7b10e29 100644 --- a/sdk/v0.53/learn/concepts/modules.mdx +++ b/sdk/v0.53/learn/concepts/modules.mdx @@ -2,7 +2,7 @@ title: Intro to Modules --- -In the previous section, you saw how blocks and transactions are processed. But where does the actual application logic live? +In the previous section, you saw how blocks and transactions are processed. But where does the actual application logic of a blockchain live? In the Cosmos SDK, **modules** define that logic. @@ -12,7 +12,7 @@ Modules are the fundamental building blocks of a Cosmos SDK application. Each mo A blockchain application needs to manage many independent concerns (accounts, balances, validator management, etc). Instead of placing all logic in a single monolithic state machine, the Cosmos SDK divides the application into modules. The SDK provides a base layer that for these modules to operate together as a cohesive blockchain. -Each module owns a slice of state, defines its messages and queries, implements its business rules, and hooks into the block lifecycle and genesis as needed. This keeps the application organized, composable, and easier to reason about. +Each module owns a slice of state, defines its messages and queries, implements its business rules, and hooks into the block lifecycle and genesis as needed. This keeps the application organized, composable, and easier to reason about. It also separates safety concerns between modules, creating a more secure system. ## What a module defines @@ -20,22 +20,31 @@ A module is a self-contained unit of state and logic. At a high level, a module defines: -state: a kv store namespace that contains the module's state -messages: the actions the module allows -message server: the module's handler for messages -keeper: the module's interface to its state +- state: a kv store namespace that contains the module's data +- messages: the actions the module allows +- queries: read-only access to the module's state +- message server: validates, applies business logic, and delegates to the keeper +- keeper: the state access layer — the only sanctioned path to the store -### 1. State +### State Each module owns its own part of the blockchain state. For example: - The `x/auth` module stores account metadata. - The `x/bank` module stores account balances. -Modules do not share storage directly. Each module has its own key-value store namespace located in a `multistore`. +Modules do not share storage directly. Each module has its own key-value store namespace located in a `multistore`. For example, the bank module's store might contain entries like: + +``` +// x/bank store (conceptual) +balances | cosmos1abc...xyz | uatom → 1000000 +balances | cosmos1def...uvw | uatom → 500000 +``` + +The key encodes the namespace, address, and denomination. The value is the encoded amount. No other module can read or write these entries directly, only the [bank keeper](#keeper) can. -### 2. Messages +### Messages As you learned in the [Transactions, Messages, and Queries](/sdk/v0.53/learn/concepts/transactions) section, each module defines the actions it allows via messages (`sdk.Msg`). @@ -56,83 +65,91 @@ When included in a transaction and executed: 2. The amount is deducted from `from_address`. 3. The amount is credited to `to_address`. -The message defines the intent and required data. The `MsgServer` orchestrates execution, delegating state reads and writes to the module's keeper. - When a transaction is executed, the SDK's `BaseApp` struct routes each message to the module that defines it. -### Message execution (`MsgServer`) - -Each module implements a **MsgServer**: the handler BaseApp calls when a message is routed to that module. The `MsgServer` validates the message, applies business rules, reads or writes state via the keeper, emits events, and returns a response. It is where a module’s state transitions are defined. - -### 3. Queries -Modules expose read-only access to their state through query services, defined in `query.proto`. +### Queries -Queries do not modify state and do not go through block execution. +Modules expose read-only access to their state through query services, defined in `query.proto`. Queries do not modify state and do not go through block execution. For example: -### 4. Business logic +``` +// query.proto +rpc Balance(QueryBalanceRequest) returns (QueryBalanceResponse); +``` -The core rules of the application live inside modules. +A caller provides an address and denomination; the query reads the balance from the keeper and returns it without modifying state. -Business logic is typically implemented in: +### Business logic -- The module’s `MsgServer` as the entry point for message handling — validating inputs, enforcing rules, and coordinating state changes -- The module’s `Keeper` as the internal API for state — reading and writing to the module’s store and encapsulating the core business logic ---- +While messages defines the intent, the `MsgServer` and `Keeper` implement the business logic to execute that intent and apply it to the module's state. -### 5. Block hooks +Business logic is split across two layers: -Modules may execute logic at specific points in the block lifecycle by implementing optional hook interfaces: +- The **`MsgServer`** handles the "What should happen" logic: it validates inputs, checks authorization. Then, it applies the business logic. Once satisfied, it delegates to the keeper. +- The **`Keeper`** handles the "make it happen" part: it is the only sanctioned path to the module’s store and executes the actual reads and writes. It trusts that the caller has already validated the request. -- `HasBeginBlocker` — runs logic at the start of each block -- `HasEndBlocker` — runs logic at the end of each block -- `HasPreBlocker` — runs logic before BeginBlock, used for consensus parameter changes - -Modules only implement the hooks they need. Most modules implement none. These hooks are invoked during block execution, as described in the lifecycle section. - ---- +### Message execution (`MsgServer`) -### 6. Genesis initialization +Each module implements a **MsgServer**, which is invoked by `BaseApp`’s message router when a message is routed to that module. The `MsgServer` checks that the signer is authorized, validates the message, applies business rules, then calls the keeper to read or write state. It emits events and returns a response. The following is a simplified example from the counter module’s `Add` handler: -Modules define how their state is initialized when the chain starts. +```go +func (m msgServer) Add(ctx context.Context, request *types.MsgAddRequest) (*types.MsgAddResponse, error) { + if request.GetAdd() >= math.MaxUint64 { + return nil, ErrNumTooLarge + } -Each module implements: + params, err := m.GetParams(ctx) + if err != nil { + return nil, err + } -- `DefaultGenesis` -- `ValidateGenesis` -- `InitGenesis` -- `ExportGenesis` + if params.MaxAddValue > 0 && request.GetAdd() > params.MaxAddValue { + return nil, ErrExceedsMaxAdd + } -During `InitChain`, BaseApp calls each module’s `InitGenesis` to populate its state from `genesis.json`. + count, err := m.counter.Get(ctx) + if err != nil && !errors.Is(err, collections.ErrNotFound) { + return nil, err + } ---- + newCount := count + request.GetAdd() -## The role of the keeper + if err = m.counter.Set(ctx, newCount); err != nil { + return nil, err + } -A **keeper** is the module’s interface to its state. + return &types.MsgAddResponse{UpdatedCount: newCount}, nil +} +``` -It: +## Keeper -- Reads and writes to the module’s KV store -- Encapsulates storage access -- Provides reusable internal methods +A **keeper** is a Go struct that holds a reference to the module’s KV store and provides typed methods for reading and writing state. It is the only sanctioned way to access a module’s state; no code touches the store directly. For example, the counter keeper: -The keeper is used by: +```go +type Keeper struct { + Schema collections.Schema + counter collections.Item[uint64] + params collections.Item[types.Params] + bankKeeper types.BankKeeper -- Message handlers (`MsgServer`) -- Query handlers -- BeginBlock / EndBlock logic -- Other modules (via well-defined interfaces) + // authority is the address capable of executing a MsgUpdateParams message. + // Typically, this should be the x/gov module account. + authority string +} -You can think of the keeper as the module’s state manager and internal API. +func (k *Keeper) GetParams(ctx context.Context) (types.Params, error) { + return k.params.Get(ctx) +} +``` ---- +The keeper does not perform authorization checks; that responsibility belongs to the `MsgServer`. The `MsgServer` and `QueryServer` both embed the keeper and call its methods. -## Inter-module access +Some messages, like `MsgUpdateParams`, are privileged and may only be executed by a specific address. The `authority` field in the keeper stores that address (typically the governance module account). -Modules are isolated by default. +### Inter-module access -Each module owns its state, and direct access to that state is restricted through the module’s keeper. Other modules cannot arbitrarily mutate another module’s storage. +Modules are isolated by default. Each module owns its state, and direct access to that state is restricted through the module’s keeper. Other modules cannot arbitrarily mutate another module’s storage. Instead, modules interact through explicitly defined keeper interfaces. @@ -145,13 +162,30 @@ Each module defines an `expected_keepers.go` file that declares the interfaces i This design keeps dependencies auditable and prevents accidental or unsafe cross-module state mutation. ---- -## Governance authority and privileged actions +### Block hooks -Not all messages are equal. Actions like parameter updates, software upgrades, and certain administrative changes are defined as messages but may only be executed by a caller with the correct authority address, typically the governance module. This lets modules enforce privileged behavior while still using the same message execution pipeline. +Modules may execute logic at specific points in the block lifecycle by implementing optional hook interfaces: + +- `HasBeginBlocker` — runs logic at the start of each block +- `HasEndBlocker` — runs logic at the end of each block +- `HasPreBlocker` — runs logic before BeginBlock, used for consensus parameter changes + +Modules only implement the hooks they need. Most modules implement none. These hooks are invoked during block execution, as described in the lifecycle section. + +### Genesis initialization + +Modules define how their state is initialized when the chain starts. + +Each module implements: + +- `DefaultGenesis` +- `ValidateGenesis` +- `InitGenesis` +- `ExportGenesis` + +During `InitChain`, BaseApp calls each module’s `InitGenesis` to populate its state from `genesis.json`. ---- ## Built-in and custom modules @@ -164,9 +198,7 @@ The Cosmos SDK provides many built-in modules, including: Applications can include any subset of these modules and can define entirely new custom modules. -A Cosmos SDK blockchain is ultimately just a collection of modules assembled into a single application. - ---- +A Cosmos SDK blockchain is ultimately a collection of modules assembled into a single application. This customization of modules and application logic down to the lowest levels of a chain is what makes the Cosmos sdk so flexible and powerful. ## Anatomy of a module (high-level) @@ -235,8 +267,6 @@ Putting everything together: - **Keeper** manages storage. - **State** records the result. -Conceptually: - ``` Transaction ↓ From 6558c8a7b946cf6ebbe59926408aa781383a24ff Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Fri, 27 Feb 2026 18:12:01 -0500 Subject: [PATCH 10/36] Update modules.mdx --- sdk/v0.53/learn/concepts/modules.mdx | 65 +++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/sdk/v0.53/learn/concepts/modules.mdx b/sdk/v0.53/learn/concepts/modules.mdx index d7b10e29..87656c0d 100644 --- a/sdk/v0.53/learn/concepts/modules.mdx +++ b/sdk/v0.53/learn/concepts/modules.mdx @@ -28,6 +28,8 @@ At a high level, a module defines: ### State +State is the data persisted on the chain — everything that transactions can read from or write to. + Each module owns its own part of the blockchain state. For example: - The `x/auth` module stores account metadata. @@ -41,7 +43,7 @@ balances | cosmos1abc...xyz | uatom → 1000000 balances | cosmos1def...uvw | uatom → 500000 ``` -The key encodes the namespace, address, and denomination. The value is the encoded amount. No other module can read or write these entries directly, only the [bank keeper](#keeper) can. +The key encodes the namespace, address, and denomination. The value is the encoded amount. No other module can read or write these entries directly, only the [module's keeper](#keeper) can. ### Messages @@ -77,20 +79,33 @@ Modules expose read-only access to their state through query services, defined i rpc Balance(QueryBalanceRequest) returns (QueryBalanceResponse); ``` -A caller provides an address and denomination; the query reads the balance from the keeper and returns it without modifying state. +In this case, the caller provides an address and denomination; the query reads the balance from the x/bank keeper and returns it without modifying state. ### Business logic -While messages defines the intent, the `MsgServer` and `Keeper` implement the business logic to execute that intent and apply it to the module's state. +While messages define intent, the `MsgServer` and `Keeper` work together to execute that intent and apply state transitions to the module. + +Business logic is conceptually split across two layers: -Business logic is split across two layers: +- The **`MsgServer`** handles the transaction-facing logic: it validates inputs, applies message-level business rules, and where required checks authorization. Once satisfied, it delegates state transitions to the keeper. +- The **`Keeper`** is the module’s state access layer: it defines the storage schema and provides the sanctioned APIs for reading and writing state. Rather than thinking of the keeper as "just storage," it is more accurate to view it as the module’s domain API — it owns how state changes. -- The **`MsgServer`** handles the "What should happen" logic: it validates inputs, checks authorization. Then, it applies the business logic. Once satisfied, it delegates to the keeper. -- The **`Keeper`** handles the "make it happen" part: it is the only sanctioned path to the module’s store and executes the actual reads and writes. It trusts that the caller has already validated the request. +--- ### Message execution (`MsgServer`) -Each module implements a **MsgServer**, which is invoked by `BaseApp`’s message router when a message is routed to that module. The `MsgServer` checks that the signer is authorized, validates the message, applies business rules, then calls the keeper to read or write state. It emits events and returns a response. The following is a simplified example from the counter module’s `Add` handler: +Each module implements a **MsgServer**, which is invoked by `BaseApp`’s message router when a message is routed to that module. + +The `MsgServer` is responsible for: + +- Validating the message fields +- Enforcing message-specific business rules +- Checking authorization when required +- Calling keeper methods to perform state transitions +- Emitting events +- Returning a response + +The following is an abridged example from the counter module’s `Add` handler (bank fee charge and metrics omitted for clarity): ```go func (m msgServer) Add(ctx context.Context, request *types.MsgAddRequest) (*types.MsgAddResponse, error) { @@ -107,6 +122,7 @@ func (m msgServer) Add(ctx context.Context, request *types.MsgAddRequest) (*type return nil, ErrExceedsMaxAdd } + sdkCtx := sdk.UnwrapSDKContext(ctx) count, err := m.counter.Get(ctx) if err != nil && !errors.Is(err, collections.ErrNotFound) { return nil, err @@ -118,13 +134,43 @@ func (m msgServer) Add(ctx context.Context, request *types.MsgAddRequest) (*type return nil, err } + sdkCtx.EventManager().EmitEvent( + sdk.NewEvent( + "count_increased", + sdk.NewAttribute("count", fmt.Sprintf("%v", newCount)), + ), + ) + return &types.MsgAddResponse{UpdatedCount: newCount}, nil } ``` +`Add` is permissionless — any account can call it. In more complex modules, the state transition logic (`counter.Get` / `counter.Set`) is typically extracted into a named keeper method so it can be reused from other entry points (e.g., `BeginBlock`, governance proposals, or other modules). + +For privileged messages like `MsgUpdateParams`, the `MsgServer` checks the caller against the stored authority before proceeding: + +```go +func (m msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { + if m.authority != msg.Authority { + return nil, sdkerrors.Wrapf( + govtypes.ErrInvalidSigner, + "invalid authority; expected %s, got %s", + m.authority, + msg.Authority, + ) + } + + return &types.MsgUpdateParamsResponse{}, m.setParams(ctx, msg.Params) +} +``` + +--- + ## Keeper -A **keeper** is a Go struct that holds a reference to the module’s KV store and provides typed methods for reading and writing state. It is the only sanctioned way to access a module’s state; no code touches the store directly. For example, the counter keeper: +A **keeper** is a Go struct that holds references to the module’s KV store (or collections schema) and any external keepers it depends on. It provides typed methods for reading and writing state and defines the module’s storage layout. + +It is the only sanctioned way to access a module’s state; no other code should access the store directly. For example, the counter keeper: ```go type Keeper struct { @@ -143,7 +189,7 @@ func (k *Keeper) GetParams(ctx context.Context) (types.Params, error) { } ``` -The keeper does not perform authorization checks; that responsibility belongs to the `MsgServer`. The `MsgServer` and `QueryServer` both embed the keeper and call its methods. +The `MsgServer` and `QueryServer` both embed the keeper and call its methods. Authorization is enforced at the `MsgServer` boundary; keeper methods trust that the caller has already validated the request. Some messages, like `MsgUpdateParams`, are privileged and may only be executed by a specific address. The `authority` field in the keeper stores that address (typically the governance module account). @@ -186,7 +232,6 @@ Each module implements: During `InitChain`, BaseApp calls each module’s `InitGenesis` to populate its state from `genesis.json`. - ## Built-in and custom modules The Cosmos SDK provides many built-in modules, including: From 44eb881692f2d0ef0d7ebd7733feeb4db41acce3 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:29:05 -0500 Subject: [PATCH 11/36] Update modules.mdx --- sdk/v0.53/learn/concepts/modules.mdx | 39 ++++++++++++---------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/sdk/v0.53/learn/concepts/modules.mdx b/sdk/v0.53/learn/concepts/modules.mdx index 87656c0d..8480ac4d 100644 --- a/sdk/v0.53/learn/concepts/modules.mdx +++ b/sdk/v0.53/learn/concepts/modules.mdx @@ -20,15 +20,15 @@ A module is a self-contained unit of state and logic. At a high level, a module defines: -- state: a kv store namespace that contains the module's data -- messages: the actions the module allows -- queries: read-only access to the module's state -- message server: validates, applies business logic, and delegates to the keeper -- keeper: the state access layer — the only sanctioned path to the store +- [state](#state): a kv store namespace that contains the module's data +- [messages](#messages): the actions the module allows +- [queries](#queries): read-only access to the module's state +- [`MsgServer`](#message-execution-msgserver): validates, applies business logic, and delegates to the keeper +- [keeper](#keeper): the state access layer — the only sanctioned path to the store ### State -State is the data persisted on the chain — everything that transactions can read from or write to. +State is the data persisted on the chain: everything that transactions can read from or write to. When a transaction is executed, modules apply **state transitions** — deterministic updates to this stored data. Each module owns its own part of the blockchain state. For example: @@ -45,7 +45,6 @@ balances | cosmos1def...uvw | uatom → 500000 The key encodes the namespace, address, and denomination. The value is the encoded amount. No other module can read or write these entries directly, only the [module's keeper](#keeper) can. - ### Messages As you learned in the [Transactions, Messages, and Queries](/sdk/v0.53/learn/concepts/transactions) section, each module defines the actions it allows via messages (`sdk.Msg`). @@ -69,7 +68,6 @@ When included in a transaction and executed: When a transaction is executed, the SDK's `BaseApp` struct routes each message to the module that defines it. - ### Queries Modules expose read-only access to their state through query services, defined in `query.proto`. Queries do not modify state and do not go through block execution. For example: @@ -164,8 +162,6 @@ func (m msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) } ``` ---- - ## Keeper A **keeper** is a Go struct that holds references to the module’s KV store (or collections schema) and any external keepers it depends on. It provides typed methods for reading and writing state and defines the module’s storage layout. @@ -180,7 +176,7 @@ type Keeper struct { bankKeeper types.BankKeeper // authority is the address capable of executing a MsgUpdateParams message. - // Typically, this should be the x/gov module account. + // Typically, this is the x/gov module account. authority string } @@ -201,14 +197,13 @@ Instead, modules interact through explicitly defined keeper interfaces. For example: -- The validator module calls methods on the bank keeper to transfer tokens. +- The staking module calls methods on the bank keeper to transfer tokens. - The governance module calls parameter update methods on other modules. Each module defines an `expected_keepers.go` file that declares the interfaces it requires from other modules. This makes cross-module dependencies explicit: a module can only call methods the other module has chosen to expose. This design keeps dependencies auditable and prevents accidental or unsafe cross-module state mutation. - ### Block hooks Modules may execute logic at specific points in the block lifecycle by implementing optional hook interfaces: @@ -217,7 +212,7 @@ Modules may execute logic at specific points in the block lifecycle by implement - `HasEndBlocker` — runs logic at the end of each block - `HasPreBlocker` — runs logic before BeginBlock, used for consensus parameter changes -Modules only implement the hooks they need. Most modules implement none. These hooks are invoked during block execution, as described in the lifecycle section. +Modules only implement the hooks they need. Most modules implement none. These hooks are invoked during block execution. ### Genesis initialization @@ -225,13 +220,15 @@ Modules define how their state is initialized when the chain starts. Each module implements: -- `DefaultGenesis` -- `ValidateGenesis` -- `InitGenesis` -- `ExportGenesis` +- `DefaultGenesis` — returns the module's default genesis state +- `ValidateGenesis` — validates the genesis state before the chain starts +- `InitGenesis` — writes the genesis state into the module's store at chain start +- `ExportGenesis` — reads the module's current state and serializes it as genesis data During `InitChain`, BaseApp calls each module’s `InitGenesis` to populate its state from `genesis.json`. +{/* todo: link to lifecycle page init genesis section */} + ## Built-in and custom modules The Cosmos SDK provides many built-in modules, including: @@ -261,8 +258,6 @@ x/ Each subdirectory under `x/` is a self-contained module. An application composes multiple modules together to form a complete blockchain. ---- - Inside a module, you will typically see a structure like this: ``` @@ -302,7 +297,7 @@ proto/myapp/mymodule/v1/ ## Modules in context -Putting everything together: +Putting everything together you've learned so far: - **Accounts** authorize transactions. - **Transactions** carry messages. @@ -326,6 +321,4 @@ Keeper State ``` -Modules are where the blockchain’s rules live. - In the next section, you will look more closely at how module state is stored, how genesis initializes it, and how the application commits deterministic state transitions. \ No newline at end of file From e8c144a34fe1ebdd0636f729a64af534160eab51 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:00:33 -0500 Subject: [PATCH 12/36] Update modules.mdx --- sdk/v0.53/learn/concepts/modules.mdx | 126 +++++++++++++-------------- 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/sdk/v0.53/learn/concepts/modules.mdx b/sdk/v0.53/learn/concepts/modules.mdx index 8480ac4d..70740251 100644 --- a/sdk/v0.53/learn/concepts/modules.mdx +++ b/sdk/v0.53/learn/concepts/modules.mdx @@ -10,7 +10,7 @@ Modules are the fundamental building blocks of a Cosmos SDK application. Each mo ## Why modules exist -A blockchain application needs to manage many independent concerns (accounts, balances, validator management, etc). Instead of placing all logic in a single monolithic state machine, the Cosmos SDK divides the application into modules. The SDK provides a base layer that for these modules to operate together as a cohesive blockchain. +A blockchain application needs to manage many independent concerns (accounts, balances, validator management, etc). Instead of placing all logic in a single monolithic state machine, the Cosmos SDK divides the application into modules. The SDK provides a base layer that allows these modules to operate together as a cohesive blockchain. Each module owns a slice of state, defines its messages and queries, implements its business rules, and hooks into the block lifecycle and genesis as needed. This keeps the application organized, composable, and easier to reason about. It also separates safety concerns between modules, creating a more secure system. @@ -28,14 +28,14 @@ At a high level, a module defines: ### State -State is the data persisted on the chain: everything that transactions can read from or write to. When a transaction is executed, modules apply **state transitions** — deterministic updates to this stored data. +State is the data persisted on the chain: everything that transactions can read from or write to. When a transaction is executed, modules apply state transitions or deterministic updates to this stored data. Each module owns its own part of the blockchain state. For example: -- The `x/auth` module stores account metadata. -- The `x/bank` module stores account balances. +- The `x/auth` module stores account metadata. {/* cosmos/cosmos-sdk x/auth/keeper/keeper.go:111 — Accounts IndexedMap persists account metadata keyed by address */} +- The `x/bank` module stores account balances. {/* cosmos/cosmos-sdk x/bank/keeper/view.go:68 — Balances IndexedMap stores account balances keyed by (address, denom) */} -Modules do not share storage directly. Each module has its own key-value store namespace located in a `multistore`. For example, the bank module's store might contain entries like: +Modules do not share storage directly. Each module has its own key-value store namespace located in a `multistore`. {/* cosmos/cosmos-sdk store/types/store.go:417 — KVStoreKey struct gives each module a named, isolated sub-store in the multistore */} For example, the bank module's store might contain entries like: ``` // x/bank store (conceptual) @@ -43,13 +43,13 @@ balances | cosmos1abc...xyz | uatom → 1000000 balances | cosmos1def...uvw | uatom → 500000 ``` -The key encodes the namespace, address, and denomination. The value is the encoded amount. No other module can read or write these entries directly, only the [module's keeper](#keeper) can. +The key encodes the namespace, address, and denomination. The value is the encoded amount. No other module can read or write these entries directly, only the [module's keeper](#keeper) can. {/* cosmos/example x/counter/keeper/keeper.go:17 — counter and params are unexported fields; external packages cannot access them directly */} ### Messages As you learned in the [Transactions, Messages, and Queries](/sdk/v0.53/learn/concepts/transactions) section, each module defines the actions it allows via messages (`sdk.Msg`). -Messages are defined in the module’s `tx.proto` file and implemented by that module’s `MsgServer`. Here is a simplified example of a message definition from the bank module: +Messages are defined in the module’s `tx.proto` file and implemented by that module’s `MsgServer`. {/* cosmos/example proto/example/counter/v1/tx.proto:11 — service Msg in tx.proto defines the module’s messages */} Here is a simplified example of a message definition from the bank module: ``` message MsgSend { @@ -59,25 +59,25 @@ message MsgSend { } ``` -This message represents a request to transfer tokens from one account to another. +This message represents a request to transfer tokens from one account to another. {/* cosmos/cosmos-sdk proto/cosmos/bank/v1beta1/tx.proto:39 — message MsgSend with from_address, to_address, amount */} When included in a transaction and executed: -1. The sender’s balance is checked. -2. The amount is deducted from `from_address`. -3. The amount is credited to `to_address`. +1. The sender’s balance is checked. {/* cosmos/cosmos-sdk x/bank/keeper/send.go:308 — GetBalance reads sender balance inside subUnlockedCoins before deduction */} +2. The amount is deducted from `from_address`. {/* cosmos/cosmos-sdk x/bank/keeper/send.go:240 — subUnlockedCoins called on fromAddr in SendCoins */} +3. The amount is credited to `to_address`. {/* cosmos/cosmos-sdk x/bank/keeper/send.go:245 — addCoins called on toAddr in SendCoins */} -When a transaction is executed, the SDK's `BaseApp` struct routes each message to the module that defines it. +When a transaction is executed, the SDK's `BaseApp` struct routes each message to the module that defines it. {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:54 — Handler returns the registered MsgServiceHandler by message type URL */} ### Queries -Modules expose read-only access to their state through query services, defined in `query.proto`. Queries do not modify state and do not go through block execution. For example: +Modules expose read-only access to their state through query services, defined in `query.proto`. {/* cosmos/example proto/example/counter/v1/query.proto:9 — service Query defines read-only RPCs */} Queries do not modify state and do not go through block execution. For example: ``` // query.proto rpc Balance(QueryBalanceRequest) returns (QueryBalanceResponse); ``` -In this case, the caller provides an address and denomination; the query reads the balance from the x/bank keeper and returns it without modifying state. +In this case, the caller provides an address and denomination; the query reads the balance from the x/bank keeper and returns it without modifying state. {/* cosmos/cosmos-sdk proto/cosmos/bank/v1beta1/query.proto:18 — Balance rpc takes QueryBalanceRequest (address + denom) and returns QueryBalanceResponse */} ### Business logic @@ -92,18 +92,18 @@ Business logic is conceptually split across two layers: ### Message execution (`MsgServer`) -Each module implements a **MsgServer**, which is invoked by `BaseApp`’s message router when a message is routed to that module. +Each module implements a **MsgServer**, which is invoked by `BaseApp`’s message router when a message is routed to that module. {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:54 — Handler looks up registered MsgServiceHandler by type URL and invokes it */} The `MsgServer` is responsible for: -- Validating the message fields -- Enforcing message-specific business rules -- Checking authorization when required -- Calling keeper methods to perform state transitions -- Emitting events -- Returning a response +- Validating the message fields {/* cosmos/example x/counter/keeper/msg_server.go:25 — checks request.GetAdd() >= math.MaxUint64 */} +- Enforcing message-specific business rules {/* cosmos/example x/counter/keeper/msg_server.go:34 — checks params.MaxAddValue > 0 && request.GetAdd() > params.MaxAddValue */} +- Checking authorization when required {/* cosmos/example x/counter/keeper/msg_server.go:76 — checks m.authority != msg.Authority in UpdateParams */} +- Calling keeper methods to perform state transitions {/* cosmos/example x/counter/keeper/msg_server.go:57 — m.counter.Set writes the new count to state */} +- Emitting events {/* cosmos/example x/counter/keeper/msg_server.go:62 — EmitEvent emits "count_increased" with updated count */} +- Returning a response {/* cosmos/example x/counter/keeper/msg_server.go:71 — returns MsgAddResponse{UpdatedCount: newCount} */} -The following is an abridged example from the counter module’s `Add` handler (bank fee charge and metrics omitted for clarity): +The following is an abridged example from the counter module’s `Add` handler (bank fee charge and metrics omitted for clarity): {/* cosmos/example x/counter/keeper/msg_server.go:24 */} ```go func (m msgServer) Add(ctx context.Context, request *types.MsgAddRequest) (*types.MsgAddResponse, error) { @@ -143,9 +143,9 @@ func (m msgServer) Add(ctx context.Context, request *types.MsgAddRequest) (*type } ``` -`Add` is permissionless — any account can call it. In more complex modules, the state transition logic (`counter.Get` / `counter.Set`) is typically extracted into a named keeper method so it can be reused from other entry points (e.g., `BeginBlock`, governance proposals, or other modules). +`Add` is permissionless — any account can call it. {/* cosmos/example x/counter/keeper/msg_server.go:24 — no authority check in the Add handler body */} In more complex modules, the state transition logic (`counter.Get` / `counter.Set`) is typically extracted into a named keeper method so it can be reused from other entry points (e.g., `BeginBlock`, governance proposals, or other modules). {/* cosmos/example x/counter/keeper/keeper.go:59 — GetParams demonstrates the named keeper method pattern */} -For privileged messages like `MsgUpdateParams`, the `MsgServer` checks the caller against the stored authority before proceeding: +For privileged messages like `MsgUpdateParams`, the `MsgServer` checks the caller against the stored authority before proceeding: {/* cosmos/example x/counter/keeper/msg_server.go:76 — m.authority != msg.Authority check returns ErrInvalidSigner if mismatched */} ```go func (m msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { @@ -158,15 +158,19 @@ func (m msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) ) } - return &types.MsgUpdateParamsResponse{}, m.setParams(ctx, msg.Params) + if err := m.setParams(ctx, msg.Params); err != nil { + return nil, err + } + + return &types.MsgUpdateParamsResponse{}, nil } ``` ## Keeper -A **keeper** is a Go struct that holds references to the module’s KV store (or collections schema) and any external keepers it depends on. It provides typed methods for reading and writing state and defines the module’s storage layout. +A **keeper** is a Go struct that holds references to the module’s KV store (or collections schema) and any external keepers it depends on. {/* cosmos/example x/counter/keeper/keeper.go:15 — Keeper struct holds Schema, counter, params, bankKeeper, and authority */} It provides typed methods for reading and writing state and defines the module’s storage layout. -It is the only sanctioned way to access a module’s state; no other code should access the store directly. For example, the counter keeper: +It is the only sanctioned way to access a module’s state; no other code should access the store directly. {/* cosmos/example x/counter/keeper/keeper.go:17 — counter and params are unexported; only Keeper methods can access them from outside the package */} For example, the counter keeper: ```go type Keeper struct { @@ -176,7 +180,7 @@ type Keeper struct { bankKeeper types.BankKeeper // authority is the address capable of executing a MsgUpdateParams message. - // Typically, this is the x/gov module account. + // Typically, this should be the x/gov module account. authority string } @@ -185,22 +189,22 @@ func (k *Keeper) GetParams(ctx context.Context) (types.Params, error) { } ``` -The `MsgServer` and `QueryServer` both embed the keeper and call its methods. Authorization is enforced at the `MsgServer` boundary; keeper methods trust that the caller has already validated the request. +The `MsgServer` and `QueryServer` both embed the keeper and call its methods. {/* cosmos/example x/counter/keeper/msg_server.go:16 — msgServer struct embeds *Keeper */} Authorization is enforced at the `MsgServer` boundary; keeper methods trust that the caller has already validated the request. -Some messages, like `MsgUpdateParams`, are privileged and may only be executed by a specific address. The `authority` field in the keeper stores that address (typically the governance module account). +Some messages, like `MsgUpdateParams`, are privileged and may only be executed by a specific address. The `authority` field in the keeper stores that address (typically the governance module account). {/* cosmos/example x/counter/keeper/keeper.go:43 — authority defaults to authtypes.NewModuleAddress(govtypes.ModuleName).String() */} ### Inter-module access Modules are isolated by default. Each module owns its state, and direct access to that state is restricted through the module’s keeper. Other modules cannot arbitrarily mutate another module’s storage. -Instead, modules interact through explicitly defined keeper interfaces. +Instead, modules interact through explicitly defined keeper interfaces. {/* cosmos/example x/counter/types/expected_keepers.go:10 — BankKeeper interface declares only the methods counter requires from bank */} For example: -- The staking module calls methods on the bank keeper to transfer tokens. -- The governance module calls parameter update methods on other modules. +- The staking module calls methods on the bank keeper to transfer tokens. {/* cosmos/cosmos-sdk x/staking/types/expected_keepers.go:39 — DelegateCoinsFromAccountToModule in staking's expected BankKeeper interface */} +- The governance module calls parameter update methods on other modules. {/* cosmos/cosmos-sdk x/gov/keeper/proposal.go:67 — proposal executor routes each proposal message to the target module's handler */} -Each module defines an `expected_keepers.go` file that declares the interfaces it requires from other modules. This makes cross-module dependencies explicit: a module can only call methods the other module has chosen to expose. +Each module defines an `expected_keepers.go` file that declares the interfaces it requires from other modules. {/* cosmos/example x/counter/types/expected_keepers.go:11 — SendCoinsFromAccountToModule is the only method counter exposes from bank */} This makes cross-module dependencies explicit: a module can only call methods the other module has chosen to expose. This design keeps dependencies auditable and prevents accidental or unsafe cross-module state mutation. @@ -208,11 +212,11 @@ This design keeps dependencies auditable and prevents accidental or unsafe cross Modules may execute logic at specific points in the block lifecycle by implementing optional hook interfaces: -- `HasBeginBlocker` — runs logic at the start of each block -- `HasEndBlocker` — runs logic at the end of each block -- `HasPreBlocker` — runs logic before BeginBlock, used for consensus parameter changes +- `HasBeginBlocker` — runs logic at the start of each block {/* cosmos/cosmos-sdk core/appmodule/module.go:73 — HasBeginBlocker interface; BeginBlock runs before transactions are processed in a block */} +- `HasEndBlocker` — runs logic at the end of each block {/* cosmos/cosmos-sdk core/appmodule/module.go:83 — HasEndBlocker interface; EndBlock runs after transactions are processed in a block */} +- `HasPreBlocker` — runs logic before BeginBlock, used for consensus parameter changes {/* cosmos/cosmos-sdk core/appmodule/module.go:65 — HasPreBlocker returns ResponsePreBlock; IsConsensusParamsChanged() at line 60 signals consensus parameter updates to the caller */} -Modules only implement the hooks they need. Most modules implement none. These hooks are invoked during block execution. +Modules only implement the hooks they need. {/* cosmos/example x/counter/module.go:77 — BeginBlock comment: "This method is included for example purposes. Not every module needs these methods." */} Most modules implement none. These hooks are invoked during block execution. ### Genesis initialization @@ -220,12 +224,12 @@ Modules define how their state is initialized when the chain starts. Each module implements: -- `DefaultGenesis` — returns the module's default genesis state -- `ValidateGenesis` — validates the genesis state before the chain starts -- `InitGenesis` — writes the genesis state into the module's store at chain start -- `ExportGenesis` — reads the module's current state and serializes it as genesis data +- `DefaultGenesis` — returns the module's default genesis state {/* cosmos/cosmos-sdk types/module/module.go:77 — DefaultGenesis method in HasGenesisBasics interface */} +- `ValidateGenesis` — validates the genesis state before the chain starts {/* cosmos/cosmos-sdk types/module/module.go:78 — ValidateGenesis method in HasGenesisBasics interface */} +- `InitGenesis` — writes the genesis state into the module's store at chain start {/* cosmos/cosmos-sdk types/module/module.go:193 — InitGenesis method in HasGenesis interface */} +- `ExportGenesis` — reads the module's current state and serializes it as genesis data {/* cosmos/cosmos-sdk types/module/module.go:194 — ExportGenesis method in HasGenesis interface */} -During `InitChain`, BaseApp calls each module’s `InitGenesis` to populate its state from `genesis.json`. +During `InitChain`, BaseApp calls each module’s `InitGenesis` to populate its state from `genesis.json`. {/* cosmos/cosmos-sdk baseapp/abci.go:53 — InitChain entry point */} {/* cosmos/cosmos-sdk types/module/module.go:489 — module manager iterates OrderInitGenesis calling each module’s InitGenesis with genesis data */} {/* todo: link to lifecycle page init genesis section */} @@ -284,41 +288,33 @@ proto/myapp/mymodule/v1/ ``` - **`keeper/`** - Contains the `Keeper` struct (state access) and implementations of the `MsgServer` and `QueryServer` interfaces. + Contains the `Keeper` struct (state access) and implementations of the `MsgServer` and `QueryServer` interfaces. {/* cosmos/example x/counter/keeper/keeper.go:15 — Keeper struct; cosmos/example x/counter/keeper/msg_server.go:16 — msgServer; cosmos/example x/counter/keeper/query_server.go:15 — NewQueryServer */} - **`types/`** - Defines the module’s public types: generated protobuf structs, store keys, and the `expected_keepers.go` interfaces that declare what this module needs from other modules. + Defines the module’s public types: generated protobuf structs, {/* cosmos/example x/counter/types/tx.pb.go:33 — MsgAddRequest generated from tx.proto */} store keys, {/* cosmos/example x/counter/types/keys.go:4 — ModuleName and StoreKey constants */} and the `expected_keepers.go` interfaces that declare what this module needs from other modules. {/* cosmos/example x/counter/types/expected_keepers.go:10 — BankKeeper interface */} - **`module.go`** - Connects the module to the application and registers genesis handlers, block hooks, and message and query services. + Connects the module to the application and registers genesis handlers, block hooks, and message and query services. {/* cosmos/example x/counter/module.go:95 — RegisterServices registers MsgServer and QueryServer with the module configurator */} - **`.proto` files** - Define messages, queries, state schemas, and genesis state. Go code is generated from these files and used throughout the module. + Define messages, queries, state schemas, and genesis state. {/* cosmos/example proto/example/counter/v1/tx.proto:11 — tx.proto defines service Msg; query.proto defines service Query */} Go code is generated from these files and used throughout the module. ## Modules in context Putting everything together you've learned so far: - **Accounts** authorize transactions. -- **Transactions** carry messages. -- **Blocks** determine when execution happens. -- **Modules** define what execution means. -- **MsgServer** executes state transitions. -- **Keeper** manages storage. -- **State** records the result. +- **Transactions** carry messages and execution constraints. +- **Blocks** order transactions and define commit boundaries. +- **Modules** define the business rules of execution. +- **MsgServer** validates messages and orchestrates state transitions. +- **Keeper** performs controlled reads and writes to module state. +- **State** persists the deterministic result of execution. ``` -Transaction - ↓ -Message - ↓ -Module - ↓ -MsgServer - ↓ -Keeper - ↓ -State +Account → Transaction → Message → Module → MsgServer → Keeper → State ``` -In the next section, you will look more closely at how module state is stored, how genesis initializes it, and how the application commits deterministic state transitions. \ No newline at end of file +In the next section, you will look more closely at how module state is stored, how genesis initializes it, and how the application commits deterministic state transitions. + +{/* todo: link to next page */} \ No newline at end of file From 87e2eb15b42c43e847416708b6c1e3e94aa87f34 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:00:35 -0500 Subject: [PATCH 13/36] Create store.mdx --- sdk/v0.53/learn/concepts/store.mdx | 224 +++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 sdk/v0.53/learn/concepts/store.mdx diff --git a/sdk/v0.53/learn/concepts/store.mdx b/sdk/v0.53/learn/concepts/store.mdx new file mode 100644 index 00000000..20df256d --- /dev/null +++ b/sdk/v0.53/learn/concepts/store.mdx @@ -0,0 +1,224 @@ +--- +title: State, Storage, and Genesis +--- + +In the previous section, you learned that modules define business logic and that keepers are responsible for reading and writing module state. This page takes a closer look at how state is stored and committed, and how a chain starts with its initial state. + +## What is state? + +State is the persistent data of the blockchain: account balances, delegations, governance proposals, module parameters, and any other data that survives between blocks. When a transaction executes, modules update state. When a block is committed, that updated state becomes the starting point for the next block: + +```go +State₀ + ↓ apply Block 1 +State₁ + ↓ apply Block 2 +State₂ +``` + +## The KVStore model + +At its lowest level, the Cosmos SDK stores state as **key-value pairs**. Both keys and values are byte arrays. Modules encode structured data into those bytes using **Protocol Buffers**, and decode them back when reading. + +{/* todo link to encoding page */} + +The following example shows a conceptual example of how the bank module stores balances: + +```go +key: 0x2 | len(address) | address_bytes | denom_bytes +value: ProtocolBuffer(amount) + +# example +key: 0x2 | 20 | cosmos1abc...xyz | uatom +value: ProtocolBuffer(1000000) +``` + +The key encodes the store prefix, address length, address, and denomination. The value is a Protocol Buffer-encoded amount. See [`x/bank/types/keys.go`](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/x/bank/types/keys.go) for the actual implementation. + +Each module owns its own namespace in the key-value store. + +## Multistore + +Each module has its own KVStore, and all module stores are mounted inside a [multistore](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/store/rootmulti/store.go) that is committed as a single state root. + +Each module can only read and write to its own store through its keeper. Access is gated by a `StoreKey`, which is a typed capability object registered at app startup. Modules that don't hold the key cannot open the store. + +This isolation follows an object-capabilities model: + +1. Modules cannot directly mutate another module's state +2. Cross-module interaction must go through exposed keeper methods + +When a block finishes executing, the multistore computes a new root hash (the **app hash**) that represents the entire application state. That hash is returned to CometBFT, included in the block header, and is what makes the chain's state verifiable. {/* todo: link to lifecycle page */} + +## How state is stored (IAVL and commit stores) + +Each module's KVStore is backed by a commit store. [See the store spec for more details.](../../build/spec/store/store.mdx) + +By default, the Cosmos SDK uses [**IAVL**](https://github.com/cosmos/iavl), a versioned AVL Merkle tree. + +IAVL gives every read and write of the tree `O(log n)` complexity, meaning the time to read or write a key scales with the height of the tree, not the total number of keys. It also versions state on each block commit, and produces deterministic root hashes that can be used to generate Merkle proofs for light clients. + +Each block commit produces a new tree version with a new root hash: + +```go +Block 1 Block 2 Block 3 + + [root h1] [root h2] [root h3] + / \ / \ / \ + [branch1] [branch2] [branch1] [branch2'] [branch1] [branch2''] + / \ / \ / \ / \ / \ / \ + [a] [b] [c] [d] [a] [b] [c] [d'] [a] [b] [c'] [d'] + ↑ ↑ + (updated) (updated) + +// branch1 and branch2 are internal nodes (they store hashes, not data). +// Leaf nodes (a, b, c, d) are actual key-value entries. +// When a leaf changes, only nodes on the path to the root are rewritten (marked '). +// Unmodified subtrees (branch1, a, b) are shared across all three versions. +``` + +Only modified nodes are rewritten, and unchanged nodes are shared across versions. The root hash changes any time any leaf changes. All validators must compute the same root hash. If they disagree, consensus halts. + +Because of this, state transitions must be deterministic, encoding must be deterministic, and transaction ordering must be consistent. + +## Store types in the SDK + +Beyond the base KVStore, the SDK provides several specialized store wrappers. + +- [CommitKVStore](#commitkvstore-persistent-store) +- [CacheMultiStore](#cachemultistore-transaction-isolation) +- [Transient store](#transient-store-ephemeral-per-block) +- [GasKVStore](#gaskvstore) +- [TraceKVStore](#tracekvstore) + +### CommitKVStore (persistent store) + +This is the main persistent store backed by IAVL. It persists across blocks, produces versioned commits, and contributes to the app hash. + +### CacheMultiStore (transaction isolation) + +Before executing each transaction, the Cosmos SDK's BaseApp struct creates a cached, copy-on-write view of the multistore. + +All writes during that transaction occur in this cached layer: + +``` +Multistore + ↓ CacheWrap (per transaction) + ↓ Execute tx + → Success → commit changes + → Failure → discard +``` + +- If the transaction succeeds, changes are written to the underlying store. +- If the transaction fails, the cache is discarded and no state changes are committed. + + +This is how transaction atomicity is implemented in the store layer. + +### Transient store (ephemeral per block) + +The SDK also supports **transient stores** which are reset at the start of each block and discarded at the end of the block. Transient stores are used for temporary per-block data such as counters or intermediate calculations and they do not affect the app hash. + +### Gas and trace store wrappers + +All store accesses are wrapped with additional behavior by the `GasKVStore` and `TraceKVStore` wrappers. + +- **GasKVStore**: charges gas for each read and write +- **TraceKVStore**: logs each store operation for debugging + +Gas is consumed at the store layer, meaning every read and write of a KVStore costs gas, and expensive operations naturally cost more. + +{/* todo: link to gas */} + +### Prefix store + +A **prefix store** wraps a KVStore and automatically prepends a fixed byte prefix to every key. This lets keepers scope their reads and writes to a sub-namespace without manually constructing prefixed keys on every call. + +```go +prefixStore := prefix.NewStore(kvStore, types.KeyPrefix("balances")) +prefixStore.Set(key, value) // stored as "balances" + key +``` + +This is how modules avoid key collisions within their own store. + +## Collections API (typed state access) + +In the Cosmos SDK, modules commonly use the collections API to define typed state access. + +Instead of manually constructing byte keys, modules define typed collections such as: + +- `collections.Item[T]` +- `collections.Map[K, V]` +- `collections.Sequence` + +Example: + +```go +// declared in the keeper struct +Counter collections.Item[uint64] + +// read and write in message handlers +count, _ := k.Counter.Get(ctx) +k.Counter.Set(ctx, count+1) +``` + +The Collections API defines the storage schema, handles encoding and decoding, ensures consistent key construction, and makes state access type-safe. + +Under the hood, collections still store data in a KVStore. Collections provide a safer abstraction over raw byte keys. See [`collections/collections.go`](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/collections/collections.go) for the base interface definitions. + +## How modules access state + +Modules typically do not read from or write to the multi-store directly. Instead, each module defines a keeper that manages access to the module’s portion of state. + +{/* todo: link to keeper page */} + +A keeper typically holds: + +- the module’s **store key** (an object-capability used to open the module’s `KVStore` from `Context`), +- a **Protocol Buffers codec** used to encode and decode values stored as bytes, +- **references (interfaces) to other keepers** the module depends on. + +State access typically flows through the keeper: + +```go +MsgServer / QueryServer + ↓ + Keeper + ↓ + KVStore +``` + +The keeper exposes high-level methods that construct keys, encode values, and enforce business logic: + +```go +func (k Keeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) +``` + +## Genesis and chain initialization + +Before the first block executes, the chain must start with an initial state called **genesis**, defined in `genesis.json`. Genesis is the first write to the KVStores — it is how every module's state exists before any transaction runs. + +During `InitChain`, BaseApp calls each module's `InitGenesis` to populate its store: + +``` +genesis.json + ↓ +BaseApp.InitChain + ↓ +Module.InitGenesis + ↓ +KVStores populated +``` + +For information onhow modules define their genesis methods (`DefaultGenesis`, `ValidateGenesis`, `InitGenesis`, `ExportGenesis`) and initialization ordering, see [modules](./modules.mdx) and the [lifecycle page](./lifecycle.mdx). + +{/* todo: add correct links to modules and lifecycle pages */} + + +## Further reading + +For more information on stores, pruning strategies, and store configuration, see the [store spec](/sdk/v0.53/build/spec/store/store). For the full store interface definitions, see [`store/types/store.go`](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/store/types/store.go) in the SDK source. + +After learning about state, storage, and initialization, the next section explains how data is serialized and encoded so that every validator interprets state identically. + From cea0e105071698f6609f414416bd4a0d349191c0 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:33:04 -0500 Subject: [PATCH 14/36] Update modules.mdx --- sdk/v0.53/learn/concepts/modules.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/v0.53/learn/concepts/modules.mdx b/sdk/v0.53/learn/concepts/modules.mdx index 70740251..41c522da 100644 --- a/sdk/v0.53/learn/concepts/modules.mdx +++ b/sdk/v0.53/learn/concepts/modules.mdx @@ -189,7 +189,7 @@ func (k *Keeper) GetParams(ctx context.Context) (types.Params, error) { } ``` -The `MsgServer` and `QueryServer` both embed the keeper and call its methods. {/* cosmos/example x/counter/keeper/msg_server.go:16 — msgServer struct embeds *Keeper */} Authorization is enforced at the `MsgServer` boundary; keeper methods trust that the caller has already validated the request. +The `MsgServer` and `QueryServer` both embed the keeper and call its methods. {/* cosmos/example x/counter/keeper/msg_server.go:17 — msgServer struct embeds *Keeper */} Authorization is enforced at the `MsgServer` boundary; keeper methods trust that the caller has already validated the request. Some messages, like `MsgUpdateParams`, are privileged and may only be executed by a specific address. The `authority` field in the keeper stores that address (typically the governance module account). {/* cosmos/example x/counter/keeper/keeper.go:43 — authority defaults to authtypes.NewModuleAddress(govtypes.ModuleName).String() */} @@ -202,7 +202,7 @@ Instead, modules interact through explicitly defined keeper interfaces. {/* cosm For example: - The staking module calls methods on the bank keeper to transfer tokens. {/* cosmos/cosmos-sdk x/staking/types/expected_keepers.go:39 — DelegateCoinsFromAccountToModule in staking's expected BankKeeper interface */} -- The governance module calls parameter update methods on other modules. {/* cosmos/cosmos-sdk x/gov/keeper/proposal.go:67 — proposal executor routes each proposal message to the target module's handler */} +- The governance module calls parameter update methods on other modules. {/* cosmos/cosmos-sdk x/gov/abci.go:189 — proposal executor calls keeper.Router().Handler(msg) to route each passed proposal message to the target module */} Each module defines an `expected_keepers.go` file that declares the interfaces it requires from other modules. {/* cosmos/example x/counter/types/expected_keepers.go:11 — SendCoinsFromAccountToModule is the only method counter exposes from bank */} This makes cross-module dependencies explicit: a module can only call methods the other module has chosen to expose. From 4c7e41ee3a42a64723b4d934c7c937d17a38f1fa Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:33:06 -0500 Subject: [PATCH 15/36] Update lifecycle.mdx --- sdk/v0.53/learn/concepts/lifecycle.mdx | 44 ++++++++++++++++++-------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/sdk/v0.53/learn/concepts/lifecycle.mdx b/sdk/v0.53/learn/concepts/lifecycle.mdx index 88d9b953..d3a4caf2 100644 --- a/sdk/v0.53/learn/concepts/lifecycle.mdx +++ b/sdk/v0.53/learn/concepts/lifecycle.mdx @@ -17,7 +17,7 @@ This page maps the block and transaction lifecycle back to those layers. ## ABCI overview -**BaseApp** is a struct in the Cosmos SDK that implements the ABCI interface. Modules plug into BaseApp and execute logic during these phases. +**BaseApp** is a struct in the Cosmos SDK that implements the ABCI interface. {/* todo: link to baseapp */} Modules plug into BaseApp and execute logic during these phases. ```go title="ABCI Flow" CometBFT @@ -30,6 +30,10 @@ ABCI: CheckTx → Mempool For every block: ↓ +ABCI: PrepareProposal (proposer only: select and filter txs) + ↓ +ABCI: ProcessProposal (all validators: accept or reject the block) + ↓ ABCI: FinalizeBlock → PreBlock → BeginBlock @@ -41,15 +45,9 @@ ABCI: Commit ## InitChain (genesis only) -`InitChain` runs once when the chain starts for the first time. This involves loading a `genesis.json` file, which defines the chain's initial state (balances, validators, module parameters, and other bootstrapping data). Modules define genesis logic in their `module.go` file. Specifically, they implement the `InitGenesis` and `ExportGenesis` functions. - -During this phase: +`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. -- BaseApp initializes stores and in-memory resources -- Modules load state from the genesis file via `InitGenesis` -- The initial validator set is established - -Genesis runs before the first block begins. +{/* todo: link to store#genesis-and-chain-initialization */} ## CheckTx and the mempool @@ -73,11 +71,23 @@ During `CheckTx`, BaseApp decodes the transaction, verifies signatures and seque No state changes are committed during `CheckTx`. 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. -When a validator proposes a block, CometBFT selects the transactions from the mempool, orders them, and reaches consensus on the block. Once the block is finalized, CometBFT sends it to the application via `FinalizeBlock`. +Validated transactions wait in the mempool until CometBFT selects a block proposer for the next round. + +## PrepareProposal + +Called by CometBFT on the block proposer only. BaseApp selects transactions from the mempool respecting the block's `MaxTxBytes` and `MaxGas` limits and returns the final transaction list. No state is written. {/* todo: link to baseapp#prepareproposal */} + +## ProcessProposal + +Called on every validator when it receives the proposed block. BaseApp verifies each transaction and returns `ACCEPT` or `REJECT`. No state is written. After validators acscept the block and consensus is reached, CometBFT calls `FinalizeBlock`. {/* todo: link to baseapp#processproposal */} ## FinalizeBlock -CometBFT calls `FinalizeBlock` once per block. Inside `FinalizeBlock`, BaseApp runs these phases in order: `PreBlock` → `BeginBlock` → transaction execution → `EndBlock`. +CometBFT calls `FinalizeBlock` once per block. Inside `FinalizeBlock`, BaseApp runs these phases in order: + +``` +PreBlock → BeginBlock → transaction execution → EndBlock +``` ### PreBlock @@ -93,13 +103,13 @@ After BeginBlock, BaseApp iterates over each transaction in the block and runs i #### Step 1: AnteHandler -Configured in a Cosmos SDK chain's `app.go` file, the `AnteHandler` runs first for every transaction. It verifies the transaction's signature, increments the sequence number, deducts the fee, and meters the gas. +Configured in a Cosmos SDK chain's `app.go` file, the `AnteHandler` runs first for every transaction. It verifies the transaction's signature, increments the sequence number, deducts the fee, and meters the gas. {/* todo: link to baseapp#antehandler */} 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 via BaseApp'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. +Each message in a transaction is routed via BaseApp's `MsgServiceRouter` {/* todo: link to baseapp#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. 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. @@ -145,6 +155,12 @@ For each submitted transaction (async): → insert into mempool For every block: + ↓ ABCI PrepareProposal (proposer only) + → select txs from mempool (MaxTxBytes, MaxGas) + → return tx list to CometBFT + ↓ ABCI ProcessProposal (all validators) + → verify txs, check gas limit + → ACCEPT or REJECT ↓ ABCI FinalizeBlock → PreBlock → x//BeginBlock @@ -165,6 +181,8 @@ The hooks that run at each phase (the AnteHandler, BeginBlocker, EndBlocker, and CometBFT drives block processing through ABCI. BaseApp implements ABCI and orchestrates execution. - Transactions are validated in `CheckTx` before entering the mempool +- `PrepareProposal` runs on the proposer to build the final tx set for the block +- `ProcessProposal` runs on all validators to accept or reject the proposed block - Each block is executed inside a single `FinalizeBlock` call - Within `FinalizeBlock`: PreBlock → BeginBlock → transactions → EndBlock - Each transaction runs through AnteHandler → message routing → message execution From a4ee5e6949f1a3f0a0b85eb10173307a5bd2e66d Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:37:30 -0500 Subject: [PATCH 16/36] Update store.mdx --- sdk/v0.53/learn/concepts/store.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/v0.53/learn/concepts/store.mdx b/sdk/v0.53/learn/concepts/store.mdx index 20df256d..05bc7c7c 100644 --- a/sdk/v0.53/learn/concepts/store.mdx +++ b/sdk/v0.53/learn/concepts/store.mdx @@ -18,7 +18,7 @@ State₂ ## The KVStore model -At its lowest level, the Cosmos SDK stores state as **key-value pairs**. Both keys and values are byte arrays. Modules encode structured data into those bytes using **Protocol Buffers**, and decode them back when reading. +At its lowest level, the Cosmos SDK stores state as **key-value pairs**. Both keys and values are byte arrays. Modules encode structured data into those bytes using Protocol Buffers, and decode them back when reading. {/* todo link to encoding page */} From 7f23fcf7135f9b33fae10bc5980e28e8e13e76ea Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Tue, 3 Mar 2026 12:44:37 -0500 Subject: [PATCH 17/36] Update store.mdx --- sdk/v0.53/learn/concepts/store.mdx | 59 +++++++++++++++--------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/sdk/v0.53/learn/concepts/store.mdx b/sdk/v0.53/learn/concepts/store.mdx index 05bc7c7c..64875bf5 100644 --- a/sdk/v0.53/learn/concepts/store.mdx +++ b/sdk/v0.53/learn/concepts/store.mdx @@ -18,7 +18,7 @@ State₂ ## The KVStore model -At its lowest level, the Cosmos SDK stores state as **key-value pairs**. Both keys and values are byte arrays. Modules encode structured data into those bytes using Protocol Buffers, and decode them back when reading. +At its lowest level, the Cosmos SDK stores state as **key-value pairs**. Both keys and values are byte arrays. {/* cosmos/cosmos-sdk store/types/store.go:302 — KVStore = GKVStore[[]byte]; keys and values are both []byte */} Modules encode structured data into those bytes using Protocol Buffers, and decode them back when reading. {/* cosmos/cosmos-sdk codec/proto_codec.go:49 — ProtoCodec.Marshal encodes using gogoproto.Marshal to []byte */} {/* todo link to encoding page */} @@ -33,34 +33,34 @@ key: 0x2 | 20 | cosmos1abc...xyz | uatom value: ProtocolBuffer(1000000) ``` -The key encodes the store prefix, address length, address, and denomination. The value is a Protocol Buffer-encoded amount. See [`x/bank/types/keys.go`](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/x/bank/types/keys.go) for the actual implementation. +The key encodes the store prefix, address length, address, and denomination. {/* cosmos/cosmos-sdk x/bank/types/keys.go:29 — BalancesPrefix = collections.NewPrefix(2); prefix byte 0x02 followed by address and denom */} The value is a Protocol Buffer-encoded amount. {/* cosmos/cosmos-sdk types/collections.go:151 — intValueCodec.Encode calls value.Marshal() (math.Int proto encoding); x/bank/types/keys.go:42 — BalanceValueCodec uses IntValue, storing just the amount (not a full Coin) */} See [`x/bank/types/keys.go`](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/x/bank/types/keys.go) for the actual implementation. -Each module owns its own namespace in the key-value store. +Each module owns its own namespace in the key-value store. {/* cosmos/cosmos-sdk store/types/store.go:416 — KVStoreKey; only the pointer value should ever be used — it functions as a capabilities key, giving each module its own isolated namespace */} ## Multistore -Each module has its own KVStore, and all module stores are mounted inside a [multistore](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/store/rootmulti/store.go) that is committed as a single state root. +Every module has its own KVStore, and all module stores are mounted inside a [multistore](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/store/rootmulti/store.go) that is committed as a single state root. {/* cosmos/cosmos-sdk store/rootmulti/store.go:154 — MountStoreWithDB mounts each module's store by StoreKey; store/rootmulti/store.go:537 — Commit() returns CommitID{Hash: cInfo.Hash()} as the single root */} -Each module can only read and write to its own store through its keeper. Access is gated by a `StoreKey`, which is a typed capability object registered at app startup. Modules that don't hold the key cannot open the store. +A module can only read and write to its own store through its keeper. Access is gated by a `StoreKey`, which is a typed capability object registered at app startup. {/* cosmos/cosmos-sdk store/types/store.go:416 — "Only the pointer value should ever be used - it functions as a capabilities key" */} Modules that don't hold the key cannot open the store. This isolation follows an object-capabilities model: 1. Modules cannot directly mutate another module's state 2. Cross-module interaction must go through exposed keeper methods -When a block finishes executing, the multistore computes a new root hash (the **app hash**) that represents the entire application state. That hash is returned to CometBFT, included in the block header, and is what makes the chain's state verifiable. {/* todo: link to lifecycle page */} +When a block finishes executing, the multistore computes a new root hash (the **app hash**) that represents the entire application state. {/* cosmos/cosmos-sdk store/rootmulti/store.go:537 — Commit() returns CommitID{Hash: cInfo.Hash()} — the root hash over all substores */} That hash is returned to CometBFT, included in the block header, and is what makes the chain's state verifiable. {/* cosmos/cosmos-sdk baseapp/abci.go:999 — res.AppHash = app.workingHash() */} {/* todo: link to lifecycle page */} ## How state is stored (IAVL and commit stores) -Each module's KVStore is backed by a commit store. [See the store spec for more details.](../../build/spec/store/store.mdx) +Each module's KVStore is backed by a commit store. {/* cosmos/cosmos-sdk store/iavl/store.go:31 — var _ types.CommitKVStore = (*Store)(nil) confirms iavl.Store implements CommitKVStore */} [See the store spec for more details.](/sdk/v0.53/build/spec/store/store) -By default, the Cosmos SDK uses [**IAVL**](https://github.com/cosmos/iavl), a versioned AVL Merkle tree. +By default, the Cosmos SDK uses [**IAVL**](https://github.com/cosmos/iavl), a versioned AVL Merkle tree. {/* cosmos/cosmos-sdk store/iavl/store.go:64 — tree := iavl.NewMutableTree(...) — explicitly constructs the IAVL tree as the backing store */} -IAVL gives every read and write of the tree `O(log n)` complexity, meaning the time to read or write a key scales with the height of the tree, not the total number of keys. It also versions state on each block commit, and produces deterministic root hashes that can be used to generate Merkle proofs for light clients. +IAVL gives every read and write of the tree `O(log n)` complexity, meaning the time to read or write a key scales with the height of the tree, not the total number of keys. It also versions state on each block commit, {/* cosmos/cosmos-sdk store/iavl/store.go:134 — Commit() calls st.tree.SaveVersion() to create a new versioned snapshot */} and produces deterministic root hashes that can be used to generate Merkle proofs for light clients. {/* cosmos/cosmos-sdk store/iavl/store.go:402 — getProofFromTree generates Merkle existence/non-existence proofs from the IAVL tree */} Each block commit produces a new tree version with a new root hash: -```go +```text Block 1 Block 2 Block 3 [root h1] [root h2] [root h3] @@ -93,11 +93,11 @@ Beyond the base KVStore, the SDK provides several specialized store wrappers. ### CommitKVStore (persistent store) -This is the main persistent store backed by IAVL. It persists across blocks, produces versioned commits, and contributes to the app hash. +This is the main persistent store backed by IAVL. {/* cosmos/cosmos-sdk store/iavl/store.go:31 — var _ types.CommitKVStore = (*Store)(nil) */} It persists across blocks, produces versioned commits, {/* cosmos/cosmos-sdk store/iavl/store.go:134 — Commit() calls st.tree.SaveVersion() returning a new version+hash */} and contributes to the app hash. {/* cosmos/cosmos-sdk store/rootmulti/store.go:537 — root Commit() aggregates all substore hashes into CommitID.Hash */} ### CacheMultiStore (transaction isolation) -Before executing each transaction, the Cosmos SDK's BaseApp struct creates a cached, copy-on-write view of the multistore. +Before executing each transaction, the Cosmos SDK's BaseApp struct creates a cached, copy-on-write view of the multistore. {/* cosmos/cosmos-sdk baseapp/baseapp.go:662 — cacheTxContext() "returns a new context based off of the provided context with a branched multi-store"; calls ms.CacheMultiStore() */} All writes during that transaction occur in this cached layer: @@ -109,30 +109,30 @@ Multistore → Failure → discard ``` -- If the transaction succeeds, changes are written to the underlying store. -- If the transaction fails, the cache is discarded and no state changes are committed. +- If the transaction succeeds, changes are written to the underlying store. {/* cosmos/cosmos-sdk store/cachemulti/store.go:128 — Write() iterates all substores and calls store.Write() on each, flushing to the parent */} +- If the transaction fails, the cache is discarded and no state changes are committed. {/* cosmos/cosmos-sdk baseapp/baseapp.go:994 — msCache.Write() is only called inside `if err == nil`; on error the CacheMultiStore is abandoned */} This is how transaction atomicity is implemented in the store layer. ### Transient store (ephemeral per block) -The SDK also supports **transient stores** which are reset at the start of each block and discarded at the end of the block. Transient stores are used for temporary per-block data such as counters or intermediate calculations and they do not affect the app hash. +The SDK also supports **transient stores** which are cleared at the end of each block when `Commit()` is called, making them empty for the next block. {/* cosmos/cosmos-sdk store/transient/store.go:62 — Commit() calls ts.Clear(), discarding all data and returning an empty CommitID */} Transient stores are used for temporary per-block data such as counters or intermediate calculations and they do not affect the app hash. ### Gas and trace store wrappers -All store accesses are wrapped with additional behavior by the `GasKVStore` and `TraceKVStore` wrappers. +All store accesses are wrapped with additional behavior by the `GasKVStore` and `TraceKVStore` wrappers. {/* cosmos/cosmos-sdk store/gaskv/store.go:29 — "GStore applies gas tracking to an underlying KVStore"; store/tracekv/store.go:21 — "Store implements the KVStore interface with tracing enabled" */} -- **GasKVStore**: charges gas for each read and write -- **TraceKVStore**: logs each store operation for debugging +- **GasKVStore**: charges gas for each read and write {/* cosmos/cosmos-sdk store/gaskv/store.go:64 — Get consumes ReadCostFlat + ReadCostPerByte; store/gaskv/store.go:76 — Set consumes WriteCostFlat + WriteCostPerByte */} +- **TraceKVStore**: logs each store operation for debugging {/* cosmos/cosmos-sdk store/tracekv/store.go:21 — "Operations are traced on each core KVStore call and written to the underlying io.Writer" */} -Gas is consumed at the store layer, meaning every read and write of a KVStore costs gas, and expensive operations naturally cost more. +Gas is consumed at the store layer, meaning every read and write of a KVStore costs gas, and expensive operations naturally cost more. {/* cosmos/cosmos-sdk store/gaskv/store.go:66 — gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, ...) on every Get call */} {/* todo: link to gas */} ### Prefix store -A **prefix store** wraps a KVStore and automatically prepends a fixed byte prefix to every key. This lets keepers scope their reads and writes to a sub-namespace without manually constructing prefixed keys on every call. +A **prefix store** wraps a KVStore and automatically prepends a fixed byte prefix to every key. {/* cosmos/cosmos-sdk store/prefix/store.go:74 — key() returns cloneAppend(s.prefix, key), prepending the prefix to every key */} This lets keepers scope their reads and writes to a sub-namespace without manually constructing prefixed keys on every call. ```go prefixStore := prefix.NewStore(kvStore, types.KeyPrefix("balances")) @@ -162,20 +162,20 @@ count, _ := k.Counter.Get(ctx) k.Counter.Set(ctx, count+1) ``` -The Collections API defines the storage schema, handles encoding and decoding, ensures consistent key construction, and makes state access type-safe. +The Collections API defines the storage schema, handles encoding and decoding, ensures consistent key construction, and makes state access type-safe. {/* cosmos/cosmos-sdk collections/collections.go:82 — Collection interface defines GetName(), GetPrefix(), ValueCodec() and genesis import/export methods */} -Under the hood, collections still store data in a KVStore. Collections provide a safer abstraction over raw byte keys. See [`collections/collections.go`](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/collections/collections.go) for the base interface definitions. +Under the hood, collections still store data in a KVStore. {/* cosmos/cosmos-sdk collections/map.go:20 — Map struct holds sa func(context.Context) store.KVStore as its backing store accessor */} Collections are used to provide a safer abstraction over raw byte keys. See [`collections/collections.go`](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/collections/collections.go) for the base interface definitions. ## How modules access state Modules typically do not read from or write to the multi-store directly. Instead, each module defines a keeper that manages access to the module’s portion of state. -{/* todo: link to keeper page */} +{/* todo: link to keeper in modules page */} A keeper typically holds: -- the module’s **store key** (an object-capability used to open the module’s `KVStore` from `Context`), -- a **Protocol Buffers codec** used to encode and decode values stored as bytes, +- the module’s **store key** (an object-capability used to open the module’s `KVStore` from `Context`), {/* cosmos/cosmos-sdk store/types/store.go:416 — KVStoreKey; "only the pointer value should ever be used - it functions as a capabilities key" */} +- a **Protocol Buffers codec** used to encode and decode values stored as bytes, {/* cosmos/cosmos-sdk codec/proto_codec.go:49 — ProtoCodec implements BinaryMarshaler using gogoproto.Marshal/Unmarshal */} - **references (interfaces) to other keepers** the module depends on. State access typically flows through the keeper: @@ -197,9 +197,9 @@ func (k Keeper) SetParams(ctx sdk.Context, params types.Params) ## Genesis and chain initialization -Before the first block executes, the chain must start with an initial state called **genesis**, defined in `genesis.json`. Genesis is the first write to the KVStores — it is how every module's state exists before any transaction runs. +Before the first block executes, the chain must start with an initial state called **genesis**, defined in `genesis.json`. {/* cosmos/cosmos-sdk types/module/module.go:491 — ctx.Logger().Info("initializing blockchain state from genesis.json") */} Genesis is the first write to the KVStores — it is how every module's state exists before any transaction runs. -During `InitChain`, BaseApp calls each module's `InitGenesis` to populate its store: +During `InitChain`, BaseApp calls each module's `InitGenesis` to populate its store: {/* cosmos/cosmos-sdk baseapp/abci.go:124 — app.abciHandlers.InitChainer(finalizeState.Context(), req) invokes the module manager's InitGenesis; types/module/module.go:486 — InitGenesis iterates m.OrderInitGenesis calling each module */} ``` genesis.json @@ -211,14 +211,15 @@ Module.InitGenesis KVStores populated ``` -For information onhow modules define their genesis methods (`DefaultGenesis`, `ValidateGenesis`, `InitGenesis`, `ExportGenesis`) and initialization ordering, see [modules](./modules.mdx) and the [lifecycle page](./lifecycle.mdx). +For information on how modules define their genesis methods (`DefaultGenesis`, `ValidateGenesis`, `InitGenesis`, `ExportGenesis`) {/* cosmos/cosmos-sdk types/module/module.go:77 — DefaultGenesis; types/module/module.go:78 — ValidateGenesis; types/module/module.go:194 — ExportGenesis */} and initialization ordering, {/* cosmos/cosmos-sdk types/module/module.go:357 — SetOrderInitGenesis sets the call order for InitGenesis across modules */} see [modules](./modules.mdx) and the [lifecycle page](./lifecycle.mdx). {/* todo: add correct links to modules and lifecycle pages */} -## Further reading +## Next steps For more information on stores, pruning strategies, and store configuration, see the [store spec](/sdk/v0.53/build/spec/store/store). For the full store interface definitions, see [`store/types/store.go`](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/store/types/store.go) in the SDK source. After learning about state, storage, and initialization, the next section explains how data is serialized and encoded so that every validator interprets state identically. +{/* todo: link to next page */} \ No newline at end of file From 4712727b7f387f8445c7ee052485d40518a53d1a Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Tue, 3 Mar 2026 12:44:42 -0500 Subject: [PATCH 18/36] Update lifecycle.mdx --- sdk/v0.53/learn/concepts/lifecycle.mdx | 90 +++++++++++++++----------- 1 file changed, 54 insertions(+), 36 deletions(-) diff --git a/sdk/v0.53/learn/concepts/lifecycle.mdx b/sdk/v0.53/learn/concepts/lifecycle.mdx index d3a4caf2..8144f473 100644 --- a/sdk/v0.53/learn/concepts/lifecycle.mdx +++ b/sdk/v0.53/learn/concepts/lifecycle.mdx @@ -8,39 +8,56 @@ Before building with the Cosmos SDK, it's important to connect the high-level ar A Cosmos SDK application can be conceptually split into layers: -- **[CometBFT](/cometbft) (consensus engine)** — orders and proposes blocks -- **[ABCI](/sdk/v0.53/learn/intro/sdk-app-architecture#abci-application-blockchain-interface) (Application-Blockchain Interface)** — the protocol CometBFT uses to talk to the application -- **SDK application (BaseApp + modules)** — the deterministic state machine that executes transactions -- **Protobuf schemas** — define transactions, messages, state, and query types +- [CometBFT](/cometbft) (consensus engine) — orders and proposes blocks +- [ABCI](/sdk/v0.53/learn/intro/sdk-app-architecture#abci-application-blockchain-interface) (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 + +{/* todo: link to sdk and encoding pages */} This page maps the block and transaction lifecycle back to those layers. ## ABCI overview -**BaseApp** is a struct in the Cosmos SDK that implements the ABCI interface. {/* todo: link to baseapp */} Modules plug into BaseApp and execute logic during these phases. - -```go title="ABCI Flow" -CometBFT - ↓ -ABCI: InitChain (once at genesis) - -For each submitted transaction (async, independent of blocks): - ↓ -ABCI: CheckTx → Mempool - -For every block: - ↓ -ABCI: PrepareProposal (proposer only: select and filter txs) - ↓ -ABCI: ProcessProposal (all validators: accept or reject the block) - ↓ -ABCI: FinalizeBlock - → PreBlock - → BeginBlock - → Execute transactions - → EndBlock - ↓ -ABCI: Commit +CometBFT and the SDK application are two separate processes with distinct responsibilities. +- [CometBFT](/cometbft/v0.38/docs/introduction/intro) handles consensus: ordering transactions, managing validators, and driving block production. +- The [SDK application](/sdk/v0.53/learn/intro/sdk-app-architecture#cosmos-sdk-application) handles state: executing transactions and updating the chain's data. + +The **ABCI** (Application Blockchain Interface) is the protocol that connects them: CometBFT calls ABCI methods on the application to drive each phase of the block lifecycle, and the application responds. + +The **BaseApp** {/* todo: link to baseapp */} struct 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. + +```python ++---------------------+ | +-------------------------+ +| CometBFT | | | SDK Application | +| (Consensus) | ABCI | (BaseApp + modules) | ++---------------------+ | +-------------------------+ + | +InitChain (once) | + Chain start -------------------|------> InitGenesis per module + | +CheckTx (per submitted tx) | + Mempool validation ------------|------> decode · verify · validate + |<------ accept → Mempool + | +PrepareProposal (proposer only) | + Build block proposal ----------|------> select txs (MaxTxBytes, MaxGas) + | +ProcessProposal (all validators) | + Evaluate proposal -------------|------> verify txs → ACCEPT / REJECT + | +FinalizeBlock (per block) | + Execute block -----------------|------> PreBlock hooks + | BeginBlock hooks + | For each tx: + | AnteHandler + | → message routing + | → MsgServer (module logic) + | EndBlock hooks + | Return AppHash +Commit | + Persist state -----------------|------> persist state to disk + |<------ return AppHash ``` ## InitChain (genesis only) @@ -67,19 +84,20 @@ Transactions are sent as raw protobuf-encoded bytes. {/* todo: link to encoding section above */} -During `CheckTx`, BaseApp decodes the transaction, verifies signatures and sequences, validates fees and gas, and performs basic message validation. +During `CheckTx`, the SDK application's BaseApp decodes the transaction, verifies signatures and sequences, validates fees and gas, and performs basic message validation. -No state changes are committed during `CheckTx`. 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. +{/* todo: link to signatures, sequence fees, and gas sections above */} +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 -Called by CometBFT on the block proposer only. BaseApp selects transactions from the mempool respecting the block's `MaxTxBytes` and `MaxGas` limits and returns the final transaction list. No state is written. {/* todo: link to baseapp#prepareproposal */} +Each round, [CometBFT](/cometbft/v0.38/docs/introduction/intro#intro-to-abci) 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. {/* todo: link to baseapp#prepareproposal */} ## ProcessProposal -Called on every validator when it receives the proposed block. BaseApp verifies each transaction and returns `ACCEPT` or `REJECT`. No state is written. After validators acscept the block and consensus is reached, CometBFT calls `FinalizeBlock`. {/* todo: link to baseapp#processproposal */} +Once the other validators receive the proposed block, CometBFT calls `ProcessProposal`. 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`. {/* todo: link to baseapp#processproposal */} ## FinalizeBlock @@ -91,11 +109,11 @@ PreBlock → BeginBlock → transaction execution → EndBlock ### 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.go`), and the application's `ModuleManager` invokes all registered PreBlockers during `FinalizeBlock`. +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.go`), and the application's `ModuleManager` invokes all registered PreBlockers during `FinalizeBlock`. ### 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.go`. +`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.go`. ### Transaction execution @@ -111,7 +129,7 @@ If the AnteHandler fails, the transaction aborts and its messages do not execute Each message in a transaction is routed via BaseApp's `MsgServiceRouter` {/* todo: link to baseapp#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. -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. +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. {/* todo: link to msgserver and keeper sections */} Messages execute sequentially in the order they appear in the transaction. @@ -144,7 +162,7 @@ Across all validators, the block execution is deterministic. Blocks must contain ## Complete lifecycle overview -``` +```go CometBFT ↓ ABCI InitChain BaseApp → x//InitGenesis From 6ef2c5b4716f4ca5ddaa8642fd2816ffea52e71d Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Tue, 3 Mar 2026 12:44:45 -0500 Subject: [PATCH 19/36] Update modules.mdx --- sdk/v0.53/learn/concepts/modules.mdx | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/sdk/v0.53/learn/concepts/modules.mdx b/sdk/v0.53/learn/concepts/modules.mdx index 41c522da..dd65a4f5 100644 --- a/sdk/v0.53/learn/concepts/modules.mdx +++ b/sdk/v0.53/learn/concepts/modules.mdx @@ -20,11 +20,11 @@ A module is a self-contained unit of state and logic. At a high level, a module defines: -- [state](#state): a kv store namespace that contains the module's data -- [messages](#messages): the actions the module allows -- [queries](#queries): read-only access to the module's state +- [State](#state): a kv store namespace that contains the module's data +- [Messages](#messages): the actions the module allows +- [Queries](#queries): read-only access to the module's state - [`MsgServer`](#message-execution-msgserver): validates, applies business logic, and delegates to the keeper -- [keeper](#keeper): the state access layer — the only sanctioned path to the store +- [Keeper](#keeper): the state access layer — the only sanctioned path to the store ### State @@ -216,7 +216,9 @@ Modules may execute logic at specific points in the block lifecycle by implement - `HasEndBlocker` — runs logic at the end of each block {/* cosmos/cosmos-sdk core/appmodule/module.go:83 — HasEndBlocker interface; EndBlock runs after transactions are processed in a block */} - `HasPreBlocker` — runs logic before BeginBlock, used for consensus parameter changes {/* cosmos/cosmos-sdk core/appmodule/module.go:65 — HasPreBlocker returns ResponsePreBlock; IsConsensusParamsChanged() at line 60 signals consensus parameter updates to the caller */} -Modules only implement the hooks they need. {/* cosmos/example x/counter/module.go:77 — BeginBlock comment: "This method is included for example purposes. Not every module needs these methods." */} Most modules implement none. These hooks are invoked during block execution. +Hooks are optional, and modules should only implement the hooks they need. {/* cosmos/example x/counter/module.go:77 — BeginBlock comment: "This method is included for example purposes. Not every module needs these methods." */} These hooks are invoked during block execution by a `ModuleManager` in the `BaseApp`, which calls each registered module's hooks in a configured order. {/* todo: link to baseapp#module-manager */} + +{/* todo: link to baseapp#module-manager — ModuleManager coordinates hook invocation order across all modules */} ### Genesis initialization @@ -235,13 +237,7 @@ During `InitChain`, BaseApp calls each module’s `InitGenesis` to populate its ## Built-in and custom modules -The Cosmos SDK provides many built-in modules, including: - -- `x/auth` -- `x/bank` -- `x/gov` -- `x/distribution` - +The Cosmos SDK provides many built-in modules. Visit the [List of Modules](/sdk/v0.53/build/modules/modules) page for more information. Applications can include any subset of these modules and can define entirely new custom modules. A Cosmos SDK blockchain is ultimately a collection of modules assembled into a single application. This customization of modules and application logic down to the lowest levels of a chain is what makes the Cosmos sdk so flexible and powerful. From 786e6eb632d2df9358d6889fe71a4d0fd4682c91 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:15:32 -0500 Subject: [PATCH 20/36] Update modules.mdx --- sdk/v0.53/learn/concepts/modules.mdx | 46 +++++----------------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/sdk/v0.53/learn/concepts/modules.mdx b/sdk/v0.53/learn/concepts/modules.mdx index dd65a4f5..3b670267 100644 --- a/sdk/v0.53/learn/concepts/modules.mdx +++ b/sdk/v0.53/learn/concepts/modules.mdx @@ -96,54 +96,24 @@ Each module implements a **MsgServer**, which is invoked by `BaseApp`’s messag The `MsgServer` is responsible for: -- Validating the message fields {/* cosmos/example x/counter/keeper/msg_server.go:25 — checks request.GetAdd() >= math.MaxUint64 */} -- Enforcing message-specific business rules {/* cosmos/example x/counter/keeper/msg_server.go:34 — checks params.MaxAddValue > 0 && request.GetAdd() > params.MaxAddValue */} -- Checking authorization when required {/* cosmos/example x/counter/keeper/msg_server.go:76 — checks m.authority != msg.Authority in UpdateParams */} -- Calling keeper methods to perform state transitions {/* cosmos/example x/counter/keeper/msg_server.go:57 — m.counter.Set writes the new count to state */} -- Emitting events {/* cosmos/example x/counter/keeper/msg_server.go:62 — EmitEvent emits "count_increased" with updated count */} -- Returning a response {/* cosmos/example x/counter/keeper/msg_server.go:71 — returns MsgAddResponse{UpdatedCount: newCount} */} +- Checking authorization when required {/* cosmos/example x/counter/keeper/msg_server.go:30 — checks m.authority != msg.Authority in UpdateParams */} +- Delegating to keeper methods that validate inputs, enforce business rules, and perform state transitions {/* cosmos/example x/counter/keeper/msg_server.go:20 — Add delegates to m.AddCount, which validates, charges fees, updates state, and emits events */} +- Returning a response {/* cosmos/example x/counter/keeper/msg_server.go:24 — returns MsgAddResponse{UpdatedCount: newCount} */} -The following is an abridged example from the counter module’s `Add` handler (bank fee charge and metrics omitted for clarity): {/* cosmos/example x/counter/keeper/msg_server.go:24 */} +The following is the counter module’s `Add` handler: {/* cosmos/example x/counter/keeper/msg_server.go:19 */} ```go func (m msgServer) Add(ctx context.Context, request *types.MsgAddRequest) (*types.MsgAddResponse, error) { - if request.GetAdd() >= math.MaxUint64 { - return nil, ErrNumTooLarge - } - - params, err := m.GetParams(ctx) + newCount, err := m.AddCount(ctx, request.GetSender(), request.GetAdd()) if err != nil { return nil, err } - if params.MaxAddValue > 0 && request.GetAdd() > params.MaxAddValue { - return nil, ErrExceedsMaxAdd - } - - sdkCtx := sdk.UnwrapSDKContext(ctx) - count, err := m.counter.Get(ctx) - if err != nil && !errors.Is(err, collections.ErrNotFound) { - return nil, err - } - - newCount := count + request.GetAdd() - - if err = m.counter.Set(ctx, newCount); err != nil { - return nil, err - } - - sdkCtx.EventManager().EmitEvent( - sdk.NewEvent( - "count_increased", - sdk.NewAttribute("count", fmt.Sprintf("%v", newCount)), - ), - ) - return &types.MsgAddResponse{UpdatedCount: newCount}, nil } ``` -`Add` is permissionless — any account can call it. {/* cosmos/example x/counter/keeper/msg_server.go:24 — no authority check in the Add handler body */} In more complex modules, the state transition logic (`counter.Get` / `counter.Set`) is typically extracted into a named keeper method so it can be reused from other entry points (e.g., `BeginBlock`, governance proposals, or other modules). {/* cosmos/example x/counter/keeper/keeper.go:59 — GetParams demonstrates the named keeper method pattern */} +`Add` is permissionless — any account can call it — and the handler itself is intentionally thin. {/* cosmos/example x/counter/keeper/msg_server.go:19 — no authority check; delegates entirely to keeper.AddCount */} All validation (overflow check, `MaxAddValue` limit), fee charging, state mutation, event emission, and telemetry live in the keeper’s `AddCount` method. {/* cosmos/example x/counter/keeper/keeper.go:83 — AddCount validates amount, checks MaxAddValue, charges AddCost, updates counter, emits event, records metric */} This keeps `msg_server.go` focused on message routing and authorization, while the keeper’s named methods can be reused from other entry points such as `BeginBlock` or governance proposals. For privileged messages like `MsgUpdateParams`, the `MsgServer` checks the caller against the stored authority before proceeding: {/* cosmos/example x/counter/keeper/msg_server.go:76 — m.authority != msg.Authority check returns ErrInvalidSigner if mismatched */} @@ -158,7 +128,7 @@ func (m msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) ) } - if err := m.setParams(ctx, msg.Params); err != nil { + if err := m.SetParams(ctx, msg.Params); err != nil { return nil, err } @@ -189,7 +159,7 @@ func (k *Keeper) GetParams(ctx context.Context) (types.Params, error) { } ``` -The `MsgServer` and `QueryServer` both embed the keeper and call its methods. {/* cosmos/example x/counter/keeper/msg_server.go:17 — msgServer struct embeds *Keeper */} Authorization is enforced at the `MsgServer` boundary; keeper methods trust that the caller has already validated the request. +The `MsgServer` and `QueryServer` both embed the keeper and call its methods. {/* cosmos/example x/counter/keeper/msg_server.go:17 — msgServer struct embeds *Keeper */} Authorization is enforced at the `MsgServer` boundary, while domain validation (input checks, business rules, fee charging, state transitions, and event emission) lives in the keeper's named methods such as `AddCount`. This lets keeper methods be safely reused from other entry points like `BeginBlock` or governance proposals. Some messages, like `MsgUpdateParams`, are privileged and may only be executed by a specific address. The `authority` field in the keeper stores that address (typically the governance module account). {/* cosmos/example x/counter/keeper/keeper.go:43 — authority defaults to authtypes.NewModuleAddress(govtypes.ModuleName).String() */} From 5bd51977836b5d891e3e8db3662dabf24cc22b7f Mon Sep 17 00:00:00 2001 From: Evan <87997759+evanorti@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:13:06 -0500 Subject: [PATCH 21/36] Encoding page (#232) * We are writing documentation for the cosmos sdk. THis is the branch an * Update CLAUDE.md * Update store.mdx --- CLAUDE.md | 2 +- sdk/v0.53/learn/concepts/encoding.mdx | 258 ++++++++++++++++++++++++++ sdk/v0.53/learn/concepts/store.mdx | 3 +- 3 files changed, 261 insertions(+), 2 deletions(-) create mode 100644 sdk/v0.53/learn/concepts/encoding.mdx diff --git a/CLAUDE.md b/CLAUDE.md index ed1aa3c9..2dc09416 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -112,4 +112,4 @@ When updating documentation: - Navigation structure must be updated in `docs.json` when adding new pages - Interactive RPC documentation is generated from the source `methods.mdx` file - Test findings in `tests/README.md` track documentation accuracy against implementation -- Use relative imports for snippets and components (e.g., `/snippets/icons.mdx`) \ No newline at end of file +- Use relative imports for snippets and components (e.g., `/snippets/icons.mdx`) diff --git a/sdk/v0.53/learn/concepts/encoding.mdx b/sdk/v0.53/learn/concepts/encoding.mdx new file mode 100644 index 00000000..b46cc192 --- /dev/null +++ b/sdk/v0.53/learn/concepts/encoding.mdx @@ -0,0 +1,258 @@ +--- +title: Encoding +--- + +In the previous section, you learned that state is stored as raw byte arrays in a key-value store and that modules encode structured data into those bytes before writing. This page explains how that encoding works, why the Cosmos SDK chose Protocol Buffers, and what that means for module development. + +## What is Protocol Buffers + +[Protocol Buffers](https://protobuf.dev/) (protobuf) is a language-neutral, binary serialization format developed by Google. You define your data structures in `.proto` files using a schema language, then generate code in your target language from that schema. The generated code handles serialization (converting structured data into bytes) and deserialization (converting bytes back into structured data). + +A simple protobuf message looks like this: + +```proto +message MsgSend { + string from_address = 1; + string to_address = 2; + repeated Coin amount = 3; +} +``` + +Each field has a name, a type, and a field number. The field numbers are what protobuf actually uses during encoding — field names are only present in the schema, not in the serialized bytes. This is why protobuf is compact and why you can rename fields without breaking wire compatibility. + +## Why the Cosmos SDK uses protobuf + +The Cosmos SDK uses protobuf for a fundamental reason: consensus requires determinism. + +Every validator in the network independently executes each block. After executing a block, each validator computes the AppHash, a cryptographic hash of the application state. For validators to agree on the AppHash, they must all produce exactly the same bytes for every piece of state they write. + +Protobuf satisfies this requirement. Given the same input, protobuf always produces the same bytes. There is no randomness in field ordering, no optional whitespace, and no locale-specific formatting. Every validator encoding the same data with protobuf produces an identical byte sequence. + +Beyond determinism, protobuf provides: + +- **Compact encoding**: binary wire format is smaller than JSON or XML, which matters for transaction throughput and block size. +- **Schema evolution**: fields can be added or deprecated without breaking existing clients, which is critical for chain upgrades. +- **Code generation**: `.proto` files generate Go structs, gRPC service stubs, and REST gateway handlers automatically. +- **Cross-language support**: clients in any language can interact with the chain by generating code from the same `.proto` files. + +## Binary and JSON encoding + +The Cosmos SDK uses protobuf in two encoding modes: + +**Binary encoding** is the default for everything that participates in consensus: transactions written to blocks, state stored in KV stores, and genesis data. Binary encoding is compact and deterministic. When a transaction is broadcast to the network, it travels as protobuf binary. When a module writes state, it serializes values to protobuf binary before calling `Set` on the store. + +**JSON encoding** is used for human-readable output: the CLI, gRPC-gateway REST endpoints, and off-chain tooling. The Cosmos SDK uses protobuf's JSON encoding (`ProtoMarshalJSON`) rather than standard Go JSON, which preserves field names from the `.proto` schema and handles special types like `Any` correctly. {/* cosmos/cosmos-sdk codec/json.go:12 — var defaultJM = &jsonpb.Marshaler{OrigName: true, EmitDefaults: true, AnyResolver: nil} — OrigName:true preserves proto field names from the schema */} {/* cosmos/cosmos-sdk codec/proto_codec.go:149 — return ProtoMarshalJSON(o, pc.interfaceRegistry) — MarshalJSON delegates to ProtoMarshalJSON with the interface registry */} + +The most important rule: **binary encoding is consensus-critical**. Two validators must produce identical binary bytes for identical data. JSON is only used where humans or external clients need to read the data; it never influences the AppHash. + +```text +Consensus-critical path Human-readable path +───────────────────────── ───────────────────────── +Transaction bytes (binary) CLI output (JSON) +State KV values (binary) REST API responses (JSON) +Genesis KV state (binary) Block explorers (JSON) +``` + +## How protobuf is used in modules + +Every piece of data that crosses a module boundary, gets stored, or travels over the wire is defined in a `.proto` file and serialized with protobuf. + +**Messages and transactions**: Each module defines its transaction messages in a `tx.proto` file. The `MsgSend` definition above is an example. When a user submits a transaction, the SDK serializes the transaction body (including its messages) to binary using protobuf before broadcasting it. {/* cosmos/cosmos-sdk x/auth/tx/encoder.go:27 — return proto.Marshal(raw) — DefaultTxEncoder serializes the transaction to protobuf binary bytes for broadcast */} + +**Queries**: Modules define their query services in `query.proto`. Request and response types are protobuf messages. The SDK uses gRPC for queries, and gRPC uses protobuf as its serialization format by definition. + +**State types**: Data stored in the KV store is protobuf-encoded. A module that stores a custom struct first marshals it to bytes using the codec, then writes those bytes to the store. When reading, it unmarshals the bytes back into the struct. + +**Genesis**: Genesis state is defined in `genesis.proto`. `InitGenesis` and `ExportGenesis` use protobuf to deserialize genesis state from `genesis.json` and serialize it back. {/* cosmos/cosmos-sdk x/auth/module.go:158 — cdc.MustUnmarshalJSON(data, &genesisState) — InitGenesis uses the JSON codec to unmarshal JSON genesis data into a protobuf-typed GenesisState struct */} {/* cosmos/cosmos-sdk x/auth/module.go:166 — return cdc.MustMarshalJSON(gs) — ExportGenesis serializes the genesis state back to JSON using the codec */} + +A concrete example shows how a module reads and writes typed state as bytes: {/* cosmos/cosmos-sdk codec/proto_codec.go:49 — func (pc *ProtoCodec) Marshal(o gogoproto.Message) ([]byte, error) — the Marshal method called as k.cdc.Marshal() in keeper code */} {/* cosmos/cosmos-sdk codec/proto_codec.go:78 — func (pc *ProtoCodec) Unmarshal(bz []byte, ptr gogoproto.Message) error — the Unmarshal method called as k.cdc.Unmarshal() in keeper code */} + +```go +// write: marshal the coin amount to bytes, then set in store +bz, err := k.cdc.Marshal(&amount) +store.Set(key, bz) + +// read: get bytes from store, unmarshal back to coin +var amount sdk.Coin +bz := store.Get(key) +k.cdc.Unmarshal(bz, &amount) +``` + +The codec (`k.cdc`) is the protobuf codec described in the next section. + +## The codec and interface registry + +The Cosmos SDK wraps protobuf in a **codec** that modules use for marshaling and unmarshaling. The primary implementation is [`ProtoCodec`](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/codec/proto_codec.go), which calls protobuf's `Marshal` and `Unmarshal` under the hood. {/* cosmos/cosmos-sdk codec/proto_codec.go:56 — return gogoproto.Marshal(o) — Marshal calls gogoproto.Marshal to produce binary bytes */} {/* cosmos/cosmos-sdk codec/proto_codec.go:79 — err := gogoproto.Unmarshal(bz, ptr) — Unmarshal calls gogoproto.Unmarshal to decode binary bytes */} + +```go +type ProtoCodec struct { + interfaceRegistry types.InterfaceRegistry +} + +func (pc *ProtoCodec) Marshal(o ProtoMarshaler) ([]byte, error) +func (pc *ProtoCodec) Unmarshal(bz []byte, ptr ProtoMarshaler) error +``` + +Keepers hold a reference to the codec and use it to encode and decode state: + +```go +type Keeper struct { + cdc codec.BinaryCodec + store storetypes.StoreKey +} +``` + +The codec is initialized once at app startup and passed to each keeper during initialization. + +### Interface types and `Any` + +Protobuf is strongly typed. You cannot store a field as "some implementation of an interface" directly in a protobuf message. The Cosmos SDK solves this using protobuf's [`google.protobuf.Any`](https://protobuf.dev/programming-guides/proto3/#any), which wraps an arbitrary message type alongside a URL that identifies what type it contains. + +`Any` is used anywhere the SDK needs to serialize a value whose concrete type is not known at compile time. The most common example is public keys. An account might use a secp256k1 key, an ed25519 key, or a multisig key. The `BaseAccount` stores the public key as `Any`: {/* cosmos/cosmos-sdk x/auth/types/auth.pb.go:34 — PubKey *any.Any — the generated Go struct stores pub_key as *any.Any, not a concrete type */} + +```proto +message BaseAccount { + string address = 1; + google.protobuf.Any pub_key = 2; + uint64 account_number = 3; + uint64 sequence = 4; +} +``` + +The `Any` field holds the serialized public key bytes plus a type URL like `/cosmos.crypto.secp256k1.PubKey`. When the SDK reads the account, it uses the type URL to look up the concrete Go type, then unmarshals the bytes into that type. + +This lookup is handled by the **interface registry**. + +### Interface registry + +The [`InterfaceRegistry`](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/codec/types/interface_registry.go) is a runtime map from type URLs to Go types. {/* cosmos/cosmos-sdk codec/types/interface_registry.go:115 — type interfaceRegistry struct { ... typeURLMap map[string]reflect.Type ... } — the registry struct definition with its typeURLMap field */} When the SDK encounters an `Any` value, it queries the registry with the type URL to find the concrete Go type, then uses protobuf to unmarshal the bytes. {/* cosmos/cosmos-sdk codec/types/interface_registry.go:395 — typ, found := registry.typeURLMap[typeURL] — Resolve looks up the concrete Go type by type URL */} + +```text +Any { type_url, value_bytes } + ↓ + InterfaceRegistry.Resolve(type_url) + ↓ + concrete Go type + ↓ + proto.Unmarshal(value_bytes, concreteType) +``` + +{/* cosmos/cosmos-sdk codec/types/interface_registry.go:394 — func (registry *interfaceRegistry) Resolve(typeURL string) (proto.Message, error) — the public Resolve method that the diagram above represents */} + +Without the interface registry, the SDK cannot decode `Any` values. This is why types must be explicitly registered before they can be deserialized. + +## Registering interface implementations + +Because the interface registry is a runtime lookup table, every concrete type that implements an SDK interface must be registered before the application starts. This is done with `RegisterInterfaces`: + +```go +// in codec registration, typically in module.go or types/codec.go +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*cryptotypes.PubKey)(nil), + &secp256k1.PubKey{}, + &ed25519.PubKey{}, + ) +} +``` + +This tells the registry: "a `PubKey` interface can be a `secp256k1.PubKey` or an `ed25519.PubKey`." {/* cosmos/cosmos-sdk crypto/codec/proto.go:13 — crypto module's RegisterInterfaces registers ed25519.PubKey, secp256k1.PubKey, and multisig.LegacyAminoPubKey under the cosmos.crypto.PubKey interface */} If a type is used in an `Any` field anywhere in the application and is not registered, the codec will fail to unmarshal it and return an error. {/* cosmos/cosmos-sdk codec/types/interface_registry.go:397 — return nil, fmt.Errorf("unable to resolve type URL %s", typeURL) — Resolve returns an error when the type URL has not been registered */} + +Each module calls `RegisterInterfaces` during app initialization, and `app.go` calls these registration functions through the module manager when building the app. {/* cosmos/cosmos-sdk types/module/module.go:120 — func (bm BasicManager) RegisterInterfaces(registry types.InterfaceRegistry) — iterates all modules calling m.RegisterInterfaces(registry) for each */} Custom types that implement SDK interfaces must follow the same pattern. + +## Proto-to-code generation workflow + +Writing `.proto` files produces `.pb.go` files through a code generation step. The generated Go code contains struct definitions, marshal/unmarshal methods, and gRPC service stubs. You never edit these generated files directly. + +The workflow is: + +**1. Write the `.proto` file** + +Proto files for a module live in the `proto/` directory at the repository root: + +``` +proto/myapp/mymodule/v1/ +├── tx.proto # message types (MsgAdd, MsgAddResponse, ...) +├── query.proto # query service (QueryCount, ...) +├── state.proto # on-chain state types +└── genesis.proto # genesis state +``` + +A message definition: + +```proto +syntax = "proto3"; +package myapp.mymodule.v1; + +message MsgAdd { + string sender = 1; + uint64 add = 2; +} + +message MsgAddResponse { + uint64 updated_count = 1; +} + +service Msg { + rpc Add(MsgAdd) returns (MsgAddResponse); +} +``` + +**2. Run code generation** + +```bash +make proto-gen +``` + +This runs `buf` (or `protoc` with plugins) against the `.proto` files and produces Go code under the module's `types/` directory: + +``` +x/mymodule/types/ +├── tx.pb.go # generated: MsgAdd, MsgAddResponse, Marshal/Unmarshal methods +├── query.pb.go # generated: query request/response types +├── query.pb.gw.go # generated: gRPC-gateway REST handlers +└── state.pb.go # generated: on-chain state types +``` + +**3. Use the generated types** + +The generated structs implement `proto.Message` and can be passed directly to the codec for marshaling, registered with the interface registry, and used in keeper methods and message handlers: + +```go +// handler receives the generated type +func (m msgServer) Add(ctx context.Context, req *types.MsgAdd) (*types.MsgAddResponse, error) { + count, err := m.AddCount(ctx, req.Sender, req.Add) + if err != nil { + return nil, err + } + return &types.MsgAddResponse{UpdatedCount: count}, nil +} +``` + +The generated gRPC service stub is registered with BaseApp's message router, connecting the handler to the transaction execution pipeline automatically. + +## Encoding in context + +Every layer of the Cosmos SDK depends on encoding: + +``` +Transaction (binary protobuf) + ↓ broadcast over p2p +CometBFT + ↓ passes raw bytes to application +BaseApp + ↓ decodes transaction, extracts messages +Module MsgServer + ↓ processes message, calls keeper +Keeper + ↓ marshals state value to bytes +KVStore (raw bytes) + ↓ committed to disk +AppHash (Merkle root over all KV bytes) +``` + +Because every step uses protobuf, every step is deterministic. Two validators executing the same transactions always produce the same bytes at every layer, and therefore always arrive at the same AppHash. + +For more details on the encoding subsystem and advanced codec usage, see the [Encoding reference](/sdk/v0.53/learn/advanced/encoding). + +The next section explains the runtime execution environment that modules operate within: `sdk.Context`, gas metering, and events. diff --git a/sdk/v0.53/learn/concepts/store.mdx b/sdk/v0.53/learn/concepts/store.mdx index 64875bf5..e7f323af 100644 --- a/sdk/v0.53/learn/concepts/store.mdx +++ b/sdk/v0.53/learn/concepts/store.mdx @@ -20,8 +20,9 @@ State₂ At its lowest level, the Cosmos SDK stores state as **key-value pairs**. Both keys and values are byte arrays. {/* cosmos/cosmos-sdk store/types/store.go:302 — KVStore = GKVStore[[]byte]; keys and values are both []byte */} Modules encode structured data into those bytes using Protocol Buffers, and decode them back when reading. {/* cosmos/cosmos-sdk codec/proto_codec.go:49 — ProtoCodec.Marshal encodes using gogoproto.Marshal to []byte */} -{/* todo link to encoding page */} +See [Encoding] for details on how modules serialize data into bytes. +{/* todo: add link to encoding page */} The following example shows a conceptual example of how the bank module stores balances: ```go From ee13b9bac55b709f1cb8ed9d8a75b71b2d1b1641 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:32:52 -0500 Subject: [PATCH 22/36] Create baseapp.mdx --- sdk/v0.53/learn/concepts/baseapp.mdx | 150 +++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 sdk/v0.53/learn/concepts/baseapp.mdx diff --git a/sdk/v0.53/learn/concepts/baseapp.mdx b/sdk/v0.53/learn/concepts/baseapp.mdx new file mode 100644 index 00000000..e39cee3e --- /dev/null +++ b/sdk/v0.53/learn/concepts/baseapp.mdx @@ -0,0 +1,150 @@ +--- +title: BaseApp Overview +--- + +In the previous sections, you learned how modules define business logic and state, and how an SDK application is structured as a codebase. This page explains how transactions actually execute: BaseApp is the engine that connects CometBFT's consensus calls to module execution. + +## What BaseApp is + +**BaseApp** is the Go struct at the core of every Cosmos SDK application. It implements the ABCI (Application Blockchain Interface) — the protocol that CometBFT uses to communicate with the application — and translates those calls into module-level operations: running block hooks, routing messages to handlers, managing state, and computing the app hash. + +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. {/* cosmos/cosmos-sdk baseapp/baseapp.go:63 — type BaseApp struct { ... } — the complete BaseApp struct */} + +## Architectural position + +BaseApp sits between CometBFT and the modules: + +``` +CometBFT (consensus engine) + ↓ ABCI (InitChain, CheckTx, FinalizeBlock, Commit, ...) +BaseApp + ↓ orchestrates block execution +ModuleManager + ↓ dispatches to individual modules +Modules (x/auth, x/bank, x/staking, ...) + ↓ read/write +State (KVStores) +``` + +CometBFT drives the block lifecycle by calling ABCI methods on BaseApp. BaseApp handles each call, delegating to the ModuleManager and routing messages to the appropriate module handlers. Modules contain the business logic. KVStores hold the resulting state. + +## Key fields + +The `BaseApp` struct holds references to everything needed to run a chain: + +```go +type BaseApp struct { + cms storetypes.CommitMultiStore // main (uncached) state store + storeLoader StoreLoader // loads stores at startup + msgServiceRouter *MsgServiceRouter // routes messages to handlers + anteHandler sdk.AnteHandler // pre-execution middleware + // ... additional fields +} +``` + +- **`cms` (CommitMultiStore)**: the root state store. All module substores are mounted here, and all state reads and writes during block execution pass through it. {/* cosmos/cosmos-sdk baseapp/baseapp.go:69 — cms storetypes.CommitMultiStore // Main (uncached) state */} +- **`storeLoader`**: a function that opens and mounts the individual module stores at application startup. {/* cosmos/cosmos-sdk baseapp/baseapp.go:71 — storeLoader StoreLoader — function that loads stores from disk */} +- **`msgServiceRouter`**: routes each message in a transaction to the correct module's `MsgServer` handler. {/* cosmos/cosmos-sdk baseapp/baseapp.go:73 — msgServiceRouter *MsgServiceRouter — routes message type URLs to registered handlers */} +- **`anteHandler`**: runs before message execution to handle cross-cutting concerns: signature verification, sequence validation, and fee deduction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:79 — anteHandler sdk.AnteHandler — pre-execution middleware set via SetAnteHandler */} + +## Coordinating transaction execution + +When CometBFT calls `FinalizeBlock`, BaseApp runs the full block execution pipeline: {/* cosmos/cosmos-sdk baseapp/abci.go:868 — func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) — FinalizeBlock entry point */} + +``` +FinalizeBlock + ├─ PreBlock → module pre-block hooks + ├─ BeginBlock → module begin-block hooks + ├─ For each transaction: + │ ├─ AnteHandler (signature verification, fee deduction, gas setup) + │ ├─ Message routing and execution + │ └─ Commit or revert (atomic per-transaction) + └─ EndBlock → module end-block hooks + → returns AppHash +``` + +**PreBlock** runs first, 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. {/* cosmos/cosmos-sdk baseapp/abci.go:757 — PreBlock called at the start of internalFinalizeBlock, before BeginBlock */} + +**BeginBlock** runs after PreBlock and handles per-block housekeeping: minting inflation rewards, distributing staking rewards, resetting per-block counters. {/* cosmos/cosmos-sdk baseapp/abci.go:762 — BeginBlock called after PreBlock, before transaction iteration */} + +**Transactions** execute one at a time in the order they appear in the block. For each transaction, the AnteHandler runs first, then each message is routed and executed. + +**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. {/* cosmos/cosmos-sdk baseapp/abci.go:802 — EndBlock called after all transactions have executed */} + +After `FinalizeBlock` completes, BaseApp computes and returns the app hash, which is the Merkle root of all committed state. {/* cosmos/cosmos-sdk baseapp/abci.go:903 — res.AppHash = app.workingHash() — app hash computed and returned in the FinalizeBlock response */} When CometBFT subsequently calls `Commit`, BaseApp persists the state changes to disk. {/* cosmos/cosmos-sdk baseapp/abci.go:935 — func (app *BaseApp) Commit() — persists finalized state to disk */} + +## Message routing + +When a transaction contains messages, BaseApp routes each one to the appropriate module handler using the `MsgServiceRouter`. + +```go +type MsgServiceRouter struct { + routes map[string]MsgServiceHandler + // ... +} +``` +{/* cosmos/cosmos-sdk baseapp/msg_service_router.go:28 — type MsgServiceRouter struct { routes map[string]MsgServiceHandler; ... } */} + +The routing process has three steps: + +1. **Registration**: During app startup, each module calls `RegisterService`, which registers its message handlers keyed by message type URL (e.g., `/cosmos.bank.v1beta1.MsgSend`). {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:70 — RegisterService iterates service methods and registers handlers in the routes map keyed by message type URL */} +2. **Lookup**: At execution time, `Handler` looks up the registered handler for the incoming message's type URL. {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:53 — func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler — returns handler by type URL from routes map */} +3. **Execution**: The retrieved handler invokes the module's `MsgServer` implementation, which validates inputs, applies business rules, and updates state through the keeper. + +This routing is entirely type-URL-based. Modules do not need to know about each other at the routing level — BaseApp is the neutral coordinator. + +## 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: {/* cosmos/cosmos-sdk baseapp/baseapp.go:328 — MountKVStores mounts IAVL or DB-backed stores for each registered KV store key */} + +```go +app.MountKVStores(keys) +``` + +Before executing each transaction, 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. This is how transaction atomicity is implemented: all messages in a transaction either all commit or none do. + +## AnteHandler integration + +The `AnteHandler` is middleware that runs before any message in a transaction executes. It is responsible for: + +- Verifying cryptographic signatures +- Validating and incrementing the account sequence number +- Deducting transaction fees from the signer's account +- Setting up the gas meter for the transaction + +The AnteHandler is configured at app startup: {/* cosmos/cosmos-sdk baseapp/options.go:229 — func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) — sets the AnteHandler; panics if called after app is sealed */} + +```go +app.SetAnteHandler(ante.NewAnteHandler(ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + SignModeHandler: app.txConfig.SignModeHandler(), + // ... +})) +``` + +If the AnteHandler fails for any reason, the transaction is rejected and its messages never execute. Gas fees for the consumed gas may still be charged, but no state changes from the transaction body are committed. + +## CheckTx and mempool validation + +Before a transaction reaches block execution, it goes through `CheckTx`. BaseApp runs a lightweight version of the AnteHandler during `CheckTx` to validate signatures, check sequence numbers, and verify fees. Transactions that fail `CheckTx` are rejected and do not enter the mempool. + +`CheckTx` does not execute messages and does not write state. It exists solely to protect the mempool from invalid or malformed transactions before they consume block space. + +## Why BaseApp exists + +BaseApp provides shared execution infrastructure so that every Cosmos SDK application gets the same ABCI lifecycle, transaction processing pipeline, and store management automatically. Without it, every chain would need to independently implement signature verification, gas metering, message routing, block hook orchestration, and state commitment. + +By embedding BaseApp and configuring it with modules, a chain inherits the full execution engine and only needs to define its application-specific business logic in its modules. + +## Summary + +BaseApp is the execution engine of a Cosmos SDK chain: + +``` +CometBFT → ABCI → BaseApp → Modules → State +``` + +It implements ABCI, coordinates the block lifecycle (PreBlock → BeginBlock → transactions → EndBlock → Commit), routes messages to module handlers via the `MsgServiceRouter`, manages the multistore, and runs the AnteHandler before each transaction. + +The next section explains `app.go`: how BaseApp is instantiated, configured, and wired with modules to produce a complete, running chain. From eb2fb16b3f414380d21ac838004d7ef5c73c729f Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:32:54 -0500 Subject: [PATCH 23/36] Create context-gas-and-events.mdx --- .../learn/concepts/context-gas-and-events.mdx | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 sdk/v0.53/learn/concepts/context-gas-and-events.mdx diff --git a/sdk/v0.53/learn/concepts/context-gas-and-events.mdx b/sdk/v0.53/learn/concepts/context-gas-and-events.mdx new file mode 100644 index 00000000..93ce3b07 --- /dev/null +++ b/sdk/v0.53/learn/concepts/context-gas-and-events.mdx @@ -0,0 +1,158 @@ +--- +title: Execution Context, Gas, and Events +--- + +In the previous section, you learned how data is serialized and why every validator must encode state identically. This page covers the runtime environment that modules execute within: the context object that carries block metadata and state access, the gas system that limits computation, and the event system that allows modules to emit observable signals. + +## What is `sdk.Context` + +Every message handler, keeper method, and block hook in the Cosmos SDK receives an `sdk.Context`. It is the execution environment for a single unit of work — a transaction, a query, or a block hook — and carries everything that code needs to read state, emit events, and consume gas. + +The `Context` struct is defined in `types/context.go`: {/* cosmos/cosmos-sdk types/context.go:39 — type Context struct { ... } */} + +```go +type Context struct { + ms storetypes.MultiStore + chainID string + gasMeter storetypes.GasMeter + blockGasMeter storetypes.GasMeter + eventManager EventManagerI + // ... additional fields +} +``` + +Context is a value type, not a pointer. It is passed by value and mutated through `With*` methods that return a new copy. This means a module can safely derive a sub-context (for example, with a different gas meter) without affecting the caller's context. + +### Block metadata + +Context exposes read-only access to the current block's metadata: + +- `ctx.BlockHeight()` returns the current block number. {/* cosmos/cosmos-sdk types/context.go:74 — func (c Context) BlockHeight() int64 { return c.header.Height } */} +- `ctx.BlockTime()` returns the block's timestamp. {/* cosmos/cosmos-sdk types/context.go:75 — func (c Context) BlockTime() time.Time { return c.header.Time } */} +- `ctx.ChainID()` returns the chain identifier string. {/* cosmos/cosmos-sdk types/context.go:76 — func (c Context) ChainID() string { return c.chainID } */} + +These values are populated by BaseApp from the block header provided by CometBFT before any block logic runs. Modules read them to implement time-dependent logic (for example, checking whether a vesting period has elapsed) or to tag events with the block height. + +### Context and state access + +State is accessed through context. The context holds a reference to the multistore, and each keeper opens its own store through the context: + +```go +func (k Keeper) GetCount(ctx context.Context) (uint64, error) { + return k.counter.Get(ctx) +} +``` + +The keeper does not hold a direct reference to the live multistore; it opens its module's store from the context on each call. This is why context must be passed to every keeper method — it is the gateway to the current block's state, the gas meter, and the event manager for that execution unit. + +## Gas metering + +### What gas measures + +Gas is a unit of computation. In the Cosmos SDK, gas accounts for both computation and state access. Every store read, store write, and iterator step costs gas. Complex computations — such as signature verification in the AnteHandler — also cost gas. + +The gas system exists to prevent abuse. Without a gas limit, a single transaction could exhaust a node's resources with an unbounded computation or an unindexed state scan. + +### Gas limit and the transaction gas meter + +Every transaction specifies a gas limit in its `auth_info.fee.gas_limit` field. {/* cosmos/cosmos-sdk proto/cosmos/tx/v1beta1/tx.proto:235 — uint64 gas_limit = 2; — the gas_limit field in the Fee message of AuthInfo */} When BaseApp begins executing a transaction, it creates a `GasMeter` initialized with that limit and attaches it to the context. {/* cosmos/cosmos-sdk types/context.go:221 — func (c Context) WithGasMeter(meter storetypes.GasMeter) Context — used by BaseApp to attach the transaction gas meter to context */} + +The `GasMeter` interface provides two key methods: {/* cosmos/cosmos-sdk store/types/gas.go:40 — type GasMeter interface { GasConsumed() Gas; ConsumeGas(amount Gas, descriptor string); ... } */} + +```go +type GasMeter interface { + GasConsumed() Gas + ConsumeGas(amount Gas, descriptor string) + // ... +} +``` + +`GasConsumed` returns the total gas used so far in the current execution unit. {/* cosmos/cosmos-sdk store/types/gas.go:41 — GasConsumed() Gas — returns total gas consumed so far */} `ConsumeGas` adds to the running total and panics with `ErrorOutOfGas` if consumption exceeds the limit. {/* cosmos/cosmos-sdk store/types/gas.go:45 — ConsumeGas(amount Gas, descriptor string) — adds amount to consumed; panics with ErrorOutOfGas if limit exceeded */} {/* cosmos/cosmos-sdk store/types/gas.go:29 — type ErrorOutOfGas struct { Descriptor string } — the error type panicked when gas is exhausted */} + +### How gas is consumed + +Gas is consumed automatically at the store layer. Every read and write through the `GasKVStore` wrapper charges gas before delegating to the underlying store: + +- A `Get` (store read) charges a flat read cost plus a per-byte cost for the key and value. {/* cosmos/cosmos-sdk store/gaskv/store.go:35 — gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, ...) on every Get call */} +- A `Set` (store write) charges a flat write cost plus a per-byte cost for the key and value. {/* cosmos/cosmos-sdk store/gaskv/store.go:49 — gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, ...) on every Set call */} + +Modules do not need to manually track gas for ordinary state access — the store layer handles it automatically. Modules call `ctx.GasMeter().ConsumeGas(...)` directly only for computation costs that are not captured by store operations (for example, a module that performs a cryptographic operation outside the store). + +### When gas runs out + +If gas is exhausted during execution, `ConsumeGas` panics with `ErrorOutOfGas`. {/* cosmos/cosmos-sdk store/types/gas.go:106 — basicGasMeter.ConsumeGas panics with ErrorOutOfGas{descriptor} when g.consumed > g.limit */} BaseApp recovers from this panic, discards all state changes from the transaction, and returns an error to the user. Fees may still be charged for the gas consumed up to the point of failure, but none of the transaction's state changes are committed. + +### Block gas limit + +In addition to the per-transaction gas meter, there is a block-level gas meter that tracks total gas consumed by all transactions in a block. {/* cosmos/cosmos-sdk types/context.go:39 — blockGasMeter storetypes.GasMeter field in Context struct tracks block-level gas */} The block gas limit prevents a single block from consuming unbounded computation. If a transaction would cause the block's gas total to exceed the limit, it is excluded from the block. + +## Events + +### What events are + +Events are observable signals emitted during transaction and block execution. A module emits events to describe what happened: tokens were transferred, a validator was slashed, a governance proposal passed. Events carry structured key-value data alongside a type string. + +Events are not part of consensus state. They are not stored in the KVStore, do not affect the app hash, and are not required for deterministic execution. Instead, they are collected by BaseApp and included in the block result, where indexers, explorers, and relayers consume them. + +### EventManager + +Modules emit events through the `EventManager`, which is attached to the context: {/* cosmos/cosmos-sdk types/context.go:87 — func (c Context) EventManager() EventManagerI { return c.eventManager } */} + +The `EventManager` is created fresh for each transaction and collects all events emitted during that execution. {/* cosmos/cosmos-sdk types/events.go:34 — type EventManager struct { events Events } — holds the slice of events accumulated during execution */} {/* cosmos/cosmos-sdk types/events.go:38 — func NewEventManager() *EventManager — creates an EventManager with an empty events slice */} + +### Emitting events + +Modules emit events using `EmitEvent` or `EmitTypedEvent`: + +```go +// emit an untyped event +ctx.EventManager().EmitEvent(sdk.NewEvent( + "increment", + sdk.NewAttribute("new_count", strconv.FormatUint(newCount, 10)), +)) +``` + +`EmitEvent` appends a raw key-value event to the manager's accumulated list. {/* cosmos/cosmos-sdk types/events.go:46 — func (em *EventManager) EmitEvent(event Event) — appends the event to em.events */} + +For events backed by protobuf message types, `EmitTypedEvent` serializes the message's fields into event attributes automatically: {/* cosmos/cosmos-sdk types/events.go:62 — func (em *EventManager) EmitTypedEvent(tev proto.Message) error — serializes proto message into an Event with attributes derived from its fields */} + +```go +ctx.EventManager().EmitTypedEvent(&types.EventCounterIncremented{ + NewCount: newCount, +}) +``` + +Using `EmitTypedEvent` is the modern approach. It provides type safety and makes the event schema explicit through proto definitions, allowing clients to deserialize events back into typed structs. + +### Who consumes events + +Events are consumed outside the node: + +- **Block explorers** index events to show users what happened in a transaction (which tokens moved, which validator was slashed, which proposal passed). +- **Relayers** (IBC) subscribe to specific event types to detect packet sends and acknowledgments. +- **Indexers and off-chain services** build queryable databases of chain activity from event streams. +- **Wallets and UIs** display event data to users as transaction receipts. + +Events are included in the block result that CometBFT returns after each block. They are not replayed or reprocessed; once a block is finalized, its events are fixed. + +## Putting it together + +During transaction execution, context, gas, and events work together as the runtime layer: + +``` +BaseApp creates Context for the transaction + ↓ +AnteHandler runs + → signature verification, fee deduction, gas meter initialized + ↓ +Message handler runs + → each store read/write consumes gas via GasKVStore + → module logic emits events via EventManager + ↓ +If gas exhausted → panic → state reverted, fees charged for gas consumed +If execution succeeds → state changes committed, events returned in block result +``` + +The context carries the gas meter and event manager into every keeper call. Gas is consumed transparently at the store layer. Events accumulate and are returned as part of the block result once execution completes. + +The next section explains how an SDK application is structured as a codebase: where modules live, what goes in `app/`, and how all the pieces are assembled. From 4a60d73b7dd716c290edc021b32c8664f4b41206 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:32:56 -0500 Subject: [PATCH 24/36] Create sdk.mdx --- sdk/v0.53/learn/concepts/sdk.mdx | 135 +++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 sdk/v0.53/learn/concepts/sdk.mdx diff --git a/sdk/v0.53/learn/concepts/sdk.mdx b/sdk/v0.53/learn/concepts/sdk.mdx new file mode 100644 index 00000000..2479857d --- /dev/null +++ b/sdk/v0.53/learn/concepts/sdk.mdx @@ -0,0 +1,135 @@ +--- +title: Intro to SDK Structure +--- + +Before writing a module or chain, it helps to understand how the Cosmos SDK organizes code and how the pieces connect. This page maps the directory structure of an SDK application, explains what lives inside a module, and shows how modules are assembled into a running application. + +## What is an SDK application + +A Cosmos SDK application is a Go binary that implements a deterministic state machine. It runs alongside CometBFT inside a single daemon process. CometBFT drives consensus; the SDK application executes transactions and maintains state. + +Every SDK application is composed of three main elements: + +- **BaseApp**: the execution engine that implements ABCI and orchestrates transaction processing +- **Modules**: self-contained units of business logic, state, messages, and queries +- **`app.go`**: the wiring layer that instantiates BaseApp, registers modules, and configures the application at startup + +These three elements are covered in depth in the next two sections. This page focuses on how they are organized in the codebase. + +## Repository structure + +An SDK application repository follows a conventional layout: + +``` +myapp/ +├── app/ +│ └── app.go # Application wiring: creates BaseApp, registers modules +├── cmd/ +│ └── main.go # Node binary entrypoint +├── x/ +│ ├── mymodule/ # Custom module +│ └── ... +└── proto/ + └── myapp/ + └── mymodule/ + └── v1/ + ├── tx.proto + ├── query.proto + ├── state.proto + └── genesis.proto +``` + +Each directory has a distinct responsibility: + +- **`x/`** contains the modules. Each subdirectory is a separate, self-contained module. Built-in Cosmos SDK modules (`x/auth`, `x/bank`, `x/staking`, etc.) follow the same layout and are imported as Go packages. Your custom modules live alongside them. + +- **`app/`** contains `app.go`, which assembles the application: creating BaseApp, mounting stores, initializing keepers, and registering all modules with the ModuleManager. + +- **`cmd/`** contains the entrypoint for the node binary. It parses command-line flags, reads configuration files, and starts the daemon process that runs both the CometBFT node and the SDK application. + +- **`proto/`** contains the Protobuf definitions for all custom types: messages, queries, state schemas, and genesis. Go code is generated from these files and consumed throughout the module. Proto files live at the repository root, not inside `x/`, so they can be shared across languages and tooling. + +## What lives inside a module + +Each module under `x/` follows a consistent internal layout: + +``` +x/mymodule/ +├── keeper/ +│ ├── keeper.go # Keeper struct: state access methods +│ ├── msg_server.go # MsgServer: handles transaction messages +│ └── query_server.go # QueryServer: handles queries +├── types/ +│ ├── expected_keepers.go # Interfaces for other modules' keepers +│ ├── keys.go # Store key and prefix definitions +│ └── *.pb.go # Generated from proto definitions +└── module.go # AppModule: wires the module into the application +``` + +The key files and what they contain: + +**`keeper/keeper.go`** — The `Keeper` struct holds references to the module's collections or store key, its codec, and any external keeper interfaces it depends on. It provides typed methods for reading and writing state. No code outside the keeper package should access the module's store directly. + +**`keeper/msg_server.go`** — The `MsgServer` implements the module's transaction handlers. It validates message inputs, checks authorization where required, and delegates state changes to the keeper. + +**`keeper/query_server.go`** — The `QueryServer` implements the module's gRPC query handlers. It reads state through the keeper and returns data without modifying state. + +**`types/expected_keepers.go`** — Declares the interfaces this module requires from other modules. For example, a module that charges fees declares a `BankKeeper` interface with only the methods it needs. This makes cross-module dependencies explicit and auditable. {/* cosmos/cosmos-sdk x/staking/types/expected_keepers.go:39 — DelegateCoinsFromAccountToModule in staking's expected BankKeeper interface — staking declares only the bank methods it needs */} + +**`module.go`** — Implements the `AppModule` interface, which connects the module to the application: registering its gRPC message and query services, defining genesis handlers, and opting into block lifecycle hooks (BeginBlock, EndBlock, PreBlock) as needed. + +## How modules are assembled into an application + +Modules are assembled through the **ModuleManager** in `app.go`. The ModuleManager maintains the set of all registered modules and coordinates their lifecycle behavior across the application. {/* cosmos/cosmos-sdk types/module/module.go:277 — type Manager struct { Modules map[string]interface{}; OrderBeginBlockers []string; OrderEndBlockers []string; OrderInitGenesis []string; ... } */} + +At startup, the ModuleManager: + +- Calls `RegisterServices` on each module to wire up all gRPC message and query handlers. {/* cosmos/cosmos-sdk types/module/module.go:455 — RegisterServices iterates modules and calls RegisterServices(cfg) on each module implementing HasServices */} +- Drives `InitGenesis` in a configured order when the chain starts, populating each module's store from `genesis.json`. {/* cosmos/cosmos-sdk types/module/module.go:479 — InitGenesis iterates OrderInitGenesis, calling each module's InitGenesis with its genesis data */} + +At each block, the ModuleManager: + +- Calls `BeginBlock` on registered modules before transactions execute. {/* cosmos/cosmos-sdk types/module/module.go:771 — BeginBlock creates a child context and calls BeginBlock for each module in OrderBeginBlockers */} +- Calls `EndBlock` on registered modules after all transactions have executed, aggregating validator updates and events. {/* cosmos/cosmos-sdk types/module/module.go:789 — EndBlock aggregates validator updates and events from modules in OrderEndBlockers */} + +**Module ordering matters.** For example, the `x/staking` module must run `EndBlock` before `x/distribution` so that delegation changes are reflected before staking rewards are calculated. The ordering is configured explicitly in `app.go`, giving chain developers full control over execution sequence. + +## Where BaseApp fits + +BaseApp sits between CometBFT and the modules: + +``` +CometBFT (consensus) + ↓ ABCI calls +BaseApp + ↓ orchestrates blocks, routes messages +ModuleManager + ↓ dispatches to individual modules +Modules (x/auth, x/bank, x/mymodule, ...) + ↓ read/write +State (KVStores) +``` + +BaseApp implements the ABCI interface that CometBFT calls to drive block execution. When CometBFT calls `FinalizeBlock`, BaseApp runs the block through all its phases — PreBlock, BeginBlock, transactions, EndBlock — and returns the resulting app hash. BaseApp is covered in detail in [BaseApp Overview](/sdk/v0.53/learn/concepts/baseapp). + +## The role of `app.go` + +`app.go` is the single file that defines a specific chain. It is where the application is assembled from its parts: + +1. Create a `BaseApp` instance with the application name, logger, database, and codec. +2. Create a `StoreKey` for each module and mount it to the multistore. +3. Instantiate each `Keeper`, passing in the codec, store key, and references to other keepers the module depends on. +4. Create the `ModuleManager` with all module instances. +5. Configure execution ordering: which modules run first during genesis, BeginBlock, and EndBlock. +6. Register all gRPC services (message and query handlers) through the ModuleManager. +7. Set the `AnteHandler` and other middleware. + +Because `app.go` is plain Go code, it is fully customizable. A chain includes exactly the modules it needs, wires keepers together as required, and controls the execution order of all lifecycle hooks. There is no framework magic — the assembly is explicit and auditable. + +## Summary + +An SDK application is a deterministic state machine composed of modules assembled in `app.go`. The codebase follows a conventional layout: modules in `x/`, application wiring in `app/`, the binary entrypoint in `cmd/`, and Protobuf definitions in `proto/`. + +The ModuleManager assembles modules and coordinates their lifecycle hooks across the application. BaseApp provides the ABCI implementation that connects the state machine to CometBFT's consensus engine. + +The next section explains what BaseApp is and how it coordinates transaction execution in detail. From ca6f94acb1550ff0fc4812aae4973ed419f0b67a Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:37:43 -0500 Subject: [PATCH 25/36] Update baseapp.mdx --- sdk/v0.53/learn/concepts/baseapp.mdx | 64 +++++++++++++++++++++------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/sdk/v0.53/learn/concepts/baseapp.mdx b/sdk/v0.53/learn/concepts/baseapp.mdx index e39cee3e..f4bf0958 100644 --- a/sdk/v0.53/learn/concepts/baseapp.mdx +++ b/sdk/v0.53/learn/concepts/baseapp.mdx @@ -8,7 +8,7 @@ In the previous sections, you learned how modules define business logic and stat **BaseApp** is the Go struct at the core of every Cosmos SDK application. It implements the ABCI (Application Blockchain Interface) — the protocol that CometBFT uses to communicate with the application — and translates those calls into module-level operations: running block hooks, routing messages to handlers, managing state, and computing the app hash. -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. {/* cosmos/cosmos-sdk baseapp/baseapp.go:63 — type BaseApp struct { ... } — the complete BaseApp struct */} +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. {/* cosmos/cosmos-sdk baseapp/baseapp.go:86 — type BaseApp struct — the complete BaseApp struct definition */} ## Architectural position @@ -26,26 +26,58 @@ Modules (x/auth, x/bank, x/staking, ...) State (KVStores) ``` -CometBFT drives the block lifecycle by calling ABCI methods on BaseApp. BaseApp handles each call, delegating to the ModuleManager and routing messages to the appropriate module handlers. Modules contain the business logic. KVStores hold the resulting state. +CometBFT drives the block lifecycle by calling ABCI methods on BaseApp. BaseApp handles each call, delegating to registered lifecycle hooks and routing messages to the appropriate module handlers. Modules contain the business logic. KVStores hold the resulting state. ## Key fields -The `BaseApp` struct holds references to everything needed to run a chain: +The [`BaseApp` struct](https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/baseapp.go) is defined in `baseapp/baseapp.go`. It holds references to everything needed to run a chain: {/* cosmos/cosmos-sdk baseapp/baseapp.go:85 — // BaseApp reflects the ABCI application implementation. */} ```go +// BaseApp reflects the ABCI application implementation. type BaseApp struct { - cms storetypes.CommitMultiStore // main (uncached) state store - storeLoader StoreLoader // loads stores at startup - msgServiceRouter *MsgServiceRouter // routes messages to handlers - anteHandler sdk.AnteHandler // pre-execution middleware + logger log.Logger + name string // application name from abci.BlockInfo + db dbm.DB // common DB backend + cms storetypes.CommitMultiStore // Main (uncached) state + storeLoader StoreLoader // function to handle store loading + grpcQueryRouter *GRPCQueryRouter // router for redirecting gRPC query calls + msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages + txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx + mempool mempool.Mempool + anteHandler sdk.AnteHandler // ante handler for fee and auth + postHandler sdk.PostHandler // post handler, optional + chainID string // ... additional fields } ``` -- **`cms` (CommitMultiStore)**: the root state store. All module substores are mounted here, and all state reads and writes during block execution pass through it. {/* cosmos/cosmos-sdk baseapp/baseapp.go:69 — cms storetypes.CommitMultiStore // Main (uncached) state */} -- **`storeLoader`**: a function that opens and mounts the individual module stores at application startup. {/* cosmos/cosmos-sdk baseapp/baseapp.go:71 — storeLoader StoreLoader — function that loads stores from disk */} -- **`msgServiceRouter`**: routes each message in a transaction to the correct module's `MsgServer` handler. {/* cosmos/cosmos-sdk baseapp/baseapp.go:73 — msgServiceRouter *MsgServiceRouter — routes message type URLs to registered handlers */} -- **`anteHandler`**: runs before message execution to handle cross-cutting concerns: signature verification, sequence validation, and fee deduction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:79 — anteHandler sdk.AnteHandler — pre-execution middleware set via SetAnteHandler */} +- **`cms` (CommitMultiStore)**: the root state store. All module substores are mounted here, and all state reads and writes during block execution pass through it. {/* cosmos/cosmos-sdk baseapp/baseapp.go:92 — cms storetypes.CommitMultiStore // Main (uncached) state */} +- **`storeLoader`**: a function that opens and mounts the individual module stores at application startup. {/* cosmos/cosmos-sdk baseapp/baseapp.go:94 — storeLoader StoreLoader // function to handle store loading, may be overridden with SetStoreLoader() */} +- **`grpcQueryRouter`**: routes incoming gRPC queries to the correct module's query handler. {/* cosmos/cosmos-sdk baseapp/baseapp.go:95 — grpcQueryRouter *GRPCQueryRouter // router for redirecting gRPC query calls */} +- **`msgServiceRouter`**: routes each message in a transaction to the correct module's `MsgServer` handler. {/* cosmos/cosmos-sdk baseapp/baseapp.go:96 — msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages */} +- **`anteHandler`**: runs before message execution to handle cross-cutting concerns: signature verification, sequence validation, and fee deduction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:102 — anteHandler sdk.AnteHandler // ante handler for fee and auth */} +- **`postHandler`**: optional middleware that runs after message execution — used for tasks such as tipping or post-execution state adjustments. {/* cosmos/cosmos-sdk baseapp/baseapp.go:103 — postHandler sdk.PostHandler // post handler, optional */} + +## Module Manager + +BaseApp's block lifecycle hooks — `PreBlock`, `BeginBlock`, and `EndBlock` — are registered as function callbacks via `SetPreBlocker`, `SetBeginBlocker`, and `SetEndBlocker`. Every standard SDK application implements those callbacks by delegating to a `ModuleManager`. {/* cosmos/cosmos-sdk baseapp/options.go:195 — func (app *BaseApp) SetPreBlocker sets abciHandlers.PreBlocker */} {/* cosmos/cosmos-sdk baseapp/options.go:203 — func (app *BaseApp) SetBeginBlocker sets abciHandlers.BeginBlocker */} + +The `ModuleManager` is defined in `types/module/module.go` and holds the full set of registered modules along with their execution ordering: {/* cosmos/cosmos-sdk types/module/module.go:287 — type Manager struct { Modules map[string]any; OrderInitGenesis []string; OrderBeginBlockers []string; OrderEndBlockers []string; ... } */} + +```go +type Manager struct { + Modules map[string]any // all registered modules, keyed by module name + OrderPreBlockers []string // module execution order for PreBlock + OrderBeginBlockers []string // module execution order for BeginBlock + OrderEndBlockers []string // module execution order for EndBlock + OrderInitGenesis []string // module execution order for chain initialization + // ... +} +``` + +When BaseApp calls the `BeginBlocker` hook, the application delegates to `mm.BeginBlock(ctx)`, which iterates `OrderBeginBlockers` and calls each module's `BeginBlock` in sequence. {/* cosmos/cosmos-sdk simapp/app.go:687 — func (app *SimApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) calls app.ModuleManager.BeginBlock(ctx) */} The same pattern applies to `PreBlock` and `EndBlock`. + +**Module ordering matters.** For example, `x/staking` must run `EndBlock` before `x/distribution` so that delegation changes are finalized before staking rewards are calculated. This ordering is configured explicitly in `app.go` using `mm.SetOrderEndBlockers(...)`. How the `ModuleManager` is assembled and configured is covered in the next section on `app.go`. ## Coordinating transaction execution @@ -83,19 +115,19 @@ type MsgServiceRouter struct { // ... } ``` -{/* cosmos/cosmos-sdk baseapp/msg_service_router.go:28 — type MsgServiceRouter struct { routes map[string]MsgServiceHandler; ... } */} +{/* cosmos/cosmos-sdk baseapp/msg_service_router.go:29 — type MsgServiceRouter struct { routes map[string]MsgServiceHandler; ... } */} The routing process has three steps: -1. **Registration**: During app startup, each module calls `RegisterService`, which registers its message handlers keyed by message type URL (e.g., `/cosmos.bank.v1beta1.MsgSend`). {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:70 — RegisterService iterates service methods and registers handlers in the routes map keyed by message type URL */} -2. **Lookup**: At execution time, `Handler` looks up the registered handler for the incoming message's type URL. {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:53 — func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler — returns handler by type URL from routes map */} +1. **Registration**: During app startup, each module calls `RegisterService`, which registers its message handlers keyed by message type URL (e.g., `/cosmos.bank.v1beta1.MsgSend`). {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:71 — func (msr *MsgServiceRouter) RegisterService iterates service methods and registers handlers in the routes map keyed by message type URL */} +2. **Lookup**: At execution time, `Handler` looks up the registered handler for the incoming message's type URL. {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:54 — func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler — returns handler by type URL from routes map */} 3. **Execution**: The retrieved handler invokes the module's `MsgServer` implementation, which validates inputs, applies business rules, and updates state through the keeper. This routing is entirely type-URL-based. Modules do not need to know about each other at the routing level — BaseApp is the neutral coordinator. ## 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: {/* cosmos/cosmos-sdk baseapp/baseapp.go:328 — MountKVStores mounts IAVL or DB-backed stores for each registered KV store key */} +BaseApp owns the `CommitMultiStore` that holds all module state. At app startup, each module registers its store key, and BaseApp mounts the corresponding store: {/* cosmos/cosmos-sdk baseapp/baseapp.go:323 — func (app *BaseApp) MountKVStores mounts IAVL or DB-backed stores for each registered KV store key */} ```go app.MountKVStores(keys) @@ -112,7 +144,7 @@ The `AnteHandler` is middleware that runs before any message in a transaction ex - Deducting transaction fees from the signer's account - Setting up the gas meter for the transaction -The AnteHandler is configured at app startup: {/* cosmos/cosmos-sdk baseapp/options.go:229 — func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) — sets the AnteHandler; panics if called after app is sealed */} +The AnteHandler is configured at app startup: {/* cosmos/cosmos-sdk baseapp/options.go:235 — func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) — sets the AnteHandler; panics if called after app is sealed */} ```go app.SetAnteHandler(ante.NewAnteHandler(ante.HandlerOptions{ From 81599f43fd9ce52600327cfc046e4deeb00810f2 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:37:49 -0500 Subject: [PATCH 26/36] Update context-gas-and-events.mdx --- sdk/v0.53/learn/concepts/context-gas-and-events.mdx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sdk/v0.53/learn/concepts/context-gas-and-events.mdx b/sdk/v0.53/learn/concepts/context-gas-and-events.mdx index 93ce3b07..d678b952 100644 --- a/sdk/v0.53/learn/concepts/context-gas-and-events.mdx +++ b/sdk/v0.53/learn/concepts/context-gas-and-events.mdx @@ -30,9 +30,12 @@ Context exposes read-only access to the current block's metadata: - `ctx.BlockHeight()` returns the current block number. {/* cosmos/cosmos-sdk types/context.go:74 — func (c Context) BlockHeight() int64 { return c.header.Height } */} - `ctx.BlockTime()` returns the block's timestamp. {/* cosmos/cosmos-sdk types/context.go:75 — func (c Context) BlockTime() time.Time { return c.header.Time } */} - `ctx.ChainID()` returns the chain identifier string. {/* cosmos/cosmos-sdk types/context.go:76 — func (c Context) ChainID() string { return c.chainID } */} +- `ctx.Logger()` returns a structured logger scoped to the current execution context. Modules use this for operational logging (e.g., logging an upgrade activation or an unexpected state) without affecting consensus. {/* cosmos/cosmos-sdk types/context.go:95 — func (c Context) Logger() log.Logger { return c.logger } */} These values are populated by BaseApp from the block header provided by CometBFT before any block logic runs. Modules read them to implement time-dependent logic (for example, checking whether a vesting period has elapsed) or to tag events with the block height. +`ctx.IsCheckTx()` returns true when the context is being used for mempool validation rather than block execution. {/* cosmos/cosmos-sdk types/context.go:99 — func (c Context) IsCheckTx() bool { return c.checkTx } */} Modules that have logic specific to mempool pre-screening — such as skipping expensive computation during `CheckTx` — can branch on this flag. + ### Context and state access State is accessed through context. The context holds a reference to the multistore, and each keeper opens its own store through the context: @@ -124,13 +127,19 @@ ctx.EventManager().EmitTypedEvent(&types.EventCounterIncremented{ Using `EmitTypedEvent` is the modern approach. It provides type safety and makes the event schema explicit through proto definitions, allowing clients to deserialize events back into typed structs. +### Block events and transaction events + +Events emitted during `BeginBlock` or `EndBlock` hooks are **block events** — they describe things that happened at the block level (inflation minted, validator updates applied). Events emitted inside a message handler are **transaction events** — they describe what a specific transaction did. + +Both types are included in the `FinalizeBlock` response that CometBFT returns to the network, but they are reported separately so clients can distinguish block-level activity from per-transaction activity. + ### Who consumes events Events are consumed outside the node: - **Block explorers** index events to show users what happened in a transaction (which tokens moved, which validator was slashed, which proposal passed). - **Relayers** (IBC) subscribe to specific event types to detect packet sends and acknowledgments. -- **Indexers and off-chain services** build queryable databases of chain activity from event streams. +- **Indexers and off-chain services** build queryable databases of chain activity from event streams. Events can also be queried via the node's REST API and WebSocket endpoint. - **Wallets and UIs** display event data to users as transaction receipts. Events are included in the block result that CometBFT returns after each block. They are not replayed or reprocessed; once a block is finalized, its events are fixed. From d176bdfab2be05d06815d393be9e13de717c23d1 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:37:52 -0500 Subject: [PATCH 27/36] Update sdk.mdx --- sdk/v0.53/learn/concepts/sdk.mdx | 84 +++++++++++++++++++------------- 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/sdk/v0.53/learn/concepts/sdk.mdx b/sdk/v0.53/learn/concepts/sdk.mdx index 2479857d..de6dee8c 100644 --- a/sdk/v0.53/learn/concepts/sdk.mdx +++ b/sdk/v0.53/learn/concepts/sdk.mdx @@ -55,44 +55,16 @@ Each module under `x/` follows a consistent internal layout: ``` x/mymodule/ -├── keeper/ -│ ├── keeper.go # Keeper struct: state access methods -│ ├── msg_server.go # MsgServer: handles transaction messages -│ └── query_server.go # QueryServer: handles queries -├── types/ -│ ├── expected_keepers.go # Interfaces for other modules' keepers -│ ├── keys.go # Store key and prefix definitions -│ └── *.pb.go # Generated from proto definitions -└── module.go # AppModule: wires the module into the application +├── keeper/ # Keeper (state access), MsgServer, QueryServer +├── types/ # Generated proto types, store keys, expected_keepers.go +└── module.go # AppModule: wires the module into the application ``` -The key files and what they contain: - -**`keeper/keeper.go`** — The `Keeper` struct holds references to the module's collections or store key, its codec, and any external keeper interfaces it depends on. It provides typed methods for reading and writing state. No code outside the keeper package should access the module's store directly. - -**`keeper/msg_server.go`** — The `MsgServer` implements the module's transaction handlers. It validates message inputs, checks authorization where required, and delegates state changes to the keeper. - -**`keeper/query_server.go`** — The `QueryServer` implements the module's gRPC query handlers. It reads state through the keeper and returns data without modifying state. - -**`types/expected_keepers.go`** — Declares the interfaces this module requires from other modules. For example, a module that charges fees declares a `BankKeeper` interface with only the methods it needs. This makes cross-module dependencies explicit and auditable. {/* cosmos/cosmos-sdk x/staking/types/expected_keepers.go:39 — DelegateCoinsFromAccountToModule in staking's expected BankKeeper interface — staking declares only the bank methods it needs */} - -**`module.go`** — Implements the `AppModule` interface, which connects the module to the application: registering its gRPC message and query services, defining genesis handlers, and opting into block lifecycle hooks (BeginBlock, EndBlock, PreBlock) as needed. +Proto definitions live separately in `proto/`, not inside `x/`. See [Intro to Modules](/sdk/v0.53/learn/concepts/modules#anatomy-of-a-module-high-level) for a complete walkthrough of each file and the role it plays. ## How modules are assembled into an application -Modules are assembled through the **ModuleManager** in `app.go`. The ModuleManager maintains the set of all registered modules and coordinates their lifecycle behavior across the application. {/* cosmos/cosmos-sdk types/module/module.go:277 — type Manager struct { Modules map[string]interface{}; OrderBeginBlockers []string; OrderEndBlockers []string; OrderInitGenesis []string; ... } */} - -At startup, the ModuleManager: - -- Calls `RegisterServices` on each module to wire up all gRPC message and query handlers. {/* cosmos/cosmos-sdk types/module/module.go:455 — RegisterServices iterates modules and calls RegisterServices(cfg) on each module implementing HasServices */} -- Drives `InitGenesis` in a configured order when the chain starts, populating each module's store from `genesis.json`. {/* cosmos/cosmos-sdk types/module/module.go:479 — InitGenesis iterates OrderInitGenesis, calling each module's InitGenesis with its genesis data */} - -At each block, the ModuleManager: - -- Calls `BeginBlock` on registered modules before transactions execute. {/* cosmos/cosmos-sdk types/module/module.go:771 — BeginBlock creates a child context and calls BeginBlock for each module in OrderBeginBlockers */} -- Calls `EndBlock` on registered modules after all transactions have executed, aggregating validator updates and events. {/* cosmos/cosmos-sdk types/module/module.go:789 — EndBlock aggregates validator updates and events from modules in OrderEndBlockers */} - -**Module ordering matters.** For example, the `x/staking` module must run `EndBlock` before `x/distribution` so that delegation changes are reflected before staking rewards are calculated. The ordering is configured explicitly in `app.go`, giving chain developers full control over execution sequence. +Modules are assembled through the **ModuleManager** in `app.go`. The ModuleManager holds the full set of registered modules and coordinates their lifecycle hooks — `InitGenesis`, `BeginBlock`, `EndBlock`, and service registration — across the application. Module ordering is configured explicitly in `app.go` and matters: for example, `x/staking` must finalize delegation changes in `EndBlock` before `x/distribution` calculates rewards. See [Intro to Modules](/sdk/v0.53/learn/concepts/modules) for a detailed walkthrough of how the ModuleManager works. ## Where BaseApp fits @@ -124,7 +96,51 @@ BaseApp implements the ABCI interface that CometBFT calls to drive block executi 6. Register all gRPC services (message and query handlers) through the ModuleManager. 7. Set the `AnteHandler` and other middleware. -Because `app.go` is plain Go code, it is fully customizable. A chain includes exactly the modules it needs, wires keepers together as required, and controls the execution order of all lifecycle hooks. There is no framework magic — the assembly is explicit and auditable. +Because `app.go` is plain Go code, it is fully customizable. A chain includes exactly the modules it needs, wires keepers together as required, and controls the execution order of all lifecycle hooks. There is no framework magic, and the assembly is explicit and auditable. + +## Other files in a chain + +A complete SDK chain repository contains more than just `x/`, `app/`, `cmd/`, and `proto/`. Here is what else you will typically find. + +### Additional files in `app/` + +Real-world applications split the `app/` directory across multiple files to keep `app.go` focused on wiring: + +``` +app/ +├── app.go # Main wiring: BaseApp, keepers, module registration +├── export.go # Exports current state as a genesis file (hard forks, snapshots) +├── upgrades.go # Upgrade handlers for consensus-breaking software changes +└── genesis.go # Helpers for genesis state initialization (optional) +``` + +- **`export.go`** — Implements `ExportAppStateAndValidators`, which serializes all module state into a `genesis.json`. This is used when migrating to a new chain version (hard fork) or creating a testnet from a live chain snapshot. +- **`upgrades.go`** — Registers named upgrade handlers consumed by the `x/upgrade` module. Each handler runs exactly once, at the block where the governance-approved upgrade height is reached, and performs any necessary state migrations. + +### At the repository root + +``` +myapp/ +├── go.mod # Go module definition: SDK version and all dependencies +├── go.sum # Cryptographic checksums for all dependencies +├── Makefile # Build, test, and codegen tasks +└── scripts/ # Automation scripts (proto generation, linting) +``` + +- **`go.mod` / `go.sum`** — Standard Go module files. `go.mod` declares the Cosmos SDK version and all other imported packages. `go.sum` provides verifiable checksums for the full dependency tree. +- **`Makefile`** — The standard entry point for development tasks: `make build` compiles the binary, `make test` runs unit tests, `make proto-gen` regenerates Go code from `.proto` files. Most SDK chains include targets for linting, simulation tests, and Docker builds. +- **`scripts/`** — Shell scripts and configuration for tooling that the Makefile invokes — typically `buf` configuration for protobuf compilation, scripts for checking dependency consistency, and helpers for running local testnets. + +### The node binary (`cmd/`) + +``` +cmd/ +└── myappdaemon/ + ├── main.go # Binary entrypoint + └── root.go # Root Cobra command: subcommands (start, tx, query, keys, ...) +``` + +The `cmd/` directory produces the node daemon binary (e.g., `simd`, `gaiad`, `wasmd`). It uses [Cobra](https://github.com/spf13/cobra) to expose subcommands for starting the node, submitting transactions, querying state, managing keys, and running genesis initialization. The `start` command spins up CometBFT and the SDK application together in a single process. ## Summary From c849b0221a0800ccf3dc43237a24b37aba487deb Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:57:57 -0500 Subject: [PATCH 28/36] Update context-gas-and-events.mdx --- .../learn/concepts/context-gas-and-events.mdx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/sdk/v0.53/learn/concepts/context-gas-and-events.mdx b/sdk/v0.53/learn/concepts/context-gas-and-events.mdx index d678b952..e8a224ad 100644 --- a/sdk/v0.53/learn/concepts/context-gas-and-events.mdx +++ b/sdk/v0.53/learn/concepts/context-gas-and-events.mdx @@ -8,7 +8,7 @@ In the previous section, you learned how data is serialized and why every valida Every message handler, keeper method, and block hook in the Cosmos SDK receives an `sdk.Context`. It is the execution environment for a single unit of work — a transaction, a query, or a block hook — and carries everything that code needs to read state, emit events, and consume gas. -The `Context` struct is defined in `types/context.go`: {/* cosmos/cosmos-sdk types/context.go:39 — type Context struct { ... } */} +The `Context` struct is defined in `types/context.go`: {/* cosmos/cosmos-sdk types/context.go:41 — type Context struct { ... } */} ```go type Context struct { @@ -27,9 +27,9 @@ Context is a value type, not a pointer. It is passed by value and mutated throug Context exposes read-only access to the current block's metadata: -- `ctx.BlockHeight()` returns the current block number. {/* cosmos/cosmos-sdk types/context.go:74 — func (c Context) BlockHeight() int64 { return c.header.Height } */} -- `ctx.BlockTime()` returns the block's timestamp. {/* cosmos/cosmos-sdk types/context.go:75 — func (c Context) BlockTime() time.Time { return c.header.Time } */} -- `ctx.ChainID()` returns the chain identifier string. {/* cosmos/cosmos-sdk types/context.go:76 — func (c Context) ChainID() string { return c.chainID } */} +- `ctx.BlockHeight()` returns the current block number. {/* cosmos/cosmos-sdk types/context.go:91 — func (c Context) BlockHeight() int64 { return c.header.Height } */} +- `ctx.BlockTime()` returns the block's timestamp. {/* cosmos/cosmos-sdk types/context.go:92 — func (c Context) BlockTime() time.Time { return c.header.Time } */} +- `ctx.ChainID()` returns the chain identifier string. {/* cosmos/cosmos-sdk types/context.go:93 — func (c Context) ChainID() string { return c.chainID } */} - `ctx.Logger()` returns a structured logger scoped to the current execution context. Modules use this for operational logging (e.g., logging an upgrade activation or an unexpected state) without affecting consensus. {/* cosmos/cosmos-sdk types/context.go:95 — func (c Context) Logger() log.Logger { return c.logger } */} These values are populated by BaseApp from the block header provided by CometBFT before any block logic runs. Modules read them to implement time-dependent logic (for example, checking whether a vesting period has elapsed) or to tag events with the block height. @@ -58,9 +58,9 @@ The gas system exists to prevent abuse. Without a gas limit, a single transactio ### Gas limit and the transaction gas meter -Every transaction specifies a gas limit in its `auth_info.fee.gas_limit` field. {/* cosmos/cosmos-sdk proto/cosmos/tx/v1beta1/tx.proto:235 — uint64 gas_limit = 2; — the gas_limit field in the Fee message of AuthInfo */} When BaseApp begins executing a transaction, it creates a `GasMeter` initialized with that limit and attaches it to the context. {/* cosmos/cosmos-sdk types/context.go:221 — func (c Context) WithGasMeter(meter storetypes.GasMeter) Context — used by BaseApp to attach the transaction gas meter to context */} +Every transaction specifies a gas limit in its `auth_info.fee.gas_limit` field. {/* cosmos/cosmos-sdk proto/cosmos/tx/v1beta1/tx.proto:235 — uint64 gas_limit = 2; — the gas_limit field in the Fee message of AuthInfo */} When BaseApp begins executing a transaction, it creates a `GasMeter` initialized with that limit and attaches it to the context. {/* cosmos/cosmos-sdk types/context.go:244 — func (c Context) WithGasMeter(meter storetypes.GasMeter) Context — used by BaseApp to attach the transaction gas meter to context */} -The `GasMeter` interface provides two key methods: {/* cosmos/cosmos-sdk store/types/gas.go:40 — type GasMeter interface { GasConsumed() Gas; ConsumeGas(amount Gas, descriptor string); ... } */} +The `GasMeter` interface provides two key methods: {/* cosmos/cosmos-sdk store/types/gas.go:41 — type GasMeter interface { GasConsumed() Gas; ConsumeGas(amount Gas, descriptor string); ... } */} ```go type GasMeter interface { @@ -70,24 +70,24 @@ type GasMeter interface { } ``` -`GasConsumed` returns the total gas used so far in the current execution unit. {/* cosmos/cosmos-sdk store/types/gas.go:41 — GasConsumed() Gas — returns total gas consumed so far */} `ConsumeGas` adds to the running total and panics with `ErrorOutOfGas` if consumption exceeds the limit. {/* cosmos/cosmos-sdk store/types/gas.go:45 — ConsumeGas(amount Gas, descriptor string) — adds amount to consumed; panics with ErrorOutOfGas if limit exceeded */} {/* cosmos/cosmos-sdk store/types/gas.go:29 — type ErrorOutOfGas struct { Descriptor string } — the error type panicked when gas is exhausted */} +`GasConsumed` returns the total gas used so far in the current execution unit. {/* cosmos/cosmos-sdk store/types/gas.go:42 — GasConsumed() Gas — returns total gas consumed so far */} `ConsumeGas` adds to the running total and panics with `ErrorOutOfGas` if consumption exceeds the limit. {/* cosmos/cosmos-sdk store/types/gas.go:46 — ConsumeGas(amount Gas, descriptor string) — adds amount to consumed; panics with ErrorOutOfGas if limit exceeded */} {/* cosmos/cosmos-sdk store/types/gas.go:30 — type ErrorOutOfGas struct { Descriptor string } — the error type panicked when gas is exhausted */} ### How gas is consumed Gas is consumed automatically at the store layer. Every read and write through the `GasKVStore` wrapper charges gas before delegating to the underlying store: -- A `Get` (store read) charges a flat read cost plus a per-byte cost for the key and value. {/* cosmos/cosmos-sdk store/gaskv/store.go:35 — gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, ...) on every Get call */} -- A `Set` (store write) charges a flat write cost plus a per-byte cost for the key and value. {/* cosmos/cosmos-sdk store/gaskv/store.go:49 — gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, ...) on every Set call */} +- A `Get` (store read) charges a flat read cost plus a per-byte cost for the key and value. {/* cosmos/cosmos-sdk store/gaskv/store.go:66 — gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, ...) on every Get call */} +- A `Set` (store write) charges a flat write cost plus a per-byte cost for the key and value. {/* cosmos/cosmos-sdk store/gaskv/store.go:80 — gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, ...) on every Set call */} Modules do not need to manually track gas for ordinary state access — the store layer handles it automatically. Modules call `ctx.GasMeter().ConsumeGas(...)` directly only for computation costs that are not captured by store operations (for example, a module that performs a cryptographic operation outside the store). ### When gas runs out -If gas is exhausted during execution, `ConsumeGas` panics with `ErrorOutOfGas`. {/* cosmos/cosmos-sdk store/types/gas.go:106 — basicGasMeter.ConsumeGas panics with ErrorOutOfGas{descriptor} when g.consumed > g.limit */} BaseApp recovers from this panic, discards all state changes from the transaction, and returns an error to the user. Fees may still be charged for the gas consumed up to the point of failure, but none of the transaction's state changes are committed. +If gas is exhausted during execution, `ConsumeGas` panics with `ErrorOutOfGas`. {/* cosmos/cosmos-sdk store/types/gas.go:116 — basicGasMeter.ConsumeGas panics with ErrorOutOfGas{descriptor} when g.consumed > g.limit */} BaseApp recovers from this panic, discards all state changes from the transaction, and returns an error to the user. Fees may still be charged for the gas consumed up to the point of failure, but none of the transaction's state changes are committed. ### Block gas limit -In addition to the per-transaction gas meter, there is a block-level gas meter that tracks total gas consumed by all transactions in a block. {/* cosmos/cosmos-sdk types/context.go:39 — blockGasMeter storetypes.GasMeter field in Context struct tracks block-level gas */} The block gas limit prevents a single block from consuming unbounded computation. If a transaction would cause the block's gas total to exceed the limit, it is excluded from the block. +In addition to the per-transaction gas meter, there is a block-level gas meter that tracks total gas consumed by all transactions in a block. {/* cosmos/cosmos-sdk types/context.go:54 — blockGasMeter storetypes.GasMeter field in Context struct tracks block-level gas */} The block gas limit prevents a single block from consuming unbounded computation. If a transaction would cause the block's gas total to exceed the limit, it is excluded from the block. ## Events @@ -99,9 +99,9 @@ Events are not part of consensus state. They are not stored in the KVStore, do n ### EventManager -Modules emit events through the `EventManager`, which is attached to the context: {/* cosmos/cosmos-sdk types/context.go:87 — func (c Context) EventManager() EventManagerI { return c.eventManager } */} +Modules emit events through the `EventManager`, which is attached to the context: {/* cosmos/cosmos-sdk types/context.go:104 — func (c Context) EventManager() *EventManager { return c.eventManager } */} -The `EventManager` is created fresh for each transaction and collects all events emitted during that execution. {/* cosmos/cosmos-sdk types/events.go:34 — type EventManager struct { events Events } — holds the slice of events accumulated during execution */} {/* cosmos/cosmos-sdk types/events.go:38 — func NewEventManager() *EventManager — creates an EventManager with an empty events slice */} +The `EventManager` is created fresh for each transaction and collects all events emitted during that execution. {/* cosmos/cosmos-sdk types/events.go:24 — type EventManager struct { events Events } — holds the slice of events accumulated during execution */} {/* cosmos/cosmos-sdk types/events.go:28 — func NewEventManager() *EventManager — creates an EventManager with an empty events slice */} ### Emitting events @@ -115,9 +115,9 @@ ctx.EventManager().EmitEvent(sdk.NewEvent( )) ``` -`EmitEvent` appends a raw key-value event to the manager's accumulated list. {/* cosmos/cosmos-sdk types/events.go:46 — func (em *EventManager) EmitEvent(event Event) — appends the event to em.events */} +`EmitEvent` appends a raw key-value event to the manager's accumulated list. {/* cosmos/cosmos-sdk types/events.go:35 — func (em *EventManager) EmitEvent(event Event) — appends the event to em.events */} -For events backed by protobuf message types, `EmitTypedEvent` serializes the message's fields into event attributes automatically: {/* cosmos/cosmos-sdk types/events.go:62 — func (em *EventManager) EmitTypedEvent(tev proto.Message) error — serializes proto message into an Event with attributes derived from its fields */} +For events backed by protobuf message types, `EmitTypedEvent` serializes the message's fields into event attributes automatically: {/* cosmos/cosmos-sdk types/events.go:57 — func (em *EventManager) EmitTypedEvent(tev proto.Message) error — serializes proto message into an Event with attributes derived from its fields */} ```go ctx.EventManager().EmitTypedEvent(&types.EventCounterIncremented{ From 978525ba7264930681ef3aa8d918df9bce67440b Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:58:08 -0500 Subject: [PATCH 29/36] Rename SDK doc and add concrete code references Rename sdk.mdx to sdk-structure.mdx and update content to include concrete code references and examples from the cosmos/cosmos-sdk repository (e.g., simapp/app.go ModuleManager ordering, simapp/export.go export function, x/upgrade keeper marking upgrades done). Also adjust wording/punctuation for clarity. These additions provide links to real code locations to clarify how modules are assembled and how export/upgrade handlers operate. --- sdk/v0.53/learn/concepts/{sdk.mdx => sdk-structure.mdx} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename sdk/v0.53/learn/concepts/{sdk.mdx => sdk-structure.mdx} (88%) diff --git a/sdk/v0.53/learn/concepts/sdk.mdx b/sdk/v0.53/learn/concepts/sdk-structure.mdx similarity index 88% rename from sdk/v0.53/learn/concepts/sdk.mdx rename to sdk/v0.53/learn/concepts/sdk-structure.mdx index de6dee8c..a97f87f5 100644 --- a/sdk/v0.53/learn/concepts/sdk.mdx +++ b/sdk/v0.53/learn/concepts/sdk-structure.mdx @@ -64,7 +64,7 @@ Proto definitions live separately in `proto/`, not inside `x/`. See [Intro to Mo ## How modules are assembled into an application -Modules are assembled through the **ModuleManager** in `app.go`. The ModuleManager holds the full set of registered modules and coordinates their lifecycle hooks — `InitGenesis`, `BeginBlock`, `EndBlock`, and service registration — across the application. Module ordering is configured explicitly in `app.go` and matters: for example, `x/staking` must finalize delegation changes in `EndBlock` before `x/distribution` calculates rewards. See [Intro to Modules](/sdk/v0.53/learn/concepts/modules) for a detailed walkthrough of how the ModuleManager works. +Modules are assembled through the **ModuleManager** in `app.go`. The ModuleManager holds the full set of registered modules and coordinates their lifecycle hooks — `InitGenesis`, `BeginBlock`, `EndBlock`, and service registration — across the application. Module ordering is configured explicitly in `app.go` and matters: for example, `x/staking` must finalize delegation changes in `EndBlock` before `x/distribution` calculates rewards. {/* cosmos/cosmos-sdk simapp/app.go:522 — app.ModuleManager.SetOrderEndBlockers(...) — explicit end-block ordering configured in simapp, illustrating that module execution order is a deliberate application-level decision */} See [Intro to Modules](/sdk/v0.53/learn/concepts/modules) for a detailed walkthrough of how the ModuleManager works. ## Where BaseApp fits @@ -96,7 +96,7 @@ BaseApp implements the ABCI interface that CometBFT calls to drive block executi 6. Register all gRPC services (message and query handlers) through the ModuleManager. 7. Set the `AnteHandler` and other middleware. -Because `app.go` is plain Go code, it is fully customizable. A chain includes exactly the modules it needs, wires keepers together as required, and controls the execution order of all lifecycle hooks. There is no framework magic, and the assembly is explicit and auditable. +Because `app.go` is plain Go code, it is fully customizable. A chain includes exactly the modules it needs, wires keepers together as required, and controls the execution order of all lifecycle hooks. There is no framework magic — the assembly is explicit and auditable. ## Other files in a chain @@ -114,8 +114,8 @@ app/ └── genesis.go # Helpers for genesis state initialization (optional) ``` -- **`export.go`** — Implements `ExportAppStateAndValidators`, which serializes all module state into a `genesis.json`. This is used when migrating to a new chain version (hard fork) or creating a testnet from a live chain snapshot. -- **`upgrades.go`** — Registers named upgrade handlers consumed by the `x/upgrade` module. Each handler runs exactly once, at the block where the governance-approved upgrade height is reached, and performs any necessary state migrations. +- **`export.go`** — Implements `ExportAppStateAndValidators`, which serializes all module state into a `genesis.json`. {/* cosmos/cosmos-sdk simapp/export.go:21 — func (app *SimApp) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAddrs, modulesToExport []string) — serializes all module state into the exported genesis format */} This is used when migrating to a new chain version (hard fork) or creating a testnet from a live chain snapshot. +- **`upgrades.go`** — Registers named upgrade handlers consumed by the `x/upgrade` module. Each handler runs exactly once, at the block where the governance-approved upgrade height is reached, and performs any necessary state migrations. {/* cosmos/cosmos-sdk x/upgrade/keeper/keeper.go:518 — k.setDone(ctx, plan.Name) — marks the upgrade plan as complete after handler execution, preventing re-execution */} ### At the repository root From bc691437f8227462dcb4835629cb6e3ef528333d Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:18:39 -0500 Subject: [PATCH 30/36] Update baseapp.mdx --- sdk/v0.53/learn/concepts/baseapp.mdx | 98 ++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/sdk/v0.53/learn/concepts/baseapp.mdx b/sdk/v0.53/learn/concepts/baseapp.mdx index f4bf0958..b013d43c 100644 --- a/sdk/v0.53/learn/concepts/baseapp.mdx +++ b/sdk/v0.53/learn/concepts/baseapp.mdx @@ -2,13 +2,9 @@ title: BaseApp Overview --- -In the previous sections, you learned how modules define business logic and state, and how an SDK application is structured as a codebase. This page explains how transactions actually execute: BaseApp is the engine that connects CometBFT's consensus calls to module execution. +**BaseApp** is the execution engine of every Cosmos SDK chain. It implements ABCI — the protocol CometBFT uses to communicate with the application — and translates those calls into module execution, transaction processing, and state transitions. {/* cosmos/cosmos-sdk baseapp/baseapp.go:85 — // BaseApp reflects the ABCI application implementation. */} -## What BaseApp is - -**BaseApp** is the Go struct at the core of every Cosmos SDK application. It implements the ABCI (Application Blockchain Interface) — the protocol that CometBFT uses to communicate with the application — and translates those calls into module-level operations: running block hooks, routing messages to handlers, managing state, and computing the app hash. - -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. {/* cosmos/cosmos-sdk baseapp/baseapp.go:86 — type BaseApp struct — the complete BaseApp struct definition */} +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. 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 @@ -30,10 +26,9 @@ CometBFT drives the block lifecycle by calling ABCI methods on BaseApp. BaseApp ## Key fields -The [`BaseApp` struct](https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/baseapp.go) is defined in `baseapp/baseapp.go`. It holds references to everything needed to run a chain: {/* cosmos/cosmos-sdk baseapp/baseapp.go:85 — // BaseApp reflects the ABCI application implementation. */} +[`BaseApp`](https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/baseapp.go) is defined in `baseapp/baseapp.go`. It holds references to everything needed to run a chain: {/* cosmos/cosmos-sdk baseapp/baseapp.go:86 — type BaseApp struct — the complete BaseApp struct definition */} ```go -// BaseApp reflects the ABCI application implementation. type BaseApp struct { logger log.Logger name string // application name from abci.BlockInfo @@ -55,14 +50,29 @@ type BaseApp struct { - **`storeLoader`**: a function that opens and mounts the individual module stores at application startup. {/* cosmos/cosmos-sdk baseapp/baseapp.go:94 — storeLoader StoreLoader // function to handle store loading, may be overridden with SetStoreLoader() */} - **`grpcQueryRouter`**: routes incoming gRPC queries to the correct module's query handler. {/* cosmos/cosmos-sdk baseapp/baseapp.go:95 — grpcQueryRouter *GRPCQueryRouter // router for redirecting gRPC query calls */} - **`msgServiceRouter`**: routes each message in a transaction to the correct module's `MsgServer` handler. {/* cosmos/cosmos-sdk baseapp/baseapp.go:96 — msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages */} +- **`txDecoder`**: decodes raw transaction bytes from CometBFT into an `sdk.Tx`. {/* cosmos/cosmos-sdk baseapp/baseapp.go:98 — txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx */} - **`anteHandler`**: runs before message execution to handle cross-cutting concerns: signature verification, sequence validation, and fee deduction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:102 — anteHandler sdk.AnteHandler // ante handler for fee and auth */} - **`postHandler`**: optional middleware that runs after message execution — used for tasks such as tipping or post-execution state adjustments. {/* cosmos/cosmos-sdk baseapp/baseapp.go:103 — postHandler sdk.PostHandler // post handler, optional */} +## Transaction decoding + +Transactions arrive from CometBFT as raw bytes. Before BaseApp can validate or execute them, it must decode them into the SDK's transaction type using the `TxDecoder`: + +``` +[]byte tx + ↓ +TxDecoder + ↓ +sdk.Tx +``` + +This step happens before the transaction enters the execution pipeline. Without it, BaseApp cannot inspect messages, run the ante handler, or route execution to the correct module. This is why `txDecoder` is part of BaseApp construction — transaction execution starts with decoding. + ## Module Manager BaseApp's block lifecycle hooks — `PreBlock`, `BeginBlock`, and `EndBlock` — are registered as function callbacks via `SetPreBlocker`, `SetBeginBlocker`, and `SetEndBlocker`. Every standard SDK application implements those callbacks by delegating to a `ModuleManager`. {/* cosmos/cosmos-sdk baseapp/options.go:195 — func (app *BaseApp) SetPreBlocker sets abciHandlers.PreBlocker */} {/* cosmos/cosmos-sdk baseapp/options.go:203 — func (app *BaseApp) SetBeginBlocker sets abciHandlers.BeginBlocker */} -The `ModuleManager` is defined in `types/module/module.go` and holds the full set of registered modules along with their execution ordering: {/* cosmos/cosmos-sdk types/module/module.go:287 — type Manager struct { Modules map[string]any; OrderInitGenesis []string; OrderBeginBlockers []string; OrderEndBlockers []string; ... } */} +The `ModuleManager` holds the full set of registered modules along with their execution ordering: {/* cosmos/cosmos-sdk types/module/module.go:287 — type Manager struct { Modules map[string]any; OrderInitGenesis []string; OrderBeginBlockers []string; OrderEndBlockers []string; ... } */} ```go type Manager struct { @@ -77,11 +87,35 @@ type Manager struct { When BaseApp calls the `BeginBlocker` hook, the application delegates to `mm.BeginBlock(ctx)`, which iterates `OrderBeginBlockers` and calls each module's `BeginBlock` in sequence. {/* cosmos/cosmos-sdk simapp/app.go:687 — func (app *SimApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) calls app.ModuleManager.BeginBlock(ctx) */} The same pattern applies to `PreBlock` and `EndBlock`. -**Module ordering matters.** For example, `x/staking` must run `EndBlock` before `x/distribution` so that delegation changes are finalized before staking rewards are calculated. This ordering is configured explicitly in `app.go` using `mm.SetOrderEndBlockers(...)`. How the `ModuleManager` is assembled and configured is covered in the next section on `app.go`. +Module ordering matters: some modules depend on others having already updated state before they run. BaseApp provides the hook points; the `ModuleManager` handles ordering and dispatch. How ordering is configured is covered in the next section on `app.go`. + +## 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`**: validates a transaction before it enters the mempool, without committing state. +- **`FinalizeBlock`**: executes transactions in a proposed block against a branched state that is committed at the end. +- **`Simulate`**: runs a transaction for gas estimation without committing state. + +This separation ensures that validation and simulation cannot accidentally mutate committed application state. + +## The transaction execution pipeline + +When BaseApp processes a transaction, it runs through a structured pipeline: + +``` +RunTx + ├─ DecodeTx → raw bytes → sdk.Tx + ├─ AnteHandler → signatures, sequence, fees, gas setup + ├─ RunMsgs → route each message to the correct module handler + └─ PostHandler → optional post-execution middleware +``` + +If the AnteHandler fails, message execution does not begin. If any message fails, the transaction reverts atomically — all messages commit or none do. -## Coordinating transaction execution +## Coordinating block execution -When CometBFT calls `FinalizeBlock`, BaseApp runs the full block execution pipeline: {/* cosmos/cosmos-sdk baseapp/abci.go:868 — func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) — FinalizeBlock entry point */} +When CometBFT calls `FinalizeBlock`, BaseApp runs the full block execution pipeline in order: {/* cosmos/cosmos-sdk baseapp/abci.go:868 — func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) — FinalizeBlock entry point */} ``` FinalizeBlock @@ -95,15 +129,15 @@ FinalizeBlock → returns AppHash ``` -**PreBlock** runs first, 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. {/* cosmos/cosmos-sdk baseapp/abci.go:757 — PreBlock called at the start of internalFinalizeBlock, before BeginBlock */} +**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. {/* cosmos/cosmos-sdk baseapp/abci.go:757 — PreBlock called at the start of internalFinalizeBlock, before BeginBlock */} **BeginBlock** runs after PreBlock and handles per-block housekeeping: minting inflation rewards, distributing staking rewards, resetting per-block counters. {/* cosmos/cosmos-sdk baseapp/abci.go:762 — BeginBlock called after PreBlock, before transaction iteration */} -**Transactions** execute one at a time in the order they appear in the block. For each transaction, the AnteHandler runs first, then each message is routed and executed. +**Transactions** execute sequentially in block order. Each is atomic: if any message fails, the entire transaction reverts. **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. {/* cosmos/cosmos-sdk baseapp/abci.go:802 — EndBlock called after all transactions have executed */} -After `FinalizeBlock` completes, BaseApp computes and returns the app hash, which is the Merkle root of all committed state. {/* cosmos/cosmos-sdk baseapp/abci.go:903 — res.AppHash = app.workingHash() — app hash computed and returned in the FinalizeBlock response */} When CometBFT subsequently calls `Commit`, BaseApp persists the state changes to disk. {/* cosmos/cosmos-sdk baseapp/abci.go:935 — func (app *BaseApp) Commit() — persists finalized state to disk */} +After `FinalizeBlock` completes, BaseApp computes and returns the app hash — the Merkle root of all committed state. {/* cosmos/cosmos-sdk baseapp/abci.go:903 — res.AppHash = app.workingHash() — app hash computed and returned in the FinalizeBlock response */} When CometBFT subsequently calls `Commit`, BaseApp persists the state changes to disk. {/* cosmos/cosmos-sdk baseapp/abci.go:935 — func (app *BaseApp) Commit() — persists finalized state to disk */} ## Message routing @@ -125,17 +159,11 @@ The routing process has three steps: This routing is entirely type-URL-based. Modules do not need to know about each other at the routing level — BaseApp is the neutral coordinator. -## Store management +## Queries -BaseApp owns the `CommitMultiStore` that holds all module state. At app startup, each module registers its store key, and BaseApp mounts the corresponding store: {/* cosmos/cosmos-sdk baseapp/baseapp.go:323 — func (app *BaseApp) MountKVStores mounts IAVL or DB-backed stores for each registered KV store key */} +For read-only access to application state, BaseApp uses the **`GRPCQueryRouter`** to route incoming gRPC queries to the correct module query service. Queries are entirely separate from transaction execution: they inspect committed state without mutating it, and they do not go through the AnteHandler or transaction pipeline. -```go -app.MountKVStores(keys) -``` - -Before executing each transaction, 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. This is how transaction atomicity is implemented: all messages in a transaction either all commit or none do. - -## AnteHandler integration +## AnteHandler The `AnteHandler` is middleware that runs before any message in a transaction executes. It is responsible for: @@ -155,7 +183,17 @@ app.SetAnteHandler(ante.NewAnteHandler(ante.HandlerOptions{ })) ``` -If the AnteHandler fails for any reason, the transaction is rejected and its messages never execute. Gas fees for the consumed gas may still be charged, but no state changes from the transaction body are committed. +If the AnteHandler fails, the transaction is rejected and its messages never execute. Gas fees for consumed gas may still be charged, but no state changes from the transaction body are committed. + +## 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: {/* cosmos/cosmos-sdk baseapp/baseapp.go:323 — func (app *BaseApp) MountKVStores mounts IAVL or DB-backed stores for each registered KV store key */} + +```go +app.MountKVStores(keys) +``` + +Before executing each transaction, 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. This is how transaction atomicity is implemented: all messages in a transaction either all commit or none do. ## CheckTx and mempool validation @@ -163,11 +201,13 @@ Before a transaction reaches block execution, it goes through `CheckTx`. BaseApp `CheckTx` does not execute messages and does not write state. It exists solely to protect the mempool from invalid or malformed transactions before they consume block space. -## Why BaseApp exists +## Other ABCI methods -BaseApp provides shared execution infrastructure so that every Cosmos SDK application gets the same ABCI lifecycle, transaction processing pipeline, and store management automatically. Without it, every chain would need to independently implement signature verification, gas metering, message routing, block hook orchestration, and state commitment. +Beyond the block execution path, BaseApp implements several ABCI methods used for initialization and metadata: -By embedding BaseApp and configuring it with modules, a chain inherits the full execution engine and only needs to define its application-specific business logic in its modules. +- **`InitChain`**: populates state from genesis during chain initialization. +- **`Info`**: returns application metadata to CometBFT — the last committed block height and app version. +- **`Commit`**: persists finalized state changes to disk and advances the committed root state. ## Summary @@ -177,6 +217,6 @@ BaseApp is the execution engine of a Cosmos SDK chain: CometBFT → ABCI → BaseApp → Modules → State ``` -It implements ABCI, coordinates the block lifecycle (PreBlock → BeginBlock → transactions → EndBlock → Commit), routes messages to module handlers via the `MsgServiceRouter`, manages the multistore, and runs the AnteHandler before each transaction. +It implements ABCI, coordinates the block lifecycle (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 explains `app.go`: how BaseApp is instantiated, configured, and wired with modules to produce a complete, running chain. From 89b8a8bc66eb6c9ef505e8a44d7e114a2375a452 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Fri, 6 Mar 2026 12:35:01 -0500 Subject: [PATCH 31/36] Update baseapp.mdx --- sdk/v0.53/learn/concepts/baseapp.mdx | 160 ++++++++++++++++----------- 1 file changed, 93 insertions(+), 67 deletions(-) diff --git a/sdk/v0.53/learn/concepts/baseapp.mdx b/sdk/v0.53/learn/concepts/baseapp.mdx index b013d43c..08f62880 100644 --- a/sdk/v0.53/learn/concepts/baseapp.mdx +++ b/sdk/v0.53/learn/concepts/baseapp.mdx @@ -54,6 +54,22 @@ type BaseApp struct { - **`anteHandler`**: runs before message execution to handle cross-cutting concerns: signature verification, sequence validation, and fee deduction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:102 — anteHandler sdk.AnteHandler // ante handler for fee and auth */} - **`postHandler`**: optional middleware that runs after message execution — used for tasks such as tipping or post-execution state adjustments. {/* cosmos/cosmos-sdk baseapp/baseapp.go:103 — postHandler sdk.PostHandler // post handler, optional */} +## Initialization and sealing + +BaseApp has a two-phase configuration model. During app construction, you call setter methods to configure it: + +```go +app.MountKVStores(keys) +app.SetAnteHandler(...) +app.SetPreBlocker(...) +app.SetBeginBlocker(...) +app.SetEndBlocker(...) +``` + +Once construction is complete, `LoadLatestVersion` is called. It validates that required components are present, initializes the check state, and **seals** the app. {/* cosmos/cosmos-sdk baseapp/baseapp.go — func (app *BaseApp) Init() validates cms and stateStore, initializes checkState, then seals */} After sealing, calling any setter method panics. + +This is why all BaseApp configuration must happen inside the `NewApp` constructor — not lazily at request time. It also means the order of calls in `app.go` matters: stores must be mounted and handlers set before `LoadLatestVersion` is called. + ## Transaction decoding Transactions arrive from CometBFT as raw bytes. Before BaseApp can validate or execute them, it must decode them into the SDK's transaction type using the `TxDecoder`: @@ -68,34 +84,13 @@ sdk.Tx This step happens before the transaction enters the execution pipeline. Without it, BaseApp cannot inspect messages, run the ante handler, or route execution to the correct module. This is why `txDecoder` is part of BaseApp construction — transaction execution starts with decoding. -## Module Manager - -BaseApp's block lifecycle hooks — `PreBlock`, `BeginBlock`, and `EndBlock` — are registered as function callbacks via `SetPreBlocker`, `SetBeginBlocker`, and `SetEndBlocker`. Every standard SDK application implements those callbacks by delegating to a `ModuleManager`. {/* cosmos/cosmos-sdk baseapp/options.go:195 — func (app *BaseApp) SetPreBlocker sets abciHandlers.PreBlocker */} {/* cosmos/cosmos-sdk baseapp/options.go:203 — func (app *BaseApp) SetBeginBlocker sets abciHandlers.BeginBlocker */} - -The `ModuleManager` holds the full set of registered modules along with their execution ordering: {/* cosmos/cosmos-sdk types/module/module.go:287 — type Manager struct { Modules map[string]any; OrderInitGenesis []string; OrderBeginBlockers []string; OrderEndBlockers []string; ... } */} - -```go -type Manager struct { - Modules map[string]any // all registered modules, keyed by module name - OrderPreBlockers []string // module execution order for PreBlock - OrderBeginBlockers []string // module execution order for BeginBlock - OrderEndBlockers []string // module execution order for EndBlock - OrderInitGenesis []string // module execution order for chain initialization - // ... -} -``` - -When BaseApp calls the `BeginBlocker` hook, the application delegates to `mm.BeginBlock(ctx)`, which iterates `OrderBeginBlockers` and calls each module's `BeginBlock` in sequence. {/* cosmos/cosmos-sdk simapp/app.go:687 — func (app *SimApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) calls app.ModuleManager.BeginBlock(ctx) */} The same pattern applies to `PreBlock` and `EndBlock`. - -Module ordering matters: some modules depend on others having already updated state before they run. BaseApp provides the hook points; the `ModuleManager` handles ordering and dispatch. How ordering is configured is covered in the next section on `app.go`. - ## 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`**: validates a transaction before it enters the mempool, without committing state. -- **`FinalizeBlock`**: executes transactions in a proposed block against a branched state that is committed at the end. -- **`Simulate`**: runs a transaction for gas estimation without committing state. +- **`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. +- **`Simulate`** (`ExecModeSimulate`): runs a transaction for gas estimation without committing state. This separation ensures that validation and simulation cannot accidentally mutate committed application state. @@ -113,31 +108,27 @@ RunTx If the AnteHandler fails, message execution does not begin. If any message fails, the transaction reverts atomically — all messages commit or none do. -## Coordinating block execution - -When CometBFT calls `FinalizeBlock`, BaseApp runs the full block execution pipeline in order: {/* cosmos/cosmos-sdk baseapp/abci.go:868 — func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) — FinalizeBlock entry point */} - -``` -FinalizeBlock - ├─ PreBlock → module pre-block hooks - ├─ BeginBlock → module begin-block hooks - ├─ For each transaction: - │ ├─ AnteHandler (signature verification, fee deduction, gas setup) - │ ├─ Message routing and execution - │ └─ Commit or revert (atomic per-transaction) - └─ EndBlock → module end-block hooks - → returns AppHash -``` +## AnteHandler -**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. {/* cosmos/cosmos-sdk baseapp/abci.go:757 — PreBlock called at the start of internalFinalizeBlock, before BeginBlock */} +The `AnteHandler` is middleware that runs before any message in a transaction executes. It is responsible for: -**BeginBlock** runs after PreBlock and handles per-block housekeeping: minting inflation rewards, distributing staking rewards, resetting per-block counters. {/* cosmos/cosmos-sdk baseapp/abci.go:762 — BeginBlock called after PreBlock, before transaction iteration */} +- Verifying cryptographic signatures +- Validating and incrementing the account sequence number +- Deducting transaction fees from the signer's account +- Setting up the gas meter for the transaction -**Transactions** execute sequentially in block order. Each is atomic: if any message fails, the entire transaction reverts. +The AnteHandler is configured at app startup: {/* cosmos/cosmos-sdk baseapp/options.go:235 — func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) — sets the AnteHandler; panics if called after app is sealed */} -**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. {/* cosmos/cosmos-sdk baseapp/abci.go:802 — EndBlock called after all transactions have executed */} +```go +app.SetAnteHandler(ante.NewAnteHandler(ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + SignModeHandler: app.txConfig.SignModeHandler(), + // ... +})) +``` -After `FinalizeBlock` completes, BaseApp computes and returns the app hash — the Merkle root of all committed state. {/* cosmos/cosmos-sdk baseapp/abci.go:903 — res.AppHash = app.workingHash() — app hash computed and returned in the FinalizeBlock response */} When CometBFT subsequently calls `Commit`, BaseApp persists the state changes to disk. {/* cosmos/cosmos-sdk baseapp/abci.go:935 — func (app *BaseApp) Commit() — persists finalized state to disk */} +If the AnteHandler fails, the transaction is rejected and its messages never execute. ## Message routing @@ -161,45 +152,80 @@ This routing is entirely type-URL-based. Modules do not need to know about each ## Queries -For read-only access to application state, BaseApp uses the **`GRPCQueryRouter`** to route incoming gRPC queries to the correct module query service. Queries are entirely separate from transaction execution: they inspect committed state without mutating it, and they do not go through the AnteHandler or transaction pipeline. +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. -## AnteHandler +## Store management -The `AnteHandler` is middleware that runs before any message in a transaction executes. It is responsible for: +BaseApp owns the `CommitMultiStore` that holds all module state. At app startup, each module registers its store key, and BaseApp mounts the corresponding store: {/* cosmos/cosmos-sdk baseapp/baseapp.go:323 — func (app *BaseApp) MountKVStores mounts IAVL or DB-backed stores for each registered KV store key */} -- Verifying cryptographic signatures -- Validating and incrementing the account sequence number -- Deducting transaction fees from the signer's account -- Setting up the gas meter for the transaction +```go +app.MountKVStores(keys) +``` -The AnteHandler is configured at app startup: {/* cosmos/cosmos-sdk baseapp/options.go:235 — func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) — sets the AnteHandler; panics if called after app is sealed */} +Before executing each transaction, 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. This is how transaction atomicity is implemented: all messages in a transaction either all commit or none do. + +## CheckTx and mempool validation + +Before a transaction reaches block execution, it goes through `CheckTx`. BaseApp runs the AnteHandler in CheckTx mode to validate signatures, check sequence numbers, and verify fees. Transactions that fail `CheckTx` are rejected and do not enter the mempool. + +`CheckTx` does not execute messages and does not write state. It exists solely to protect the mempool from invalid or malformed transactions before they consume block space. + +## Coordinating block execution + +When CometBFT calls `FinalizeBlock`, BaseApp runs the full block execution pipeline in order: {/* cosmos/cosmos-sdk baseapp/abci.go:868 — func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) — FinalizeBlock entry point */} -```go -app.SetAnteHandler(ante.NewAnteHandler(ante.HandlerOptions{ - AccountKeeper: app.AccountKeeper, - BankKeeper: app.BankKeeper, - SignModeHandler: app.txConfig.SignModeHandler(), - // ... -})) +``` +FinalizeBlock + ├─ PreBlock → module pre-block hooks + ├─ BeginBlock → module begin-block hooks + ├─ For each transaction: + │ ├─ AnteHandler (signature verification, fee deduction, gas setup) + │ ├─ Message routing and execution + │ └─ Commit or revert (atomic per-transaction) + └─ EndBlock → module end-block hooks + → returns AppHash ``` -If the AnteHandler fails, the transaction is rejected and its messages never execute. Gas fees for consumed gas may still be charged, but no state changes from the transaction body are committed. +**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. {/* cosmos/cosmos-sdk baseapp/abci.go:757 — PreBlock called at the start of internalFinalizeBlock, before BeginBlock */} -## Store management +**BeginBlock** runs after PreBlock and handles per-block housekeeping: minting inflation rewards, distributing staking rewards, resetting per-block counters. {/* cosmos/cosmos-sdk baseapp/abci.go:762 — BeginBlock called after PreBlock, before transaction iteration */} -BaseApp owns the `CommitMultiStore` that holds all module state. At app startup, each module registers its store key, and BaseApp mounts the corresponding store: {/* cosmos/cosmos-sdk baseapp/baseapp.go:323 — func (app *BaseApp) MountKVStores mounts IAVL or DB-backed stores for each registered KV store key */} +**Transactions** execute sequentially in block order. Each is atomic: if any message fails, the entire transaction reverts. + +**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. {/* cosmos/cosmos-sdk baseapp/abci.go:802 — EndBlock called after all transactions have executed */} + +After `FinalizeBlock` completes, BaseApp computes and returns the app hash — the Merkle root of all committed state. {/* cosmos/cosmos-sdk baseapp/abci.go:903 — res.AppHash = app.workingHash() — app hash computed and returned in the FinalizeBlock response */} When CometBFT subsequently calls `Commit`, BaseApp persists the state changes to disk. {/* cosmos/cosmos-sdk baseapp/abci.go:935 — func (app *BaseApp) Commit() — persists finalized state to disk */} + +## Module Manager + +BaseApp's block lifecycle hooks — `PreBlock`, `BeginBlock`, and `EndBlock` — are registered as function callbacks via `SetPreBlocker`, `SetBeginBlocker`, and `SetEndBlocker`. Every standard SDK application implements those callbacks by delegating to a `ModuleManager`. {/* cosmos/cosmos-sdk baseapp/options.go:195 — func (app *BaseApp) SetPreBlocker sets abciHandlers.PreBlocker */} {/* cosmos/cosmos-sdk baseapp/options.go:203 — func (app *BaseApp) SetBeginBlocker sets abciHandlers.BeginBlocker */} + +The `ModuleManager` holds the full set of registered modules along with their execution ordering: {/* cosmos/cosmos-sdk types/module/module.go:287 — type Manager struct { Modules map[string]any; OrderInitGenesis []string; OrderBeginBlockers []string; OrderEndBlockers []string; ... } */} ```go -app.MountKVStores(keys) +type Manager struct { + Modules map[string]any // all registered modules, keyed by module name + OrderPreBlockers []string // module execution order for PreBlock + OrderBeginBlockers []string // module execution order for BeginBlock + OrderEndBlockers []string // module execution order for EndBlock + OrderInitGenesis []string // module execution order for chain initialization + // ... +} ``` -Before executing each transaction, 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. This is how transaction atomicity is implemented: all messages in a transaction either all commit or none do. +When BaseApp calls the `BeginBlocker` hook, the application delegates to `mm.BeginBlock(ctx)`, which iterates `OrderBeginBlockers` and calls each module's `BeginBlock` in sequence. {/* cosmos/cosmos-sdk simapp/app.go:687 — func (app *SimApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) calls app.ModuleManager.BeginBlock(ctx) */} The same pattern applies to `PreBlock` and `EndBlock`. -## CheckTx and mempool validation +Module ordering matters: some modules depend on others having already updated state before they run. BaseApp provides the hook points; the `ModuleManager` handles ordering and dispatch. How ordering is configured is covered in the next section on `app.go`. -Before a transaction reaches block execution, it goes through `CheckTx`. BaseApp runs a lightweight version of the AnteHandler during `CheckTx` to validate signatures, check sequence numbers, and verify fees. Transactions that fail `CheckTx` are rejected and do not enter the mempool. +## Block proposal and vote extensions -`CheckTx` does not execute messages and does not write state. It exists solely to protect the mempool from invalid or malformed transactions before they consume block space. +ABCI 2.0 added a proposal phase that runs during consensus rounds, before `FinalizeBlock` 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 use `PrepareProposal` to 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. The canonical use case is oracle price feeds — validators inject off-chain data into consensus so it becomes available on-chain at block start. + +All four are configurable in `app.go` via `SetPrepareProposal`, `SetProcessProposal`, `SetExtendVoteHandler`, and `SetVerifyVoteExtensionHandler`. Chains that do not need custom behavior can leave the defaults in place. ## Other ABCI methods From 600dfffa3312ccc9540708081c1578f35abb4ed4 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Fri, 6 Mar 2026 13:33:39 -0500 Subject: [PATCH 32/36] Update baseapp.mdx --- sdk/v0.53/learn/concepts/baseapp.mdx | 146 ++++++++++----------------- 1 file changed, 55 insertions(+), 91 deletions(-) diff --git a/sdk/v0.53/learn/concepts/baseapp.mdx b/sdk/v0.53/learn/concepts/baseapp.mdx index 08f62880..9e3f6887 100644 --- a/sdk/v0.53/learn/concepts/baseapp.mdx +++ b/sdk/v0.53/learn/concepts/baseapp.mdx @@ -2,13 +2,13 @@ title: BaseApp Overview --- -**BaseApp** is the execution engine of every Cosmos SDK chain. It implements ABCI — the protocol CometBFT uses to communicate with the application — and translates those calls into module execution, transaction processing, and state transitions. {/* cosmos/cosmos-sdk baseapp/baseapp.go:85 — // BaseApp reflects the ABCI application implementation. */} +`BaseApp` is the execution engine of every Cosmos SDK chain. It implements [ABCI (Application Blockchain Interface)](/sdk/next/learn/intro/sdk-app-architecture#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. Without it, every chain would need to independently implement ABCI handling, signature verification, gas metering, message routing, block hook orchestration, and state commitment. +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` sits between CometBFT and the modules: ``` CometBFT (consensus engine) @@ -22,11 +22,11 @@ Modules (x/auth, x/bank, x/staking, ...) State (KVStores) ``` -CometBFT drives the block lifecycle by calling ABCI methods on BaseApp. BaseApp handles each call, delegating to registered lifecycle hooks and routing messages to the appropriate module handlers. Modules contain the business logic. KVStores hold the resulting state. +CometBFT drives the block lifecycle by calling ABCI methods on `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`](https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/baseapp.go) is defined in `baseapp/baseapp.go`. It holds references to everything needed to run a chain: {/* cosmos/cosmos-sdk baseapp/baseapp.go:86 — type BaseApp struct — the complete BaseApp struct definition */} +[`BaseApp`](https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/baseapp.go) is defined in `baseapp/baseapp.go`. It holds references to everything needed to run a chain: ```go type BaseApp struct { @@ -41,38 +41,34 @@ type BaseApp struct { mempool mempool.Mempool anteHandler sdk.AnteHandler // ante handler for fee and auth postHandler sdk.PostHandler // post handler, optional + // ... + sealed bool + // ... chainID string - // ... additional fields + // ... } ``` -- **`cms` (CommitMultiStore)**: the root state store. All module substores are mounted here, and all state reads and writes during block execution pass through it. {/* cosmos/cosmos-sdk baseapp/baseapp.go:92 — cms storetypes.CommitMultiStore // Main (uncached) state */} -- **`storeLoader`**: a function that opens and mounts the individual module stores at application startup. {/* cosmos/cosmos-sdk baseapp/baseapp.go:94 — storeLoader StoreLoader // function to handle store loading, may be overridden with SetStoreLoader() */} -- **`grpcQueryRouter`**: routes incoming gRPC queries to the correct module's query handler. {/* cosmos/cosmos-sdk baseapp/baseapp.go:95 — grpcQueryRouter *GRPCQueryRouter // router for redirecting gRPC query calls */} -- **`msgServiceRouter`**: routes each message in a transaction to the correct module's `MsgServer` handler. {/* cosmos/cosmos-sdk baseapp/baseapp.go:96 — msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages */} -- **`txDecoder`**: decodes raw transaction bytes from CometBFT into an `sdk.Tx`. {/* cosmos/cosmos-sdk baseapp/baseapp.go:98 — txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx */} -- **`anteHandler`**: runs before message execution to handle cross-cutting concerns: signature verification, sequence validation, and fee deduction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:102 — anteHandler sdk.AnteHandler // ante handler for fee and auth */} -- **`postHandler`**: optional middleware that runs after message execution — used for tasks such as tipping or post-execution state adjustments. {/* cosmos/cosmos-sdk baseapp/baseapp.go:103 — postHandler sdk.PostHandler // post handler, optional */} +For a complete list of fields, see the [`BaseApp` struct definition](https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/baseapp.go). -## Initialization and sealing - -BaseApp has a two-phase configuration model. During app construction, you call setter methods to configure it: +- `cms` (CommitMultiStore): the root state store. All module substores are mounted here, and all state reads and writes during block execution pass through it. {/* cosmos/cosmos-sdk baseapp/baseapp.go:92 — cms storetypes.CommitMultiStore // Main (uncached) state */} +- `storeLoader`: a function that opens and mounts the individual module stores at application startup. {/* cosmos/cosmos-sdk baseapp/baseapp.go:94 — storeLoader StoreLoader // function to handle store loading, may be overridden with SetStoreLoader() */} +- `grpcQueryRouter`: routes incoming gRPC queries to the correct module's query handler. {/* cosmos/cosmos-sdk baseapp/baseapp.go:95 — grpcQueryRouter *GRPCQueryRouter // router for redirecting gRPC query calls */} +- `msgServiceRouter`: routes each message in a transaction to the correct module's `MsgServer` handler. {/* cosmos/cosmos-sdk baseapp/baseapp.go:96 — msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages */} +- `txDecoder`: decodes raw transaction bytes from CometBFT into an `sdk.Tx`. {/* cosmos/cosmos-sdk baseapp/baseapp.go:98 — txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx */} +- `anteHandler`: runs before message execution to handle cross-cutting concerns: signature verification, sequence validation, and fee deduction. {/* cosmos/cosmos-sdk baseapp/baseapp.go:102 — anteHandler sdk.AnteHandler // ante handler for fee and auth */} +- `postHandler`: optional middleware that runs after message execution — used for tasks such as tipping or post-execution state adjustments. {/* cosmos/cosmos-sdk baseapp/baseapp.go:103 — postHandler sdk.PostHandler // post handler, optional */} +- `sealed`: set to `true` after `LoadLatestVersion` is called. Setter methods panic if called after sealing. -```go -app.MountKVStores(keys) -app.SetAnteHandler(...) -app.SetPreBlocker(...) -app.SetBeginBlocker(...) -app.SetEndBlocker(...) -``` +## Initialization and sealing -Once construction is complete, `LoadLatestVersion` is called. It validates that required components are present, initializes the check state, and **seals** the app. {/* cosmos/cosmos-sdk baseapp/baseapp.go — func (app *BaseApp) Init() validates cms and stateStore, initializes checkState, then seals */} After sealing, calling any setter method panics. +{/* TODO: move the full initialization and sealing explanation (two-phase config model, code example, ordering rules, why everything must be in NewApp) to the app.go page, where it's actionable. Keep only the concept here. */} -This is why all BaseApp configuration must happen inside the `NewApp` constructor — not lazily at request time. It also means the order of calls in `app.go` matters: stores must be mounted and handlers set before `LoadLatestVersion` is called. +`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`. {/* cosmos/cosmos-sdk baseapp/baseapp.go — func (app *BaseApp) Init() validates cms and stateStore, initializes checkState, then seals */} Any setter called after sealing panics. On first launch, CometBFT calls `InitChain`, which populates state from the genesis file before the chain begins producing blocks. How this shapes the structure of `app.go` is covered in the next section. ## Transaction decoding -Transactions arrive from CometBFT as raw bytes. Before BaseApp can validate or execute them, it must decode them into the SDK's transaction type using the `TxDecoder`: +Transactions arrive from CometBFT as raw bytes. Before `BaseApp` can validate or execute them, it must decode them into the SDK's transaction type using the `TxDecoder`: ``` []byte tx @@ -82,21 +78,21 @@ TxDecoder sdk.Tx ``` -This step happens before the transaction enters the execution pipeline. Without it, BaseApp cannot inspect messages, run the ante handler, or route execution to the correct module. This is why `txDecoder` is part of BaseApp construction — transaction execution starts with decoding. +This step happens before the transaction enters the execution pipeline. Without it, `BaseApp` cannot inspect messages, run the ante handler, 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: +`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. -- **`Simulate`** (`ExecModeSimulate`): runs a transaction for gas estimation without committing state. +- `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. +- `Simulate` (`ExecModeSimulate`): runs a transaction for gas estimation without committing state. This separation ensures that validation and simulation cannot accidentally mutate committed application state. ## The transaction execution pipeline -When BaseApp processes a transaction, it runs through a structured pipeline: +When `BaseApp` processes a transaction, it runs through a structured pipeline: ``` RunTx @@ -106,33 +102,19 @@ RunTx └─ PostHandler → optional post-execution middleware ``` -If the AnteHandler fails, message execution does not begin. If any message fails, the transaction reverts atomically — all messages commit or none do. +If the AnteHandler fails, message execution does not begin. If any message fails, the transaction reverts atomically; all messages commit or none do. ## AnteHandler -The `AnteHandler` is middleware that runs before any message in a transaction executes. It is responsible for: +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. -- Verifying cryptographic signatures -- Validating and incrementing the account sequence number -- Deducting transaction fees from the signer's account -- Setting up the gas meter for the transaction - -The AnteHandler is configured at app startup: {/* cosmos/cosmos-sdk baseapp/options.go:235 — func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) — sets the AnteHandler; panics if called after app is sealed */} - -```go -app.SetAnteHandler(ante.NewAnteHandler(ante.HandlerOptions{ - AccountKeeper: app.AccountKeeper, - BankKeeper: app.BankKeeper, - SignModeHandler: app.txConfig.SignModeHandler(), - // ... -})) -``` +{/* TODO: move the AnteHandler configuration example (SetAnteHandler, HandlerOptions, ordering rules) to the app.go page, where it's actionable. Keep only the concept here. then link out to the app.go page. */} If the AnteHandler fails, the transaction is rejected and its messages never execute. ## Message routing -When a transaction contains messages, BaseApp routes each one to the appropriate module handler using the `MsgServiceRouter`. +When a transaction contains messages, `BaseApp` routes each one to the appropriate module handler using the `MsgServiceRouter`. ```go type MsgServiceRouter struct { @@ -144,35 +126,35 @@ type MsgServiceRouter struct { The routing process has three steps: -1. **Registration**: During app startup, each module calls `RegisterService`, which registers its message handlers keyed by message type URL (e.g., `/cosmos.bank.v1beta1.MsgSend`). {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:71 — func (msr *MsgServiceRouter) RegisterService iterates service methods and registers handlers in the routes map keyed by message type URL */} -2. **Lookup**: At execution time, `Handler` looks up the registered handler for the incoming message's type URL. {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:54 — func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler — returns handler by type URL from routes map */} -3. **Execution**: The retrieved handler invokes the module's `MsgServer` implementation, which validates inputs, applies business rules, and updates state through the keeper. +1. Registration: During app startup, each module calls `RegisterService`, which registers its message handlers keyed by message type URL (e.g., `/cosmos.bank.v1beta1.MsgSend`). {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:71 — func (msr *MsgServiceRouter) RegisterService iterates service methods and registers handlers in the routes map keyed by message type URL */} +2. Lookup: At execution time, `Handler` looks up the registered handler for the incoming message's type URL. {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:54 — func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler — returns handler by type URL from routes map */} +3. Execution: The retrieved handler invokes the module's `MsgServer` implementation, which validates inputs, applies business rules, and updates state through the keeper. -This routing is entirely type-URL-based. Modules do not need to know about each other at the routing level — BaseApp is the neutral coordinator. +This routing is entirely type-URL-based. Modules do not need to know about each other at the routing level; `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. +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: {/* cosmos/cosmos-sdk baseapp/baseapp.go:323 — func (app *BaseApp) MountKVStores mounts IAVL or DB-backed stores for each registered KV store key */} +`BaseApp` owns the `CommitMultiStore` that holds all module state. At app startup, each module registers its store key, and `BaseApp` mounts the corresponding store: {/* cosmos/cosmos-sdk baseapp/baseapp.go:323 — func (app *BaseApp) MountKVStores mounts IAVL or DB-backed stores for each registered KV store key */} ```go app.MountKVStores(keys) ``` -Before executing each transaction, 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. This is how transaction atomicity is implemented: all messages in a transaction either all commit or none do. +Before executing each transaction, `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 through `CheckTx`. BaseApp runs the AnteHandler in CheckTx mode to validate signatures, check sequence numbers, and verify fees. Transactions that fail `CheckTx` are rejected and do not enter the mempool. +Before a transaction reaches block execution, it goes through `CheckTx`. `BaseApp` runs the AnteHandler in CheckTx mode to validate signatures, check sequence numbers, and verify fees. Transactions that fail `CheckTx` are rejected and do not enter the mempool. `CheckTx` does not execute messages and does not write state. It exists solely to protect the mempool from invalid or malformed transactions before they consume block space. ## Coordinating block execution -When CometBFT calls `FinalizeBlock`, BaseApp runs the full block execution pipeline in order: {/* cosmos/cosmos-sdk baseapp/abci.go:868 — func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) — FinalizeBlock entry point */} +When CometBFT calls `FinalizeBlock`, `BaseApp` runs the full block execution pipeline in order: {/* cosmos/cosmos-sdk baseapp/abci.go:868 — func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) — FinalizeBlock entry point */} ``` FinalizeBlock @@ -194,50 +176,31 @@ FinalizeBlock **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. {/* cosmos/cosmos-sdk baseapp/abci.go:802 — EndBlock called after all transactions have executed */} -After `FinalizeBlock` completes, BaseApp computes and returns the app hash — the Merkle root of all committed state. {/* cosmos/cosmos-sdk baseapp/abci.go:903 — res.AppHash = app.workingHash() — app hash computed and returned in the FinalizeBlock response */} When CometBFT subsequently calls `Commit`, BaseApp persists the state changes to disk. {/* cosmos/cosmos-sdk baseapp/abci.go:935 — func (app *BaseApp) Commit() — persists finalized state to disk */} - -## Module Manager - -BaseApp's block lifecycle hooks — `PreBlock`, `BeginBlock`, and `EndBlock` — are registered as function callbacks via `SetPreBlocker`, `SetBeginBlocker`, and `SetEndBlocker`. Every standard SDK application implements those callbacks by delegating to a `ModuleManager`. {/* cosmos/cosmos-sdk baseapp/options.go:195 — func (app *BaseApp) SetPreBlocker sets abciHandlers.PreBlocker */} {/* cosmos/cosmos-sdk baseapp/options.go:203 — func (app *BaseApp) SetBeginBlocker sets abciHandlers.BeginBlocker */} +After `FinalizeBlock` completes, `BaseApp` computes and returns the app hash — the Merkle root of all committed state. {/* cosmos/cosmos-sdk baseapp/abci.go:903 — res.AppHash = app.workingHash() — app hash computed and returned in the FinalizeBlock response */} When CometBFT subsequently calls `Commit`, `BaseApp` persists the state changes to disk. {/* cosmos/cosmos-sdk baseapp/abci.go:935 — func (app *BaseApp) Commit() — persists finalized state to disk */} -The `ModuleManager` holds the full set of registered modules along with their execution ordering: {/* cosmos/cosmos-sdk types/module/module.go:287 — type Manager struct { Modules map[string]any; OrderInitGenesis []string; OrderBeginBlockers []string; OrderEndBlockers []string; ... } */} +{/* todo: add link to lifecycle page and module page */} -```go -type Manager struct { - Modules map[string]any // all registered modules, keyed by module name - OrderPreBlockers []string // module execution order for PreBlock - OrderBeginBlockers []string // module execution order for BeginBlock - OrderEndBlockers []string // module execution order for EndBlock - OrderInitGenesis []string // module execution order for chain initialization - // ... -} -``` +## Module Manager -When BaseApp calls the `BeginBlocker` hook, the application delegates to `mm.BeginBlock(ctx)`, which iterates `OrderBeginBlockers` and calls each module's `BeginBlock` in sequence. {/* cosmos/cosmos-sdk simapp/app.go:687 — func (app *SimApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) calls app.ModuleManager.BeginBlock(ctx) */} The same pattern applies to `PreBlock` and `EndBlock`. +`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. -Module ordering matters: some modules depend on others having already updated state before they run. BaseApp provides the hook points; the `ModuleManager` handles ordering and dispatch. How ordering is configured is covered in the next section on `app.go`. +{/* TODO: move the ModuleManager struct, SetPreBlocker/SetBeginBlocker/SetEndBlocker wiring, mm.BeginBlock(ctx) delegation, and ordering configuration to the app.go page, where it's actionable. add link to that page*/} ## Block proposal and vote extensions -ABCI 2.0 added a proposal phase that runs during consensus rounds, before `FinalizeBlock` executes. BaseApp exposes four handlers for this phase, with default implementations wired at construction: +ABCI 2.0 added a proposal phase that runs during consensus rounds, before `FinalizeBlock` 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 use `PrepareProposal` to 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. The canonical use case is oracle price feeds — validators inject off-chain data into consensus so it becomes available on-chain at block start. +- `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 use `PrepareProposal` to 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. All four are configurable in `app.go` via `SetPrepareProposal`, `SetProcessProposal`, `SetExtendVoteHandler`, and `SetVerifyVoteExtensionHandler`. Chains that do not need custom behavior can leave the defaults in place. -## Other ABCI methods - -Beyond the block execution path, BaseApp implements several ABCI methods used for initialization and metadata: - -- **`InitChain`**: populates state from genesis during chain initialization. -- **`Info`**: returns application metadata to CometBFT — the last committed block height and app version. -- **`Commit`**: persists finalized state changes to disk and advances the committed root state. +{/* TODO: move configuration examples and override patterns for PrepareProposal, ProcessProposal, ExtendVote, and VerifyVoteExtension to the app.go page, where it's actionable. Add link to that page. */} -## Summary +## Putting it all together -BaseApp is the execution engine of a Cosmos SDK chain: +`BaseApp` is the execution engine of a Cosmos SDK chain: ``` CometBFT → ABCI → BaseApp → Modules → State @@ -245,4 +208,5 @@ CometBFT → ABCI → BaseApp → Modules → State It implements ABCI, coordinates the block lifecycle (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 explains `app.go`: how BaseApp is instantiated, configured, and wired with modules to produce a complete, running chain. +The next section explains `app.go`: how `BaseApp` is instantiated, configured, and wired with modules to produce a complete, running chain. +{/* todo: add link to app.go page */} \ No newline at end of file From 4db92d07c64ffd2fcc6e1d581867444c99c994b5 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:28:57 -0500 Subject: [PATCH 33/36] Update baseapp.mdx --- sdk/v0.53/learn/concepts/baseapp.mdx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sdk/v0.53/learn/concepts/baseapp.mdx b/sdk/v0.53/learn/concepts/baseapp.mdx index 9e3f6887..f7f67e88 100644 --- a/sdk/v0.53/learn/concepts/baseapp.mdx +++ b/sdk/v0.53/learn/concepts/baseapp.mdx @@ -64,7 +64,7 @@ For a complete list of fields, see the [`BaseApp` struct definition](https://git {/* TODO: move the full initialization and sealing explanation (two-phase config model, code example, ordering rules, why everything must be in NewApp) to the app.go page, where it's actionable. Keep only the concept here. */} -`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`. {/* cosmos/cosmos-sdk baseapp/baseapp.go — func (app *BaseApp) Init() validates cms and stateStore, initializes checkState, then seals */} Any setter called after sealing panics. On first launch, CometBFT calls `InitChain`, which populates state from the genesis file before the chain begins producing blocks. How this shapes the structure of `app.go` is covered in the next section. +`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`. {/* cosmos/cosmos-sdk baseapp/baseapp.go — func (app *BaseApp) Init() validates cms and stateStore, initializes checkState, then seals */} 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. {/* cosmos/cosmos-sdk baseapp/abci.go:89 — InitChain calls StoreConsensusParams if req.ConsensusParams != nil */} 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. {/* cosmos/cosmos-sdk baseapp/abci.go:82,122,124 — stateManager.SetState for finalize+check, WithBlockGasMeter(InfiniteGasMeter), abciHandlers.InitChainer */} How this shapes the structure of `app.go` is covered in the next section. ## Transaction decoding @@ -86,9 +86,11 @@ This step happens before the transaction enters the execution pipeline. Without - `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. {/* cosmos/cosmos-sdk baseapp/abci.go — PrepareProposal sets prepareProposalState from app.cms and runs runTx in execModePrepareProposal */} +- `ProcessProposal` (`ExecModeProcessProposal`): runs on every validator to validate an incoming proposal. Also executes against a branched state that is never committed. {/* cosmos/cosmos-sdk baseapp/abci.go — ProcessProposal sets processProposalState from app.cms and runs runTx in execModeProcessProposal */} - `Simulate` (`ExecModeSimulate`): runs a transaction for gas estimation without committing state. -This separation ensures that validation and simulation cannot accidentally mutate committed application state. +This separation ensures that validation, proposal handling, and simulation cannot accidentally mutate committed application state. ## The transaction execution pipeline @@ -110,7 +112,7 @@ The `AnteHandler` is middleware that runs before any message in a transaction ex {/* TODO: move the AnteHandler configuration example (SetAnteHandler, HandlerOptions, ordering rules) to the app.go page, where it's actionable. Keep only the concept here. then link out to the app.go page. */} -If the AnteHandler fails, the transaction is rejected and its messages never execute. +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 — fee deduction and sequence increment — are already flushed to `finalizeBlockState` and will be committed with the block. {/* cosmos/cosmos-sdk baseapp/baseapp.go:932 — msCache.Write() is called immediately after AnteHandler success, before RunMsgs begins */} Fees are charged even for transactions whose messages fail. ## Message routing @@ -176,7 +178,7 @@ FinalizeBlock **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. {/* cosmos/cosmos-sdk baseapp/abci.go:802 — EndBlock called after all transactions have executed */} -After `FinalizeBlock` completes, `BaseApp` computes and returns the app hash — the Merkle root of all committed state. {/* cosmos/cosmos-sdk baseapp/abci.go:903 — res.AppHash = app.workingHash() — app hash computed and returned in the FinalizeBlock response */} When CometBFT subsequently calls `Commit`, `BaseApp` persists the state changes to disk. {/* cosmos/cosmos-sdk baseapp/abci.go:935 — func (app *BaseApp) Commit() — persists finalized state to disk */} +After `FinalizeBlock` completes, `BaseApp` computes and returns the app hash — the Merkle root of all committed state. {/* cosmos/cosmos-sdk baseapp/abci.go:999,1013 — res.AppHash = app.workingHash() — app hash computed via cms.WorkingHash() and returned in the FinalizeBlock response */} 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. {/* cosmos/cosmos-sdk baseapp/abci.go:1062,1085,1087 — app.cms.Commit(), stateManager.SetState(execModeCheck, ...), stateManager.ClearState(execModeFinalize) */} {/* todo: add link to lifecycle page and module page */} From f44739914477c3a8517901c8665ba407d43fe236 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:28:59 -0500 Subject: [PATCH 34/36] Update modules.mdx --- sdk/v0.53/learn/concepts/modules.mdx | 126 +++++++++++++++------------ 1 file changed, 69 insertions(+), 57 deletions(-) diff --git a/sdk/v0.53/learn/concepts/modules.mdx b/sdk/v0.53/learn/concepts/modules.mdx index 3b670267..eb107643 100644 --- a/sdk/v0.53/learn/concepts/modules.mdx +++ b/sdk/v0.53/learn/concepts/modules.mdx @@ -32,10 +32,10 @@ State is the data persisted on the chain: everything that transactions can read Each module owns its own part of the blockchain state. For example: -- The `x/auth` module stores account metadata. {/* cosmos/cosmos-sdk x/auth/keeper/keeper.go:111 — Accounts IndexedMap persists account metadata keyed by address */} -- The `x/bank` module stores account balances. {/* cosmos/cosmos-sdk x/bank/keeper/view.go:68 — Balances IndexedMap stores account balances keyed by (address, denom) */} +- The `x/auth` module stores account metadata. +- The `x/bank` module stores account balances. -Modules do not share storage directly. Each module has its own key-value store namespace located in a `multistore`. {/* cosmos/cosmos-sdk store/types/store.go:417 — KVStoreKey struct gives each module a named, isolated sub-store in the multistore */} For example, the bank module's store might contain entries like: +Modules do not share storage directly. Each module has its own key-value store namespace located in a `multistore`. For example, the bank module's store might contain entries like: ``` // x/bank store (conceptual) @@ -43,13 +43,15 @@ balances | cosmos1abc...xyz | uatom → 1000000 balances | cosmos1def...uvw | uatom → 500000 ``` -The key encodes the namespace, address, and denomination. The value is the encoded amount. No other module can read or write these entries directly, only the [module's keeper](#keeper) can. {/* cosmos/example x/counter/keeper/keeper.go:17 — counter and params are unexported fields; external packages cannot access them directly */} +The key encodes the namespace, address, and denomination. The value is the encoded amount. No other module can read or write these entries directly, only the [module's keeper](#keeper) can. + +Most state is updated by user-submitted transactions. Some state, called **params**, holds module configuration values. Params are stored in the module's KV store like any other state, but are updated through a privileged message (`MsgUpdateParams`) that only the designated `authority` address can execute. ### Messages -As you learned in the [Transactions, Messages, and Queries](/sdk/v0.53/learn/concepts/transactions) section, each module defines the actions it allows via messages (`sdk.Msg`). +As you learned in the [Transactions, Messages, and Queries](/sdk/next/learn/concepts/transactions) section, each module defines the actions it allows via messages (`sdk.Msg`). -Messages are defined in the module’s `tx.proto` file and implemented by that module’s `MsgServer`. {/* cosmos/example proto/example/counter/v1/tx.proto:11 — service Msg in tx.proto defines the module’s messages */} Here is a simplified example of a message definition from the bank module: +Messages are defined in the module's `tx.proto` file and implemented by that module's `MsgServer`. Here is a simplified example of a message definition from the bank module: ``` message MsgSend { @@ -59,25 +61,25 @@ message MsgSend { } ``` -This message represents a request to transfer tokens from one account to another. {/* cosmos/cosmos-sdk proto/cosmos/bank/v1beta1/tx.proto:39 — message MsgSend with from_address, to_address, amount */} +This message represents a request to transfer tokens from one account to another. When included in a transaction and executed: -1. The sender’s balance is checked. {/* cosmos/cosmos-sdk x/bank/keeper/send.go:308 — GetBalance reads sender balance inside subUnlockedCoins before deduction */} -2. The amount is deducted from `from_address`. {/* cosmos/cosmos-sdk x/bank/keeper/send.go:240 — subUnlockedCoins called on fromAddr in SendCoins */} -3. The amount is credited to `to_address`. {/* cosmos/cosmos-sdk x/bank/keeper/send.go:245 — addCoins called on toAddr in SendCoins */} +1. The sender's balance is checked. +2. The amount is deducted from `from_address`. +3. The amount is credited to `to_address`. -When a transaction is executed, the SDK's `BaseApp` struct routes each message to the module that defines it. {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:54 — Handler returns the registered MsgServiceHandler by message type URL */} +When a transaction is executed, the SDK's `BaseApp` struct routes each message to the module that defines it. ### Queries -Modules expose read-only access to their state through query services, defined in `query.proto`. {/* cosmos/example proto/example/counter/v1/query.proto:9 — service Query defines read-only RPCs */} Queries do not modify state and do not go through block execution. For example: +Modules expose read-only access to their state through query services, defined in `query.proto`. Queries do not modify state and do not go through block execution. For example: ``` // query.proto rpc Balance(QueryBalanceRequest) returns (QueryBalanceResponse); ``` -In this case, the caller provides an address and denomination; the query reads the balance from the x/bank keeper and returns it without modifying state. {/* cosmos/cosmos-sdk proto/cosmos/bank/v1beta1/query.proto:18 — Balance rpc takes QueryBalanceRequest (address + denom) and returns QueryBalanceResponse */} +In this case, the caller provides an address and denomination; the query reads the balance from the x/bank keeper and returns it without modifying state. ### Business logic @@ -86,21 +88,24 @@ While messages define intent, the `MsgServer` and `Keeper` work together to exec Business logic is conceptually split across two layers: - The **`MsgServer`** handles the transaction-facing logic: it validates inputs, applies message-level business rules, and where required checks authorization. Once satisfied, it delegates state transitions to the keeper. -- The **`Keeper`** is the module’s state access layer: it defines the storage schema and provides the sanctioned APIs for reading and writing state. Rather than thinking of the keeper as "just storage," it is more accurate to view it as the module’s domain API — it owns how state changes. +- The **`Keeper`** is the module's state access layer: it defines the storage schema and provides the sanctioned APIs for reading and writing state. Rather than thinking of the keeper as "just storage," it is more accurate to view it as the module's domain API — it owns how state changes. --- ### Message execution (`MsgServer`) -Each module implements a **MsgServer**, which is invoked by `BaseApp`’s message router when a message is routed to that module. {/* cosmos/cosmos-sdk baseapp/msg_service_router.go:54 — Handler looks up registered MsgServiceHandler by type URL and invokes it */} +Each module implements a `MsgServer`, which is invoked by `BaseApp`'s message router when a message is routed to that module. The `MsgServer` is responsible for: -- Checking authorization when required {/* cosmos/example x/counter/keeper/msg_server.go:30 — checks m.authority != msg.Authority in UpdateParams */} -- Delegating to keeper methods that validate inputs, enforce business rules, and perform state transitions {/* cosmos/example x/counter/keeper/msg_server.go:20 — Add delegates to m.AddCount, which validates, charges fees, updates state, and emits events */} -- Returning a response {/* cosmos/example x/counter/keeper/msg_server.go:24 — returns MsgAddResponse{UpdatedCount: newCount} */} +- Checking authorization when required +- Delegating to keeper methods that validate inputs, enforce business rules, and perform state transitions +- Returning a response + +{/* todo: link to Build a Module tutorial */} +The following examples are from the `x/counter` tutorial, which walks you through building a module that lets accounts increment a shared counter. -The following is the counter module’s `Add` handler: {/* cosmos/example x/counter/keeper/msg_server.go:19 */} +The following is the counter module's `Add` handler which is the `MsgServer` method that processes a request to increment the counter: ```go func (m msgServer) Add(ctx context.Context, request *types.MsgAddRequest) (*types.MsgAddResponse, error) { @@ -113,9 +118,9 @@ func (m msgServer) Add(ctx context.Context, request *types.MsgAddRequest) (*type } ``` -`Add` is permissionless — any account can call it — and the handler itself is intentionally thin. {/* cosmos/example x/counter/keeper/msg_server.go:19 — no authority check; delegates entirely to keeper.AddCount */} All validation (overflow check, `MaxAddValue` limit), fee charging, state mutation, event emission, and telemetry live in the keeper’s `AddCount` method. {/* cosmos/example x/counter/keeper/keeper.go:83 — AddCount validates amount, checks MaxAddValue, charges AddCost, updates counter, emits event, records metric */} This keeps `msg_server.go` focused on message routing and authorization, while the keeper’s named methods can be reused from other entry points such as `BeginBlock` or governance proposals. +In this example, `Add` is permissionless; any account can call it. All validation (overflow check, `MaxAddValue` limit), fee charging, state mutation, event emission, and telemetry live in the keeper's `AddCount` method. This keeps `msg_server.go` focused on message routing and authorization, while the keeper's named methods can be reused from other entry points such as `BeginBlock` or governance proposals. -For privileged messages like `MsgUpdateParams`, the `MsgServer` checks the caller against the stored authority before proceeding: {/* cosmos/example x/counter/keeper/msg_server.go:76 — m.authority != msg.Authority check returns ErrInvalidSigner if mismatched */} +For privileged messages like `MsgUpdateParams`, the `MsgServer` checks the caller against the stored authority before proceeding (`x/counter`): {/* todo: link to counter tutorial */} ```go func (m msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { @@ -138,43 +143,50 @@ func (m msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) ## Keeper -A **keeper** is a Go struct that holds references to the module’s KV store (or collections schema) and any external keepers it depends on. {/* cosmos/example x/counter/keeper/keeper.go:15 — Keeper struct holds Schema, counter, params, bankKeeper, and authority */} It provides typed methods for reading and writing state and defines the module’s storage layout. +A module's **keeper** is its state access layer. It owns the module's KV store and provides typed methods for reading and writing state. The store fields are unexported, so nothing outside the `keeper` package can access them directly. -It is the only sanctioned way to access a module’s state; no other code should access the store directly. {/* cosmos/example x/counter/keeper/keeper.go:17 — counter and params are unexported; only Keeper methods can access them from outside the package */} For example, the counter keeper: +The `MsgServer` and `QueryServer` both embed the keeper. The `MsgServer` handles authorization; the keeper handles everything else: input validation, business rules, fee charging, state transitions, and event emission. Because all business logic runs through keeper methods, the same rules apply whether the caller is a message handler, a block hook, or a governance proposal. + +The `x/counter` keeper holds two state items and two external dependencies: {/* todo: link to counter tutorial */} ```go type Keeper struct { Schema collections.Schema - counter collections.Item[uint64] - params collections.Item[types.Params] - bankKeeper types.BankKeeper + counter collections.Item[uint64] // the counter value + params collections.Item[types.Params] // max add value, fee per increment + bankKeeper types.BankKeeper // used to charge fees on increment // authority is the address capable of executing a MsgUpdateParams message. // Typically, this should be the x/gov module account. authority string } -func (k *Keeper) GetParams(ctx context.Context) (types.Params, error) { - return k.params.Get(ctx) +// GetCount returns the current counter value. +func (k *Keeper) GetCount(ctx context.Context) (uint64, error) { + count, err := k.counter.Get(ctx) + if err != nil && !errors.Is(err, collections.ErrNotFound) { + return 0, err + } + return count, nil } ``` -The `MsgServer` and `QueryServer` both embed the keeper and call its methods. {/* cosmos/example x/counter/keeper/msg_server.go:17 — msgServer struct embeds *Keeper */} Authorization is enforced at the `MsgServer` boundary, while domain validation (input checks, business rules, fee charging, state transitions, and event emission) lives in the keeper's named methods such as `AddCount`. This lets keeper methods be safely reused from other entry points like `BeginBlock` or governance proposals. +`counter` and `params` use `collections.Item`, a typed single-value entry backed by the module's KV store. `GetCount` treats `ErrNotFound` as zero, so the counter starts at 0 without an explicit genesis initialization. -Some messages, like `MsgUpdateParams`, are privileged and may only be executed by a specific address. The `authority` field in the keeper stores that address (typically the governance module account). {/* cosmos/example x/counter/keeper/keeper.go:43 — authority defaults to authtypes.NewModuleAddress(govtypes.ModuleName).String() */} +The `authority` field holds the address allowed to call privileged messages like `MsgUpdateParams`, typically the governance module account. The `MsgServer` checks this before executing those messages. ### Inter-module access -Modules are isolated by default. Each module owns its state, and direct access to that state is restricted through the module’s keeper. Other modules cannot arbitrarily mutate another module’s storage. +Modules are isolated by default. Each module owns its state, and direct access to that state is restricted through the module's keeper. Other modules cannot arbitrarily mutate another module's storage. -Instead, modules interact through explicitly defined keeper interfaces. {/* cosmos/example x/counter/types/expected_keepers.go:10 — BankKeeper interface declares only the methods counter requires from bank */} +Instead, modules interact through explicitly defined keeper interfaces. `x/counter` uses this pattern to call into `x/bank`. {/* todo: link to counter tutorial */} For example: -- The staking module calls methods on the bank keeper to transfer tokens. {/* cosmos/cosmos-sdk x/staking/types/expected_keepers.go:39 — DelegateCoinsFromAccountToModule in staking's expected BankKeeper interface */} -- The governance module calls parameter update methods on other modules. {/* cosmos/cosmos-sdk x/gov/abci.go:189 — proposal executor calls keeper.Router().Handler(msg) to route each passed proposal message to the target module */} +- The staking module calls methods on the bank keeper to transfer tokens. +- The governance module calls parameter update methods on other modules. -Each module defines an `expected_keepers.go` file that declares the interfaces it requires from other modules. {/* cosmos/example x/counter/types/expected_keepers.go:11 — SendCoinsFromAccountToModule is the only method counter exposes from bank */} This makes cross-module dependencies explicit: a module can only call methods the other module has chosen to expose. +Each module defines an `expected_keepers.go` file that declares the interfaces it requires from other modules. This makes cross-module dependencies explicit: a module can only call methods the other module has chosen to expose. This design keeps dependencies auditable and prevents accidental or unsafe cross-module state mutation. @@ -182,11 +194,11 @@ This design keeps dependencies auditable and prevents accidental or unsafe cross Modules may execute logic at specific points in the block lifecycle by implementing optional hook interfaces: -- `HasBeginBlocker` — runs logic at the start of each block {/* cosmos/cosmos-sdk core/appmodule/module.go:73 — HasBeginBlocker interface; BeginBlock runs before transactions are processed in a block */} -- `HasEndBlocker` — runs logic at the end of each block {/* cosmos/cosmos-sdk core/appmodule/module.go:83 — HasEndBlocker interface; EndBlock runs after transactions are processed in a block */} -- `HasPreBlocker` — runs logic before BeginBlock, used for consensus parameter changes {/* cosmos/cosmos-sdk core/appmodule/module.go:65 — HasPreBlocker returns ResponsePreBlock; IsConsensusParamsChanged() at line 60 signals consensus parameter updates to the caller */} +- `HasBeginBlocker` — runs logic at the start of each block +- `HasEndBlocker` — runs logic at the end of each block +- `HasPreBlocker` — runs logic before BeginBlock, used for consensus parameter changes -Hooks are optional, and modules should only implement the hooks they need. {/* cosmos/example x/counter/module.go:77 — BeginBlock comment: "This method is included for example purposes. Not every module needs these methods." */} These hooks are invoked during block execution by a `ModuleManager` in the `BaseApp`, which calls each registered module's hooks in a configured order. {/* todo: link to baseapp#module-manager */} +Hooks are optional, and modules should only implement the hooks they need. These hooks are invoked during block execution by a `ModuleManager` in the `BaseApp`, which calls each registered module's hooks in a configured order. {/* todo: link to baseapp#module-manager */} {/* todo: link to baseapp#module-manager — ModuleManager coordinates hook invocation order across all modules */} @@ -196,12 +208,12 @@ Modules define how their state is initialized when the chain starts. Each module implements: -- `DefaultGenesis` — returns the module's default genesis state {/* cosmos/cosmos-sdk types/module/module.go:77 — DefaultGenesis method in HasGenesisBasics interface */} -- `ValidateGenesis` — validates the genesis state before the chain starts {/* cosmos/cosmos-sdk types/module/module.go:78 — ValidateGenesis method in HasGenesisBasics interface */} -- `InitGenesis` — writes the genesis state into the module's store at chain start {/* cosmos/cosmos-sdk types/module/module.go:193 — InitGenesis method in HasGenesis interface */} -- `ExportGenesis` — reads the module's current state and serializes it as genesis data {/* cosmos/cosmos-sdk types/module/module.go:194 — ExportGenesis method in HasGenesis interface */} +- `DefaultGenesis` — returns the module's default genesis state +- `ValidateGenesis` — validates the genesis state before the chain starts +- `InitGenesis` — writes the genesis state into the module's store at chain start +- `ExportGenesis` — reads the module's current state and serializes it as genesis data -During `InitChain`, BaseApp calls each module’s `InitGenesis` to populate its state from `genesis.json`. {/* cosmos/cosmos-sdk baseapp/abci.go:53 — InitChain entry point */} {/* cosmos/cosmos-sdk types/module/module.go:489 — module manager iterates OrderInitGenesis calling each module’s InitGenesis with genesis data */} +During `InitChain`, BaseApp calls each module's `InitGenesis` to populate its state from `genesis.json`. {/* todo: link to lifecycle page init genesis section */} @@ -225,7 +237,7 @@ x/ └── mymodule/ # Your custom module ``` -Each subdirectory under `x/` is a self-contained module. +Each subdirectory under `x/` is a self-contained module. An application composes multiple modules together to form a complete blockchain. Inside a module, you will typically see a structure like this: @@ -237,7 +249,7 @@ x/mymodule/ │ ├── msg_server.go # MsgServer implementation │ └── query_server.go # QueryServer implementation ├── types/ -│ ├── expected_keepers.go # Interfaces for other modules’ keepers +│ ├── expected_keepers.go # Interfaces for other modules' keepers │ ├── keys.go # Store key definitions │ └── *.pb.go # Generated from proto definitions └── module.go # AppModule implementation and hook registration @@ -254,27 +266,27 @@ proto/myapp/mymodule/v1/ ``` - **`keeper/`** - Contains the `Keeper` struct (state access) and implementations of the `MsgServer` and `QueryServer` interfaces. {/* cosmos/example x/counter/keeper/keeper.go:15 — Keeper struct; cosmos/example x/counter/keeper/msg_server.go:16 — msgServer; cosmos/example x/counter/keeper/query_server.go:15 — NewQueryServer */} + Contains the `Keeper` struct (state access) and implementations of the `MsgServer` and `QueryServer` interfaces. - **`types/`** - Defines the module’s public types: generated protobuf structs, {/* cosmos/example x/counter/types/tx.pb.go:33 — MsgAddRequest generated from tx.proto */} store keys, {/* cosmos/example x/counter/types/keys.go:4 — ModuleName and StoreKey constants */} and the `expected_keepers.go` interfaces that declare what this module needs from other modules. {/* cosmos/example x/counter/types/expected_keepers.go:10 — BankKeeper interface */} + Defines the module's public types: generated protobuf structs, store keys, and the `expected_keepers.go` interfaces that declare what this module needs from other modules. - **`module.go`** - Connects the module to the application and registers genesis handlers, block hooks, and message and query services. {/* cosmos/example x/counter/module.go:95 — RegisterServices registers MsgServer and QueryServer with the module configurator */} + Connects the module to the application and registers genesis handlers, block hooks, and message and query services. - **`.proto` files** - Define messages, queries, state schemas, and genesis state. {/* cosmos/example proto/example/counter/v1/tx.proto:11 — tx.proto defines service Msg; query.proto defines service Query */} Go code is generated from these files and used throughout the module. + Define messages, queries, state schemas, and genesis state. Go code is generated from these files and used throughout the module. ## Modules in context Putting everything together you've learned so far: -- **Accounts** authorize transactions. -- **Transactions** carry messages and execution constraints. -- **Blocks** order transactions and define commit boundaries. -- **Modules** define the business rules of execution. -- **MsgServer** validates messages and orchestrates state transitions. -- **Keeper** performs controlled reads and writes to module state. +- **Accounts** authorize transactions. +- **Transactions** carry messages and execution constraints. +- **Blocks** order transactions and define commit boundaries. +- **Modules** define the business rules of execution. +- **MsgServer** validates messages and orchestrates state transitions. +- **Keeper** performs controlled reads and writes to module state. - **State** persists the deterministic result of execution. ``` @@ -283,4 +295,4 @@ Account → Transaction → Message → Module → MsgServer → Keeper → Stat In the next section, you will look more closely at how module state is stored, how genesis initializes it, and how the application commits deterministic state transitions. -{/* todo: link to next page */} \ No newline at end of file +{/* todo: link to next page */} From 80c78957a042f27534af50687e0717ed062cf563 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:29:01 -0500 Subject: [PATCH 35/36] Update encoding.mdx --- sdk/v0.53/learn/concepts/encoding.mdx | 102 +++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 4 deletions(-) diff --git a/sdk/v0.53/learn/concepts/encoding.mdx b/sdk/v0.53/learn/concepts/encoding.mdx index b46cc192..650352f8 100644 --- a/sdk/v0.53/learn/concepts/encoding.mdx +++ b/sdk/v0.53/learn/concepts/encoding.mdx @@ -4,7 +4,7 @@ title: Encoding In the previous section, you learned that state is stored as raw byte arrays in a key-value store and that modules encode structured data into those bytes before writing. This page explains how that encoding works, why the Cosmos SDK chose Protocol Buffers, and what that means for module development. -## What is Protocol Buffers +## What is Protobuf? [Protocol Buffers](https://protobuf.dev/) (protobuf) is a language-neutral, binary serialization format developed by Google. You define your data structures in `.proto` files using a schema language, then generate code in your target language from that schema. The generated code handles serialization (converting structured data into bytes) and deserialization (converting bytes back into structured data). @@ -18,15 +18,15 @@ message MsgSend { } ``` -Each field has a name, a type, and a field number. The field numbers are what protobuf actually uses during encoding — field names are only present in the schema, not in the serialized bytes. This is why protobuf is compact and why you can rename fields without breaking wire compatibility. +Each field has a name, a type, and a field number. The field numbers are what protobuf actually uses during encoding; field names are only present in the schema, not in the serialized bytes. This is why protobuf is compact and why you can rename fields without breaking wire compatibility. ## Why the Cosmos SDK uses protobuf The Cosmos SDK uses protobuf for a fundamental reason: consensus requires determinism. -Every validator in the network independently executes each block. After executing a block, each validator computes the AppHash, a cryptographic hash of the application state. For validators to agree on the AppHash, they must all produce exactly the same bytes for every piece of state they write. +Every validator in the network independently executes each block. After execution, each validator computes the AppHash, a cryptographic hash of the application state. For validators to agree on the AppHash, they must all produce exactly the same bytes for every piece of state they write. -Protobuf satisfies this requirement. Given the same input, protobuf always produces the same bytes. There is no randomness in field ordering, no optional whitespace, and no locale-specific formatting. Every validator encoding the same data with protobuf produces an identical byte sequence. +Protobuf satisfies this requirement. The Cosmos SDK enforces **deterministic protobuf serialization** so that identical data always produces identical bytes across all nodes. This is formalized in [ADR-027 (Deterministic Protobuf Serialization)](https://github.com/cosmos/cosmos-sdk/blob/v0.53.0/docs/architecture/adr-027-deterministic-protobuf-serialization.md), which specifies constraints such as requiring fields to appear in ascending field-number order and varint encodings to be as short as possible. The SDK validates incoming transactions against these rules before processing them, so a non-deterministically encoded transaction is rejected rather than producing divergent state. Every validator encoding the same data under these rules produces an identical byte sequence. Beyond determinism, protobuf provides: @@ -53,6 +53,50 @@ State KV values (binary) REST API responses (JSON) Genesis KV state (binary) Block explorers (JSON) ``` +Note: genesis data is distributed as JSON in `genesis.json`, but during chain initialization `InitGenesis` deserializes that JSON into protobuf structs and writes them to the KV store as binary. The KV store—and therefore the AppHash—only ever contains the binary form. + +## Transaction encoding + +Transactions are protobuf messages defined in `cosmos.tx.v1beta1`. A transaction is composed of three parts: {/* cosmos/cosmos-sdk proto/cosmos/tx/v1beta1/tx.proto:16 — message Tx { TxBody body = 1; AuthInfo auth_info = 2; repeated bytes signatures = 3; } */} + +```text +Tx + ├─ TxBody + │ └─ repeated google.protobuf.Any messages + ├─ AuthInfo + │ ├─ repeated SignerInfo (each with sequence) + │ └─ Fee + └─ repeated bytes signatures +``` + +- **TxBody** contains the messages to execute, serialized as `repeated google.protobuf.Any messages`. {/* cosmos/cosmos-sdk proto/cosmos/tx/v1beta1/tx.proto:104 — repeated google.protobuf.Any messages = 1 — TxBody.messages field */} +- **AuthInfo** contains signer information (including the per-signer sequence number) and fee. +- **signatures** contains the cryptographic signatures, one per signer. + +Messages inside the transaction are stored as `google.protobuf.Any` values so that a single transaction can contain multiple message types from different modules. + +When a user submits a transaction, the SDK encodes it as a `TxRaw`—a flat structure with the `TxBody` bytes, `AuthInfo` bytes, and signatures already serialized—and broadcasts that binary representation over the network. {/* cosmos/cosmos-sdk x/auth/tx/encoder.go:27 — return proto.Marshal(raw) — DefaultTxEncoder serializes TxRaw to binary bytes for broadcast */} + +## Transaction signing and `SignDoc` + +Transactions are not signed directly. Instead, the SDK constructs a deterministic structure called a **`SignDoc`**, which defines exactly what bytes the signer commits to: {/* cosmos/cosmos-sdk proto/cosmos/tx/v1beta1/tx.proto:50 — message SignDoc { bytes body_bytes = 1; bytes auth_info_bytes = 2; string chain_id = 3; uint64 account_number = 4; } */} + +```text +SignDoc + ├─ body_bytes (serialized TxBody) + ├─ auth_info_bytes (serialized AuthInfo, includes sequence per signer) + ├─ chain_id (prevents cross-chain replay) + └─ account_number (ties the signature to a specific on-chain account) +``` + +The `SignDoc` is serialized to protobuf binary and then signed with the user's private key: + +```text +signature = Sign(proto.Marshal(SignDoc)) +``` + +Because `SignDoc` is serialized deterministically, all validators verify the exact same bytes when checking transaction signatures. The per-signer sequence number lives in `AuthInfo.SignerInfo.sequence` and is included in `auth_info_bytes`, which is part of `SignDoc`—this is what prevents replay attacks. + ## How protobuf is used in modules Every piece of data that crosses a module boundary, gets stored, or travels over the wire is defined in a `.proto` file and serialized with protobuf. @@ -65,6 +109,21 @@ Every piece of data that crosses a module boundary, gets stored, or travels over **Genesis**: Genesis state is defined in `genesis.proto`. `InitGenesis` and `ExportGenesis` use protobuf to deserialize genesis state from `genesis.json` and serialize it back. {/* cosmos/cosmos-sdk x/auth/module.go:158 — cdc.MustUnmarshalJSON(data, &genesisState) — InitGenesis uses the JSON codec to unmarshal JSON genesis data into a protobuf-typed GenesisState struct */} {/* cosmos/cosmos-sdk x/auth/module.go:166 — return cdc.MustMarshalJSON(gs) — ExportGenesis serializes the genesis state back to JSON using the codec */} +**State keys**: While values in the KV store are protobuf-encoded structs, keys are manually constructed byte sequences defined by each module. A module might store balances under a key like: + +```text +0x02 | address_bytes → protobuf-encoded Coin +``` + +Modules define their key layout with byte-prefix constants to avoid collisions with other modules and to maintain a predictable state layout. In practice, modules do not build raw keys by hand. Instead they create **prefix stores**, which automatically prepend a module-specific key prefix to all reads and writes: {/* cosmos/cosmos-sdk store/prefix/store.go:23 — func NewStore(parent types.KVStore, prefix []byte) Store — creates a prefix store scoped to a byte prefix */} + +```go +store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixBalances) +// all Set/Get calls on `store` are automatically namespaced under KeyPrefixBalances +``` + +This gives each module a logical namespace inside its own store key without requiring global coordination over raw byte offsets. + A concrete example shows how a module reads and writes typed state as bytes: {/* cosmos/cosmos-sdk codec/proto_codec.go:49 — func (pc *ProtoCodec) Marshal(o gogoproto.Message) ([]byte, error) — the Marshal method called as k.cdc.Marshal() in keeper code */} {/* cosmos/cosmos-sdk codec/proto_codec.go:78 — func (pc *ProtoCodec) Unmarshal(bz []byte, ptr gogoproto.Message) error — the Unmarshal method called as k.cdc.Unmarshal() in keeper code */} ```go @@ -121,6 +180,23 @@ message BaseAccount { The `Any` field holds the serialized public key bytes plus a type URL like `/cosmos.crypto.secp256k1.PubKey`. When the SDK reads the account, it uses the type URL to look up the concrete Go type, then unmarshals the bytes into that type. +#### Messages inside transactions + +Transaction messages are the most common use of `Any` in the SDK. A transaction can carry multiple message types from different modules—`bank.MsgSend`, `staking.MsgDelegate`, `gov.MsgVote`—in a single `TxBody`. Because protobuf requires concrete types at the field level, each message is packed into an `Any` before being placed inside the transaction: + +```text +MsgSend + ↓ pack into Any +Any { + type_url: "/cosmos.bank.v1beta1.MsgSend" + value: +} + ↓ placed in TxBody.messages +repeated google.protobuf.Any messages +``` + +During decoding, the SDK reads the `type_url`, looks up the concrete type in the interface registry, and unmarshals the bytes into the correct message struct. This is why every `sdk.Msg` implementation must be registered with `RegisterInterfaces` before the application starts. + This lookup is handled by the **interface registry**. ### Interface registry @@ -231,6 +307,24 @@ func (m msgServer) Add(ctx context.Context, req *types.MsgAdd) (*types.MsgAddRes The generated gRPC service stub is registered with BaseApp's message router, connecting the handler to the transaction execution pipeline automatically. +## Legacy Amino encoding + +Before adopting protobuf, the Cosmos SDK used a custom serialization format called **Amino**. The `LegacyAmino` type still exists in the SDK and wraps `*amino.Codec`: {/* cosmos/cosmos-sdk codec/amino.go:16 — type LegacyAmino struct { Amino *amino.Codec } */} + +```go +type LegacyAmino struct { + Amino *amino.Codec +} +``` + +Amino was used for transaction encoding, JSON signing documents, and interface serialization. It has been replaced by protobuf for all of these roles, but some legacy components still reference it: + +- `LegacyAmino` is still present in the codec package for backward-compatibility +- `LegacyAminoPubKey` (multisig) is registered alongside protobuf public key types +- Some older chains, hardware wallets, and client tooling depend on Amino JSON signing + +New modules and chains should use protobuf exclusively. The `LegacyAmino` codec is maintained for compatibility but is not used in the consensus-critical path. + ## Encoding in context Every layer of the Cosmos SDK depends on encoding: From e4387b7e9960aa3a7e919fe70880ac50dcce0ef7 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:29:03 -0500 Subject: [PATCH 36/36] Update store.mdx --- sdk/v0.53/learn/concepts/store.mdx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/sdk/v0.53/learn/concepts/store.mdx b/sdk/v0.53/learn/concepts/store.mdx index e7f323af..31a0dc9f 100644 --- a/sdk/v0.53/learn/concepts/store.mdx +++ b/sdk/v0.53/learn/concepts/store.mdx @@ -82,6 +82,28 @@ Only modified nodes are rewritten, and unchanged nodes are shared across version Because of this, state transitions must be deterministic, encoding must be deterministic, and transaction ordering must be consistent. +### Database backend + +The IAVL tree does not store data in memory. It writes versioned nodes to a **database backend** — a key-value store on disk. {/* cosmos/cosmos-sdk store/iavl/store.go:64 — NewMutableTree receives a db dbm.DB parameter that is the backing database */} + +The Cosmos SDK uses [CometBFT's `db` package](https://github.com/cometbft/cometbft-db) to abstract over the database implementation. The default backend is **PebbleDB**, a high-performance LSM-tree store. Other supported backends include **RocksDB** and **memDB** (in-memory, for testing). {/* cosmos/cosmos-sdk server/start.go — openDB selects the backend based on app.toml's db_backend config value */} + +The full storage stack from top to bottom is: + +``` +Module keeper + ↓ +KVStore (namespaced, wrapped with gas/trace) + ↓ +CommitMultiStore (multistore, computes app hash) + ↓ +IAVL tree (versioned Merkle tree) + ↓ +Database backend (PebbleDB on disk) +``` + +The database backend is selected at node startup and configured in `app.toml`. Application code never interacts with it directly; the store layer owns that boundary. + ## Store types in the SDK Beyond the base KVStore, the SDK provides several specialized store wrappers.