BaseApp

The Cosmos SDK is a framework for developing application-specific blockchains, and is — by design — a very modular system. The core functionality of a Cosmos SDK application is implemented in the BaseApp type, which comes with a built-in implementation of ABCI.

To understand how state-transitions work in a Cosmos SDK application, it’s important to note that BaseAppmaintains four primary volatile states and a root state. The root state is the canonical state of the application, while the volatile states represent sub-states at different stages of execution during state-transitions.

Syntax

For the sake of this document, let us denote the canonical, root state of the application as root. The four primary volatile states are

  • checkState (used during CheckTx),
  • prepareProposalState (used during PrepareProposal),
  • processProposalState (used during ProcessProposal), and
  • finalizeBlockState (used during FinalizeBlock).

Store Branching

Internally, the root state is only a single CommitMultiStore, from which the four volatile states are derived by using a mechanism called store branching (this is performed in the CacheWrap function). The types can be visualized as follows:

ABCI

The Application-Blockchain Interface (ABCI) is a generic interface that sits between a state-machine and its consensus engine. The Cosmos SDK BaseApp comes with a built-in implementation of the interface. The main ABCI messages that BaseApp uses are important for understanding the state-transition function of an appchain.

CheckTx

The CheckTx function is called by the underlying consensus engine when a new unconfirmed transaction is received by a full-node. The role of CheckTx is to guard the full-node’s mempool from spam transactions. During execution CheckTx does the following:

  1. Extract the sdk.Msgs from the transaction.
  2. Perform stateless and then stateful by calling Validate() on each message in the transaction.
  3. Perform non-module related stateful checks on the account. Namely, validate signatures, check that enough fees are provided, and that the sending account has enough funds to pay said fees.

Note that no precise gas counting occurs in (2), as the messages are not processed1. This also means that gas fees are not charged for the resources used during the execution of CheckTx. Usually, it is the responsibility of the AnteHandler to check that the provided gas is larger than some minimum amount (based on the raw transaction size), in order to prevent spam.

Steps (2) and (3) are performed in the runTx() function, which CheckTx() calls with the runTxModeCheck mode.

CheckTx State Updates

During each step of CheckTx(), the volatile state checkState is used for all reads and writes, such that it tracks temporary changes without modifying the root state; checkState is branched off the last committed state from root. When the AnteHandler is executed by CheckTx, it branches the already branched checkState. This has the side effect that checkState is only updated on success, e.g., if the AnteHandler fails, the state transitions are discarded.

PrepareProposal

The ABCI PrepareProposal method ensures that only valid transactions are committed to state. Transactions in the mempool are iterated over via the mempool’s Select() method, then the runTx() function is called, which encodes and validates each transaction. From there, the AnteHandler is executed. Roughly speaking, the execution flow is responsible for:

  1. Extracting the sdk.Msgs from the transaction.
  2. Performing stateless then stateful checks (as in CheckTx).
  3. Performing any additional application-specific checks such as ensuring certain conditions are met before a transaction is proposed.
  4. Returning an abci.ResponseCheckTx with Code = 0 if successful.

PrepareProposal does not commit any state updates. It complements the ProcessProposal method which is executed after it.

ProcessProposal

The ProcessProposal method is called by the BaseApp as part of the ABCI message flow, and is executed during the FinalizeBlock phase of the consensus engine. The purpose of this function is to give more control to the application for block validation; allowing it to check all transactions in a proposed block before the validator sends the prevote for the block.

  1. Validates the proposed block by checking all transactions in it.
  2. Checks the proposed block against the current application state to ensure that is valid, and that it can be executed.
  3. Updates the application’s state based on the proposal.
  4. Returns a response to the consensus engine indicating the result of the proposal processing.

Footnotes

  1. sdk.Msgs only need to be processed when the canonical (root) state needs to be updated, which happens during FinalizeBlock.