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 duringCheckTx
),prepareProposalState
(used duringPrepareProposal
),processProposalState
(used duringProcessProposal
), andfinalizeBlockState
(used duringFinalizeBlock
).
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:
- Extract the
sdk.Msg
s from the transaction. - Perform stateless and then stateful by calling
Validate()
on each message in the transaction. - 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:
- Extracting the
sdk.Msg
s from the transaction. - Performing stateless then stateful checks (as in
CheckTx
). - Performing any additional application-specific checks such as ensuring certain conditions are met before a transaction is proposed.
- Returning an
abci.ResponseCheckTx
withCode = 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.
- Validates the proposed block by checking all transactions in it.
- Checks the proposed block against the current application state to ensure that is valid, and that it can be executed.
- Updates the application’s state based on the proposal.
- Returns a response to the consensus engine indicating the result of the proposal processing.
Footnotes
-
sdk.Msg
s only need to be processed when the canonical (root
) state needs to be updated, which happens duringFinalizeBlock
. ↩