# Baseapp

# Pre-requisite Readings

# Introduction

BaseApp is a base type that implements the core of an SDK application, namely:

  • The Application Blockchain Interface, for the state-machine to communicate with the underlying consensus engine (e.g. Tendermint).
  • A Router, to route messages and queries to the appropriate module.
  • Different states, as the state-machine can have different volatile states updated based on the ABCI message received.

The goal of BaseApp is to provide the fundamental layer of an SDK application that developers can easily extend to build their own custom application. Usually, developers will create a custom type for their application, like so:

Copy type App struct { // reference to a BaseApp *bam.BaseApp // list of application store keys // list of application keepers // module manager }

Extending the application with BaseApp gives the former access to all of BaseApp's methods. This allows developers to compose their custom application with the modules they want, while not having to concern themselves with the hard work of implementing the ABCI, the routing and state management logic.

# Type Definition

The BaseApp type holds many important parameters for any Cosmos SDK based application.

Copy type BaseApp struct { // nolint: maligned // initialized on creation logger log.Logger name string // application name from abci.Info db dbm.DB // common DB backend cms sdk.CommitMultiStore // Main (uncached) state storeLoader StoreLoader // function to handle store loading, may be overridden with SetStoreLoader() router sdk.Router // handle any kind of message queryRouter sdk.QueryRouter // router for redirecting query calls txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx // set upon LoadVersion or LoadLatestVersion. baseKey *sdk.KVStoreKey // Main KVStore in cms anteHandler sdk.AnteHandler // ante handler for fee and auth initChainer sdk.InitChainer // initialize state with validators and state blob beginBlocker sdk.BeginBlocker // logic to run before any txs endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes addrPeerFilter sdk.PeerFilter // filter peers by address and port idPeerFilter sdk.PeerFilter // filter peers by node ID fauxMerkleMode bool // if true, IAVL MountStores uses MountStoresDB for simulation speed. // volatile states: // // checkState is set on InitChain and reset on Commit // deliverState is set on InitChain and BeginBlock and set to nil on Commit checkState *state // for CheckTx deliverState *state // for DeliverTx // an inter-block write-through cache provided to the context during deliverState interBlockCache sdk.MultiStorePersistentCache // absent validators from begin block voteInfos []abci.VoteInfo // consensus params // TODO: Move this in the future to baseapp param store on main store. consensusParams *abci.ConsensusParams // The minimum gas prices a validator is willing to accept for processing a // transaction. This is mainly used for DoS and spam prevention. minGasPrices sdk.DecCoins // flag for sealing options and parameters to a BaseApp sealed bool // block height at which to halt the chain and gracefully shutdown haltHeight uint64 // minimum block time (in Unix seconds) at which to halt the chain and gracefully shutdown haltTime uint64 // application's version string appVersion string }

Let us go through the most important components.

Note: Not all parameters are described, only the most important ones. Refer to the type definition for the full list.

First, the important parameters that are initialized during the bootstrapping of the application:

  • CommitMultiStore: This is the main store of the application, which holds the canonical state that is committed at the end of each block. This store is not cached, meaning it is not used to update the application's volatile (un-committed) states. The CommitMultiStore is a multi-store, meaning a store of stores. Each module of the application uses one or multiple KVStores in the multi-store to persist their subset of the state.
  • Database: The db is used by the CommitMultiStore to handle data persistence.
  • Router: The router facilitates the routing of messages to the appropriate module for it to be processed. Here a message refers to the transaction components that need to be processed by the application in order to update the state, and not to ABCI messages which implement the interface between the application and the underlying consensus engine.
  • Query Router: The query router facilitates the routing of queries to the appropriate module for it to be processed. These queries are not ABCI messages themselves, but they are relayed to the application from the underlying consensus engine via the ABCI message Query.
  • TxDecoder: It is used to decode raw transaction bytes relayed by the underlying Tendermint engine.
  • BaseKey: This key is used to access the main store in the CommitMultiStore. The main store is used to persist data related to the core of the application, like consensus parameters.
  • AnteHandler: This handler is used to handle signature verification, fee payment, and other pre-message execution checks when a transaction is received. It's executed during CheckTx/RecheckTx and DeliverTx.
  • InitChainer, BeginBlocker and EndBlocker: These are the functions executed when the application receives the InitChain, BeginBlock and EndBlock ABCI messages from the underlying Tendermint engine.

Then, parameters used to define volatile states (i.e. cached states):

  • checkState: This state is updated during CheckTx, and reset on Commit.
  • deliverState: This state is updated during DeliverTx, and set to nil on Commit and gets re-initialized on BeginBlock.

Finally, a few more important parameterd:

  • voteInfos: This parameter carries the list of validators whose precommit is missing, either because they did not vote or because the proposer did not include their vote. This information is carried by the Context and can be used by the application for various things like punishing absent validators.
  • minGasPrices: This parameter defines the minimum gas prices accepted by the node. This is a local parameter, meaning each full-node can set a different minGasPrices. It is used in the AnteHandler during CheckTx, mainly as a spam protection mechanism. The transaction enters the mempool only if the gas prices of the transaction are greater than one of the minimum gas price in minGasPrices (e.g. if minGasPrices == 1uatom,1photon, the gas-price of the transaction must be greater than 1uatom OR 1photon).
  • appVersion: Version of the application. It is set in the application's constructor function.

# Constructor

Copy func NewBaseApp( name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp), ) *BaseApp { // ... }

The BaseApp constructor function is pretty straightforward. The only thing worth noting is the possibility to provide additional options to the BaseApp, which will execute them in order. The options are generally setter functions for important parameters, like SetPruning() to set pruning options or SetMinGasPrices() to set the node's min-gas-prices.

Naturally, developers can add additional options based on their application's needs.

# State Updates

The BaseApp maintains two primary volatile states and a root or main state. The main state is the canonical state of the application and the volatile states, checkState and deliverState, are used to handle state transitions in-between the main state made during Commit.

Internally, there is only a single CommitMultiStore which we refer to as the main or root state. From this root state, we derive two volatile state through a mechanism called cache-wrapping. The types can be illustrated as follows:

Types

# InitChain State Updates

During InitChain, the two volatile states, checkState and deliverState are set by cache-wrapping the root CommitMultiStore. Any subsequent reads and writes happen on cached versions of the CommitMultiStore.

InitChain

# CheckTx State Updates

During CheckTx, the checkState, which is based off of the last committed state from the root store, is used for any reads and writes. Here we only execute the AnteHandler and verify a router exists for every message in the transaction. Note, when we execute the AnteHandler, we cache-wrap the already cache-wrapped checkState. This has the side effect that if the AnteHandler fails, the state transitions won't be reflected in the checkState -- i.e. checkState is only updated on success.

CheckTx

# BeginBlock State Updates

During BeginBlock, the deliverState is set for use in subsequent DeliverTx ABCI messages. The deliverState is based off of the last committed state from the root store and is cache-wrapped. Note, the deliverState is set to nil on Commit.

BeginBlock

# DeliverTx State Updates

The state flow for DeliverTx is nearly identical to CheckTx except state transitions occur on the deliverState and messages in a transaction are executed. Similarly to CheckTx, state transitions occur on a doubly cache-wrapped state -- deliverState. Successful message execution results in writes being committed to deliverState. Note, if message execution fails, state transitions from the AnteHandler are persisted.

DeliverTx

# Commit State Updates

During Commit all the state transitions that occurred in the deliverState are finally written to the root CommitMultiStore which in turn is committed to disk and results in a new application root hash. These state transitions are now considered final. Finally, the checkState is set to the newly committed state and deliverState is set to nil to be reset on BeginBlock.

Commit

# Routing

When messages and queries are received by the application, they must be routed to the appropriate module in order to be processed. Routing is done via baseapp, which holds a router for messages, and a query router for queries.

# Message Routing

Messages need to be routed after they are extracted from transactions, which are sent from the underlying Tendermint engine via the CheckTx and DeliverTx ABCI messages. To do so, baseapp holds a router which maps paths (string) to the appropriate module handler using the .Route(ctx sdk.Context, path string) function. Usually, the path is the name of the module.

The default router included in baseapp is stateless. However, some applications may want to make use of more stateful routing mechanisms such as allowing governance to disable certain routes or point them to new modules for upgrade purposes. For this reason, the sdk.Context is also passed into the Route function of the Router interface. For a stateless router that doesn't want to make use of this, can just ignore the ctx.

The application's router is initilalized with all the routes using the application's module manager, which itself is initialized with all the application's modules in the application's constructor.

# Query Routing

Similar to messages, queries need to be routed to the appropriate module's querier. To do so, baseapp holds a query router, which maps module names to module queriers. The queryRouter is called during the initial stages of query processing, which is done via the Query ABCI message.

Just like the router, the query router is initilalized with all the query routes using the application's module manager, which itself is initialized with all the application's modules in the application's constructor.

# Main ABCI Messages

The Application-Blockchain Interface (ABCI) is a generic interface that connects a state-machine with a consensus engine to form a functional full-node. It can be wrapped in any language, and needs to be implemented by each application-specific blockchain built on top of an ABCI-compatible consensus engine like Tendermint.

The consensus engine handles two main tasks:

  • The networking logic, which mainly consists in gossiping block parts, transactions and consensus votes.
  • The consensus logic, which results in the deterministic ordering of transactions in the form of blocks.

It is not the role of the consensus engine to define the state or the validity of transactions. Generally, transactions are handled by the consensus engine in the form of []bytes, and relayed to the application via the ABCI to be decoded and processed. At keys moments in the networking and consensus processes (e.g. beginning of a block, commit of a block, reception of an unconfirmed transaction, ...), the consensus engine emits ABCI messages for the state-machine to act on.

Developers building on top of the Cosmos SDK need not implement the ABCI themselves, as baseapp comes with a built-in implementation of the interface. Let us go through the main ABCI messages that baseapp implements: CheckTx and DeliverTx

# CheckTx

CheckTx is sent by the underlying consensus engine when a new unconfirmed (i.e. not yet included in a valid block) transaction is received by a full-node. The role of CheckTx is to guard the full-node's mempool (where unconfirmed transactions are stored until they are included in a block) from spam transactions. Unconfirmed transactions are relayed to peers only if they pass CheckTx.

CheckTx() can perform both stateful and stateless checks, but developers should strive to make them lightweight. In the Cosmos SDK, after decoding transactions, CheckTx() is implemented to do the following checks:

  1. Extract the messages from the transaction.
  2. Perform stateless checks by calling ValidateBasic() on each of the messages. This is done first, as stateless checks are less computationally expensive than stateful checks. If ValidateBasic() fail, CheckTx returns before running stateful checks, which saves resources.
  3. Perform non-module related stateful checks on the account. This step is mainly about checking that the message signatures are valid, that enough fees are provided and that the sending account has enough funds to pay for said fees. Note that no precise gas counting occurs here, as messages are not processed. Usually, the AnteHandler will check that the gas provided with the transaction is superior to a minimum reference gas amount based on the raw transaction size, in order to avoid spam with transactions that provide 0 gas.
  4. Ensure that a Route exists for each message, but do not actually process messages. Messages only need to be processed when the canonical state need to be updated, which happens during DeliverTx.

Steps 2. and 3. are performed by the AnteHandler in the RunTx() function, which CheckTx() calls with the runTxModeCheck mode. During each step of CheckTx(), a special volatile state called checkState is updated. This state is used to keep track of the temporary changes triggered by the CheckTx() calls of each transaction without modifying the main canonical state . For example, when a transaction goes through CheckTx(), the transaction's fees are deducted from the sender's account in checkState. If a second transaction is received from the same account before the first is processed, and the account has consumed all its funds in checkState during the first transaction, the second transaction will fail CheckTx() and be rejected. In any case, the sender's account will not actually pay the fees until the transaction is actually included in a block, because checkState never gets committed to the main state. The checkState is reset to the latest state of the main state each time a blocks gets committed.

CheckTx returns a response to the underlying consensus engine of type abci.ResponseCheckTx. The response contains:

  • Code (uint32): Response Code. 0 if successful.
  • Data ([]byte): Result bytes, if any.
  • Log (string): The output of the application's logger. May be non-deterministic.
  • Info (string): Additional information. May be non-deterministic.
  • GasWanted (int64): Amount of gas requested for transaction. It is provided by users when they generate the transaction.
  • GasUsed (int64): Amount of gas consumed by transaction. During CheckTx, this value is computed by multiplying the standard cost of a transaction byte by the size of the raw transaction. Next is an example: Copy package ante import ( sdk "github.com/cosmos/cosmos-sdk/types" err "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/multisig" "github.com/cosmos/cosmos-sdk/x/auth/keeper" "github.com/cosmos/cosmos-sdk/x/auth/types" ) var ( _ TxWithMemo = (*types.StdTx)(nil) // assert StdTx implements TxWithMemo ) // ValidateBasicDecorator will call tx.ValidateBasic and return any non-nil error. // If ValidateBasic passes, decorator calls next AnteHandler in chain. Note, // ValidateBasicDecorator decorator will not get executed on ReCheckTx since it // is not dependent on application state. type ValidateBasicDecorator struct{} func NewValidateBasicDecorator() ValidateBasicDecorator { return ValidateBasicDecorator{} } func (vbd ValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { // no need to validate basic on recheck tx, call next antehandler if ctx.IsReCheckTx() { return next(ctx, tx, simulate) } if err := tx.ValidateBasic(); err != nil { return ctx, err } return next(ctx, tx, simulate) } // Tx must have GetMemo() method to use ValidateMemoDecorator type TxWithMemo interface { sdk.Tx GetMemo() string } // ValidateMemoDecorator will validate memo given the parameters passed in // If memo is too large decorator returns with error, otherwise call next AnteHandler // CONTRACT: Tx must implement TxWithMemo interface type ValidateMemoDecorator struct { ak keeper.AccountKeeper } func NewValidateMemoDecorator(ak keeper.AccountKeeper) ValidateMemoDecorator { return ValidateMemoDecorator{ ak: ak, } } func (vmd ValidateMemoDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { memoTx, ok := tx.(TxWithMemo) if !ok { return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") } params := vmd.ak.GetParams(ctx) memoLength := len(memoTx.GetMemo()) if uint64(memoLength) > params.MaxMemoCharacters { return ctx, err.Wrapf(err.ErrMemoTooLarge, "maximum number of characters is %d but received %d characters", params.MaxMemoCharacters, memoLength, ) } return next(ctx, tx, simulate) } // ConsumeTxSizeGasDecorator will take in parameters and consume gas proportional // to the size of tx before calling next AnteHandler. Note, the gas costs will be // slightly over estimated due to the fact that any given signing account may need // to be retrieved from state. // // CONTRACT: If simulate=true, then signatures must either be completely filled // in or empty. // CONTRACT: To use this decorator, signatures of transaction must be represented // as types.StdSignature otherwise simulate mode will incorrectly estimate gas cost. type ConsumeTxSizeGasDecorator struct { ak keeper.AccountKeeper } func NewConsumeGasForTxSizeDecorator(ak keeper.AccountKeeper) ConsumeTxSizeGasDecorator { return ConsumeTxSizeGasDecorator{ ak: ak, } } func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { sigTx, ok := tx.(SigVerifiableTx) if !ok { return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") } params := cgts.ak.GetParams(ctx) ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*sdk.Gas(len(ctx.TxBytes())), "txSize") // simulate gas cost for signatures in simulate mode if simulate { // in simulate mode, each element should be a nil signature sigs := sigTx.GetSignatures() for i, signer := range sigTx.GetSigners() { // if signature is already filled in, no need to simulate gas cost if sigs[i] != nil { continue } acc := cgts.ak.GetAccount(ctx, signer) var pubkey crypto.PubKey // use placeholder simSecp256k1Pubkey if sig is nil if acc == nil || acc.GetPubKey() == nil { pubkey = simSecp256k1Pubkey } else { pubkey = acc.GetPubKey() } // use stdsignature to mock the size of a full signature simSig := types.StdSignature{ Signature: simSecp256k1Sig[:], PubKey: pubkey, } sigBz := types.ModuleCdc.MustMarshalBinaryLengthPrefixed(simSig) cost := sdk.Gas(len(sigBz) + 6) // If the pubkey is a multi-signature pubkey, then we estimate for the maximum // number of signers. if _, ok := pubkey.(multisig.PubKeyMultisigThreshold); ok { cost *= params.TxSigLimit } ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*cost, "txSize") } } return next(ctx, tx, simulate) }
  • Events ([]cmn.KVPair): Key-Value tags for filtering and indexing transactions (eg. by account). See events for more.
  • Codespace (string): Namespace for the Code.

# RecheckTx

After Commit, CheckTx is run again on all transactions that remain in the node's local mempool after filtering those included in the block. To prevent the mempool from rechecking all transactions every time a block is committed, the configuration option mempool.recheck=false can be set. As of Tendermint v0.32.1, an additional Type parameter is made available to the CheckTx function that indicates whether an incoming transaction is new (CheckTxType_New), or a recheck (CheckTxType_Recheck). This allows certain checks like signature verification can be skipped during CheckTxType_Recheck.

# DeliverTx

When the underlying consensus engine receives a block proposal, each transaction in the block needs to be processed by the application. To that end, the underlying consensus engine sends a DeliverTx message to the application for each transaction in a sequential order.

Before the first transaction of a given block is processed, a volatile state called deliverState is intialized during BeginBlock. This state is updated each time a transaction is processed via DeliverTx, and committed to the main state when the block is committed, after what is is set to nil.

DeliverTx performs the exact same steps as CheckTx, with a little caveat at step 3 and the addition of a fifth step:

  1. The AnteHandler does not check that the transaction's gas-prices is sufficient. That is because the min-gas-prices value gas-prices is checked against is local to the node, and therefore what is enough for one full-node might not be for another. This means that the proposer can potentially include transactions for free, although they are not incentivised to do so, as they earn a bonus on the total fee of the block they propose.
  2. For each message in the transaction, route to the appropriate module's handler. Additional stateful checks are performed, and the cache-wrapped multistore held in deliverState's context is updated by the module's keeper. If the handler returns successfully, the cache-wrapped multistore held in context is written to deliverState CacheMultiStore.

During step 5., each read/write to the store increases the value of GasConsumed. You can find the default cost of each operation:

Copy type GasConfig struct { HasCost Gas DeleteCost Gas ReadCostFlat Gas ReadCostPerByte Gas WriteCostFlat Gas WriteCostPerByte Gas IterNextCostFlat Gas }

At any point, if GasConsumed > GasWanted, the function returns with Code != 0 and DeliverTx fails.

DeliverTx returns a response to the underlying consensus engine of type abci.ResponseDeliverTx. The response contains:

  • Code (uint32): Response Code. 0 if successful.
  • Data ([]byte): Result bytes, if any.
  • Log (string): The output of the application's logger. May be non-deterministic.
  • Info (string): Additional information. May be non-deterministic.
  • GasWanted (int64): Amount of gas requested for transaction. It is provided by users when they generate the transaction.
  • GasUsed (int64): Amount of gas consumed by transaction. During DeliverTx, this value is computed by multiplying the standard cost of a transaction byte by the size of the raw transaction, and by adding gas each time a read/write to the store occurs.
  • Events ([]cmn.KVPair): Key-Value tags for filtering and indexing transactions (eg. by account). See events for more.
  • Codespace (string): Namespace for the Code.

# RunTx, AnteHandler and RunMsgs

# RunTx

RunTx is called from CheckTx/DeliverTx to handle the transaction, with runTxModeCheck or runTxModeDeliver as parameter to differentiate between the two modes of execution. Note that when RunTx receives a transaction, it has already been decoded.

The first thing RunTx does upon being called is to retrieve the context's CacheMultiStore by calling the getContextForTx() function with the appropriate mode (either runTxModeCheck or runTxModeDeliver). This CacheMultiStore is a cached version of the main store instantiated during BeginBlock for DeliverTx and during the Commit of the previous block for CheckTx. After that, two defer func() are called for gas management. They are executed when runTx returns and make sure gas is actually consumed, and will throw errors, if any.

After that, RunTx() calls ValidateBasic() on each messagein the Tx, which runs preliminary stateless validity checks. If any message fails to pass ValidateBasic(), RunTx() returns with an error.

Then, the anteHandler of the application is run (if it exists). In preparation of this step, both the checkState/deliverState's context and context's CacheMultiStore are cached-wrapped using the cacheTxContext() function.

Copy package baseapp import ( "encoding/json" "errors" "fmt" "io/ioutil" "os" "reflect" "runtime/debug" "strings" "github.com/gogo/protobuf/proto" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" dbm "github.com/tendermint/tm-db" "github.com/cosmos/cosmos-sdk/store" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" ) const ( runTxModeCheck runTxMode = iota // Check a transaction runTxModeReCheck // Recheck a (pending) transaction after a commit runTxModeSimulate // Simulate a transaction runTxModeDeliver // Deliver a transaction // MainStoreKey is the string representation of the main store MainStoreKey = "main" ) var ( _ abci.Application = (*BaseApp)(nil) // mainConsensusParamsKey defines a key to store the consensus params in the // main store. mainConsensusParamsKey = []byte("consensus_params") ) type ( // Enum mode for app.runTx runTxMode uint8 // StoreLoader defines a customizable function to control how we load the CommitMultiStore // from disk. This is useful for state migration, when loading a datastore written with // an older version of the software. In particular, if a module changed the substore key name // (or removed a substore) between two versions of the software. StoreLoader func(ms sdk.CommitMultiStore) error ) // BaseApp reflects the ABCI application implementation. type BaseApp struct { // nolint: maligned // initialized on creation logger log.Logger name string // application name from abci.Info db dbm.DB // common DB backend cms sdk.CommitMultiStore // Main (uncached) state storeLoader StoreLoader // function to handle store loading, may be overridden with SetStoreLoader() router sdk.Router // handle any kind of message queryRouter sdk.QueryRouter // router for redirecting query calls txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx // set upon LoadVersion or LoadLatestVersion. baseKey *sdk.KVStoreKey // Main KVStore in cms anteHandler sdk.AnteHandler // ante handler for fee and auth initChainer sdk.InitChainer // initialize state with validators and state blob beginBlocker sdk.BeginBlocker // logic to run before any txs endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes addrPeerFilter sdk.PeerFilter // filter peers by address and port idPeerFilter sdk.PeerFilter // filter peers by node ID fauxMerkleMode bool // if true, IAVL MountStores uses MountStoresDB for simulation speed. // volatile states: // // checkState is set on InitChain and reset on Commit // deliverState is set on InitChain and BeginBlock and set to nil on Commit checkState *state // for CheckTx deliverState *state // for DeliverTx // an inter-block write-through cache provided to the context during deliverState interBlockCache sdk.MultiStorePersistentCache // absent validators from begin block voteInfos []abci.VoteInfo // consensus params // TODO: Move this in the future to baseapp param store on main store. consensusParams *abci.ConsensusParams // The minimum gas prices a validator is willing to accept for processing a // transaction. This is mainly used for DoS and spam prevention. minGasPrices sdk.DecCoins // flag for sealing options and parameters to a BaseApp sealed bool // block height at which to halt the chain and gracefully shutdown haltHeight uint64 // minimum block time (in Unix seconds) at which to halt the chain and gracefully shutdown haltTime uint64 // application's version string appVersion string } // NewBaseApp returns a reference to an initialized BaseApp. It accepts a // variadic number of option functions, which act on the BaseApp to set // configuration choices. // // NOTE: The db is used to store the version number for now. func NewBaseApp( name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp), ) *BaseApp { app := &BaseApp{ logger: logger, name: name, db: db, cms: store.NewCommitMultiStore(db), storeLoader: DefaultStoreLoader, router: NewRouter(), queryRouter: NewQueryRouter(), txDecoder: txDecoder, fauxMerkleMode: false, } for _, option := range options { option(app) } if app.interBlockCache != nil { app.cms.SetInterBlockCache(app.interBlockCache) } return app } // Name returns the name of the BaseApp. func (app *BaseApp) Name() string { return app.name } // AppVersion returns the application's version string. func (app *BaseApp) AppVersion() string { return app.appVersion } // Logger returns the logger of the BaseApp. func (app *BaseApp) Logger() log.Logger { return app.logger } // MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp // multistore. func (app *BaseApp) MountStores(keys ...sdk.StoreKey) { for _, key := range keys { switch key.(type) { case *sdk.KVStoreKey: if !app.fauxMerkleMode { app.MountStore(key, sdk.StoreTypeIAVL) } else { // StoreTypeDB doesn't do anything upon commit, and it doesn't // retain history, but it's useful for faster simulation. app.MountStore(key, sdk.StoreTypeDB) } case *sdk.TransientStoreKey: app.MountStore(key, sdk.StoreTypeTransient) default: panic("Unrecognized store key type " + reflect.TypeOf(key).Name()) } } } // MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp // multistore. func (app *BaseApp) MountKVStores(keys map[string]*sdk.KVStoreKey) { for _, key := range keys { if !app.fauxMerkleMode { app.MountStore(key, sdk.StoreTypeIAVL) } else { // StoreTypeDB doesn't do anything upon commit, and it doesn't // retain history, but it's useful for faster simulation. app.MountStore(key, sdk.StoreTypeDB) } } } // MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp // multistore. func (app *BaseApp) MountTransientStores(keys map[string]*sdk.TransientStoreKey) { for _, key := range keys { app.MountStore(key, sdk.StoreTypeTransient) } } // MountStoreWithDB mounts a store to the provided key in the BaseApp // multistore, using a specified DB. func (app *BaseApp) MountStoreWithDB(key sdk.StoreKey, typ sdk.StoreType, db dbm.DB) { app.cms.MountStoreWithDB(key, typ, db) } // MountStore mounts a store to the provided key in the BaseApp multistore, // using the default DB. func (app *BaseApp) MountStore(key sdk.StoreKey, typ sdk.StoreType) { app.cms.MountStoreWithDB(key, typ, nil) } // LoadLatestVersion loads the latest application version. It will panic if // called more than once on a running BaseApp. func (app *BaseApp) LoadLatestVersion(baseKey *sdk.KVStoreKey) error { err := app.storeLoader(app.cms) if err != nil { return err } return app.initFromMainStore(baseKey) } // DefaultStoreLoader will be used by default and loads the latest version func DefaultStoreLoader(ms sdk.CommitMultiStore) error { return ms.LoadLatestVersion() } // StoreLoaderWithUpgrade is used to prepare baseapp with a fixed StoreLoader // pattern. This is useful in test cases, or with custom upgrade loading logic. func StoreLoaderWithUpgrade(upgrades *storetypes.StoreUpgrades) StoreLoader { return func(ms sdk.CommitMultiStore) error { return ms.LoadLatestVersionAndUpgrade(upgrades) } } // UpgradeableStoreLoader can be configured by SetStoreLoader() to check for the // existence of a given upgrade file - json encoded StoreUpgrades data. // // If not file is present, it will peform the default load (no upgrades to store). // // If the file is present, it will parse the file and execute those upgrades // (rename or delete stores), while loading the data. It will also delete the // upgrade file upon successful load, so that the upgrade is only applied once, // and not re-applied on next restart // // This is useful for in place migrations when a store key is renamed between // two versions of the software. (TODO: this code will move to x/upgrades // when PR #4233 is merged, here mainly to help test the design) func UpgradeableStoreLoader(upgradeInfoPath string) StoreLoader { return func(ms sdk.CommitMultiStore) error { _, err := os.Stat(upgradeInfoPath) if os.IsNotExist(err) { return DefaultStoreLoader(ms) } else if err != nil { return err } // there is a migration file, let's execute data, err := ioutil.ReadFile(upgradeInfoPath) if err != nil { return fmt.Errorf("cannot read upgrade file %s: %v", upgradeInfoPath, err) } var upgrades storetypes.StoreUpgrades err = json.Unmarshal(data, &upgrades) if err != nil { return fmt.Errorf("cannot parse upgrade file: %v", err) } err = ms.LoadLatestVersionAndUpgrade(&upgrades) if err != nil { return fmt.Errorf("load and upgrade database: %v", err) } // if we have a successful load, we delete the file err = os.Remove(upgradeInfoPath) if err != nil { return fmt.Errorf("deleting upgrade file %s: %v", upgradeInfoPath, err) } return nil } } // LoadVersion loads the BaseApp application version. It will panic if called // more than once on a running baseapp. func (app *BaseApp) LoadVersion(version int64, baseKey *sdk.KVStoreKey) error { err := app.cms.LoadVersion(version) if err != nil { return err } return app.initFromMainStore(baseKey) } // LastCommitID returns the last CommitID of the multistore. func (app *BaseApp) LastCommitID() sdk.CommitID { return app.cms.LastCommitID() } // LastBlockHeight returns the last committed block height. func (app *BaseApp) LastBlockHeight() int64 { return app.cms.LastCommitID().Version } // initializes the remaining logic from app.cms func (app *BaseApp) initFromMainStore(baseKey *sdk.KVStoreKey) error { mainStore := app.cms.GetKVStore(baseKey) if mainStore == nil { return errors.New("baseapp expects MultiStore with 'main' KVStore") } // memoize baseKey if app.baseKey != nil { panic("app.baseKey expected to be nil; duplicate init?") } app.baseKey = baseKey // Load the consensus params from the main store. If the consensus params are // nil, it will be saved later during InitChain. // // TODO: assert that InitChain hasn't yet been called. consensusParamsBz := mainStore.Get(mainConsensusParamsKey) if consensusParamsBz != nil { var consensusParams = &abci.ConsensusParams{} err := proto.Unmarshal(consensusParamsBz, consensusParams) if err != nil { panic(err) } app.setConsensusParams(consensusParams) } // needed for the export command which inits from store but never calls initchain app.setCheckState(abci.Header{}) app.Seal() return nil } func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) { app.minGasPrices = gasPrices } func (app *BaseApp) setHaltHeight(haltHeight uint64) { app.haltHeight = haltHeight } func (app *BaseApp) setHaltTime(haltTime uint64) { app.haltTime = haltTime } func (app *BaseApp) setInterBlockCache(cache sdk.MultiStorePersistentCache) { app.interBlockCache = cache } // Router returns the router of the BaseApp. func (app *BaseApp) Router() sdk.Router { if app.sealed { // We cannot return a Router when the app is sealed because we can't have // any routes modified which would cause unexpected routing behavior. panic("Router() on sealed BaseApp") } return app.router } // QueryRouter returns the QueryRouter of a BaseApp. func (app *BaseApp) QueryRouter() sdk.QueryRouter { return app.queryRouter } // Seal seals a BaseApp. It prohibits any further modifications to a BaseApp. func (app *BaseApp) Seal() { app.sealed = true } // IsSealed returns true if the BaseApp is sealed and false otherwise. func (app *BaseApp) IsSealed() bool { return app.sealed } // setCheckState sets the BaseApp's checkState with a cache-wrapped multi-store // (i.e. a CacheMultiStore) and a new Context with the cache-wrapped multi-store, // provided header, and minimum gas prices set. It is set on InitChain and reset // on Commit. func (app *BaseApp) setCheckState(header abci.Header) { ms := app.cms.CacheMultiStore() app.checkState = &state{ ms: ms, ctx: sdk.NewContext(ms, header, true, app.logger).WithMinGasPrices(app.minGasPrices), } } // setDeliverState sets the BaseApp's deliverState with a cache-wrapped multi-store // (i.e. a CacheMultiStore) and a new Context with the cache-wrapped multi-store, // and provided header. It is set on InitChain and BeginBlock and set to nil on // Commit. func (app *BaseApp) setDeliverState(header abci.Header) { ms := app.cms.CacheMultiStore() app.deliverState = &state{ ms: ms, ctx: sdk.NewContext(ms, header, false, app.logger), } } // setConsensusParams memoizes the consensus params. func (app *BaseApp) setConsensusParams(consensusParams *abci.ConsensusParams) { app.consensusParams = consensusParams } // setConsensusParams stores the consensus params to the main store. func (app *BaseApp) storeConsensusParams(consensusParams *abci.ConsensusParams) { consensusParamsBz, err := proto.Marshal(consensusParams) if err != nil { panic(err) } mainStore := app.cms.GetKVStore(app.baseKey) mainStore.Set(mainConsensusParamsKey, consensusParamsBz) } // getMaximumBlockGas gets the maximum gas from the consensus params. It panics // if maximum block gas is less than negative one and returns zero if negative // one. func (app *BaseApp) getMaximumBlockGas() uint64 { if app.consensusParams == nil || app.consensusParams.Block == nil { return 0 } maxGas := app.consensusParams.Block.MaxGas switch { case maxGas < -1: panic(fmt.Sprintf("invalid maximum block gas: %d", maxGas)) case maxGas == -1: return 0 default: return uint64(maxGas) } } func (app *BaseApp) validateHeight(req abci.RequestBeginBlock) error { if req.Header.Height < 1 { return fmt.Errorf("invalid height: %d", req.Header.Height) } prevHeight := app.LastBlockHeight() if req.Header.Height != prevHeight+1 { return fmt.Errorf("invalid height: %d; expected: %d", req.Header.Height, prevHeight+1) } return nil } // validateBasicTxMsgs executes basic validator calls for messages. func validateBasicTxMsgs(msgs []sdk.Msg) sdk.Error { if len(msgs) == 0 { return sdk.ErrUnknownRequest("Tx.GetMsgs() must return at least one message in list") } for _, msg := range msgs { // Validate the Msg. err := msg.ValidateBasic() if err != nil { return err } } return nil } // Returns the applications's deliverState if app is in runTxModeDeliver, // otherwise it returns the application's checkstate. func (app *BaseApp) getState(mode runTxMode) *state { if mode == runTxModeDeliver { return app.deliverState } return app.checkState } // retrieve the context for the tx w/ txBytes and other memoized values. func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) sdk.Context { ctx := app.getState(mode).ctx. WithTxBytes(txBytes). WithVoteInfos(app.voteInfos). WithConsensusParams(app.consensusParams) if mode == runTxModeReCheck { ctx = ctx.WithIsReCheckTx(true) } if mode == runTxModeSimulate { ctx, _ = ctx.CacheContext() } return ctx } // cacheTxContext returns a new context based off of the provided context with // a cache wrapped multi-store. func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context, sdk.CacheMultiStore) { ms := ctx.MultiStore() // TODO: https://github.com/cosmos/cosmos-sdk/issues/2824 msCache := ms.CacheMultiStore() if msCache.TracingEnabled() { msCache = msCache.SetTracingContext( sdk.TraceContext( map[string]interface{}{ "txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)), }, ), ).(sdk.CacheMultiStore) } return ctx.WithMultiStore(msCache), msCache } // runTx processes a transaction. The transactions is processed via an // anteHandler. The provided txBytes may be nil in some cases, eg. in tests. For // further details on transaction execution, reference the BaseApp SDK // documentation. func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk.Result) { // NOTE: GasWanted should be returned by the AnteHandler. GasUsed is // determined by the GasMeter. We need access to the context to get the gas // meter so we initialize upfront. var gasWanted uint64 ctx := app.getContextForTx(mode, txBytes) ms := ctx.MultiStore() // only run the tx if there is block gas remaining if mode == runTxModeDeliver && ctx.BlockGasMeter().IsOutOfGas() { return sdk.ErrOutOfGas("no block gas left to run tx").Result() } var startingGas uint64 if mode == runTxModeDeliver { startingGas = ctx.BlockGasMeter().GasConsumed() } defer func() { if r := recover(); r != nil { switch rType := r.(type) { case sdk.ErrorOutOfGas: log := fmt.Sprintf( "out of gas in location: %v; gasWanted: %d, gasUsed: %d", rType.Descriptor, gasWanted, ctx.GasMeter().GasConsumed(), ) result = sdk.ErrOutOfGas(log).Result() default: log := fmt.Sprintf("recovered: %v\nstack:\n%v", r, string(debug.Stack())) result = sdk.ErrInternal(log).Result() } } result.GasWanted = gasWanted result.GasUsed = ctx.GasMeter().GasConsumed() }() // If BlockGasMeter() panics it will be caught by the above recover and will // return an error - in any case BlockGasMeter will consume gas past the limit. // // NOTE: This must exist in a separate defer function for the above recovery // to recover from this one. defer func() { if mode == runTxModeDeliver { ctx.BlockGasMeter().ConsumeGas( ctx.GasMeter().GasConsumedToLimit(), "block gas meter", ) if ctx.BlockGasMeter().GasConsumed() < startingGas { panic(sdk.ErrorGasOverflow{Descriptor: "tx gas summation"}) } } }() var msgs = tx.GetMsgs() if err := validateBasicTxMsgs(msgs); err != nil { return err.Result() } if app.anteHandler != nil { var anteCtx sdk.Context var msCache sdk.CacheMultiStore // Cache wrap context before anteHandler call in case it aborts. // This is required for both CheckTx and DeliverTx. // Ref: https://github.com/cosmos/cosmos-sdk/issues/2772 // // NOTE: Alternatively, we could require that anteHandler ensures that // writes do not happen if aborted/failed. This may have some // performance benefits, but it'll be more difficult to get right. anteCtx, msCache = app.cacheTxContext(ctx, txBytes) newCtx, err := app.anteHandler(anteCtx, tx, mode == runTxModeSimulate) if !newCtx.IsZero() { // At this point, newCtx.MultiStore() is cache-wrapped, or something else // replaced by the ante handler. We want the original multistore, not one // which was cache-wrapped for the ante handler. // // Also, in the case of the tx aborting, we need to track gas consumed via // the instantiated gas meter in the ante handler, so we update the context // prior to returning. ctx = newCtx.WithMultiStore(ms) } // GasMeter expected to be set in AnteHandler gasWanted = ctx.GasMeter().Limit() if err != nil { res := sdk.ResultFromError(err) res.GasWanted = gasWanted res.GasUsed = ctx.GasMeter().GasConsumed() return res } msCache.Write() } // Create a new Context based off of the existing Context with a cache-wrapped // MultiStore in case message processing fails. At this point, the MultiStore // is doubly cached-wrapped. runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes) result = app.runMsgs(runMsgCtx, msgs, mode) result.GasWanted = gasWanted // Safety check: don't write the cache state unless we're in DeliverTx. if mode != runTxModeDeliver { return result } // only update state if all messages pass if result.IsOK() { msCache.Write() } return result } // runMsgs iterates through all the messages and executes them. func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (result sdk.Result) { msgLogs := make(sdk.ABCIMessageLogs, 0, len(msgs)) data := make([]byte, 0, len(msgs)) var ( code sdk.CodeType codespace sdk.CodespaceType ) events := sdk.EmptyEvents() // NOTE: GasWanted is determined by ante handler and GasUsed by the GasMeter. for i, msg := range msgs { // match message route msgRoute := msg.Route() handler := app.router.Route(msgRoute) if handler == nil { return sdk.ErrUnknownRequest("unrecognized message type: " + msgRoute).Result() } var msgResult sdk.Result // skip actual execution for CheckTx and ReCheckTx mode if mode != runTxModeCheck && mode != runTxModeReCheck { msgResult = handler(ctx, msg) } // Each message result's Data must be length prefixed in order to separate // each result. data = append(data, msgResult.Data...) msgEvents := msgResult.Events // append events from the message's execution and a message action event msgEvents = msgEvents.AppendEvent( sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type())), ) events = events.AppendEvents(msgEvents) // stop execution and return on first failed message if !msgResult.IsOK() { msgLogs = append(msgLogs, sdk.NewABCIMessageLog(uint16(i), false, msgResult.Log, msgEvents)) code = msgResult.Code codespace = msgResult.Codespace break } msgLogs = append(msgLogs, sdk.NewABCIMessageLog(uint16(i), true, msgResult.Log, msgEvents)) } result = sdk.Result{ Code: code, Codespace: codespace, Data: data, Log: strings.TrimSpace(msgLogs.String()), GasUsed: ctx.GasMeter().GasConsumed(), Events: events, } return result }

This allows RunTx not to commit the changes made to the state during the execution of anteHandler if it ends up failing. It also prevents the module implementing the anteHandler from writing to state, which is an important part of the object-capabilities of the Cosmos SDK.

Finally, the RunMsgs() function is called to process the messagess in the Tx. In preparation of this step, just like with the anteHandler, both the checkState/deliverState's context and context's CacheMultiStore are cached-wrapped using the cacheTxContext() function.

# AnteHandler

The AnteHandler is a special handler that implements the anteHandler interface and is used to authenticate the transaction before the transaction's internal messages are processed.

Copy package types // Handler defines the core of the state transition function of an application. type Handler func(ctx Context, msg Msg) Result // AnteHandler authenticates transactions, before their internal messages are handled. // If newCtx.IsZero(), ctx is used instead. type AnteHandler func(ctx Context, tx Tx, simulate bool) (newCtx Context, err error) // AnteDecorator wraps the next AnteHandler to perform custom pre- and post-processing. type AnteDecorator interface { AnteHandle(ctx Context, tx Tx, simulate bool, next AnteHandler) (newCtx Context, err error) } // ChainDecorator chains AnteDecorators together with each AnteDecorator // wrapping over the decorators further along chain and returns a single AnteHandler. // // NOTE: The first element is outermost decorator, while the last element is innermost // decorator. Decorator ordering is critical since some decorators will expect // certain checks and updates to be performed (e.g. the Context) before the decorator // is run. These expectations should be documented clearly in a CONTRACT docline // in the decorator's godoc. // // NOTE: Any application that uses GasMeter to limit transaction processing cost // MUST set GasMeter with the FIRST AnteDecorator. Failing to do so will cause // transactions to be processed with an infinite gasmeter and open a DOS attack vector. // Use `ante.SetUpContextDecorator` or a custom Decorator with similar functionality. func ChainAnteDecorators(chain ...AnteDecorator) AnteHandler { if (chain[len(chain)-1] != Terminator{}) { chain = append(chain, Terminator{}) } if len(chain) == 1 { return func(ctx Context, tx Tx, simulate bool) (Context, error) { return chain[0].AnteHandle(ctx, tx, simulate, nil) } } return func(ctx Context, tx Tx, simulate bool) (Context, error) { return chain[0].AnteHandle(ctx, tx, simulate, ChainAnteDecorators(chain[1:]...)) } } // Terminator AnteDecorator will get added to the chain to simplify decorator code // Don't need to check if next == nil further up the chain // ______ // <((((((\\\ // / . }\ // ;--..--._|} // (\ '--/\--' ) // \\ | '-' :'| // \\ . -==- .-| // \\ \.__.' \--._ // [\\ __.--| // _/'--. // \ \\ .'-._ ('-----'/ __/ \ // \ \\ / __>| | '--. | // \ \\ | \ | / / / // \ '\ / \ | | _/ / // \ \ \ | | / / // snd \ \ \ / type Terminator struct{} // Simply return provided Context and nil error func (t Terminator) AnteHandle(ctx Context, _ Tx, _ bool, _ AnteHandler) (Context, error) { return ctx, nil }

The AnteHandler is theoretically optional, but still a very important component of public blockchain networks. It serves 3 primary purposes:

  • Be a primary line of defense against spam and second line of defense (the first one being the mempool) against transaction replay with fees deduction and sequence checking.
  • Perform preliminary stateful validity checks like ensuring signatures are valid or that the sender has enough funds to pay for fees.
  • Play a role in the incentivisation of stakeholders via the collection of transaction fees.

baseapp holds an anteHandler as paraemter, which is initialized in the application's constructor. The most widely used anteHandler today is that of the auth module.

Click here for more on the anteHandler.

# RunMsgs

RunMsgs is called from RunTx with runTxModeCheck as parameter to check the existence of a route for each message the transaction, and with runTxModeDeliver to actually process the messages.

First, it retreives the message's route using the Msg.Route() method. Then, using the application's router and the route, it checks for the existence of a handler. At this point, if mode == runTxModeCheck, RunMsgs returns. If instead mode == runTxModeDeliver, the handler function for the message is executed, before RunMsgs returns.

# Other ABCI Messages

# InitChain

The InitChain ABCI message is sent from the underlying Tendermint engine when the chain is first started. It is mainly used to initialize parameters and state like:

Finally, the InitChain(req abci.RequestInitChain) method of baseapp calls the initChainer() of the application in order to initialize the main state of the application from the genesis file and, if defined, call the InitGenesis function of each of the application's modules.

# BeginBlock

The BeginBlock ABCI message is sent from the underlying Tendermint engine when a block proposal created by the correct proposer is received, before DeliverTx is run for each transaction in the block. It allows developers to have logic be executed at the beginning of each block. In the Cosmos SDK, the BeginBlock(req abci.RequestBeginBlock) method does the following:

  • Initialize deliverState with the latest header using the req abci.RequestBeginBlock passed as parameter via the setDeliverState function. Copy // setDeliverState sets the BaseApp's deliverState with a cache-wrapped multi-store // (i.e. a CacheMultiStore) and a new Context with the cache-wrapped multi-store, // and provided header. It is set on InitChain and BeginBlock and set to nil on // Commit. func (app *BaseApp) setDeliverState(header abci.Header) { ms := app.cms.CacheMultiStore() app.deliverState = &state{ ms: ms, ctx: sdk.NewContext(ms, header, false, app.logger), } } This function also resets the main gas meter.
  • Initialize the block gas meter with the maxGas limit. The gas consumed within the block cannot go above maxGas. This parameter is defined in the application's consensus parameters.
  • Run the application's beginBlocker(), which mainly runs the BeginBlocker() method of each of the application's modules.
  • Set the VoteInfos of the application, i.e. the list of validators whose precommit for the previous block was included by the proposer of the current block. This information is carried into the Context so that it can be used during DeliverTx and EndBlock.

# EndBlock

The EndBlock ABCI message is sent from the underlying Tendermint engine after DeliverTx as been run for each transaction in the block. It allows developers to have logic be executed at the end of each block. In the Cosmos SDK, the bulk EndBlock(req abci.RequestEndBlock) method is to run the application's EndBlocker(), which mainly runs the EndBlocker() method of each of the application's modules.

# Commit

The Commit ABCI message is sent from the underlying Tendermint engine after the full-node has received precommits from 2/3+ of validators (weighted by voting power). On the baseapp end, the Commit(res abci.ResponseCommit) function is implemented to commit all the valid state transitions that occured during BeginBlock, DeliverTx and EndBlock and to reset state for the next block.

To commit state-transitions, the Commit function calls the Write() function on deliverState.ms, where deliverState.ms is a cached multistore of the main store app.cms. Then, the Commit function sets checkState to the latest header (obtbained from deliverState.ctx.BlockHeader) and deliverState to nil.

Finally, Commit returns the hash of the commitment of app.cms back to the underlying consensus engine. This hash is used as a reference in the header of the next block.

# Info

The Info ABCI message is a simple query from the underlying consensus engine, notably used to sync the latter with the application during a handshake that happens on startup. When called, the Info(res abci.ResponseInfo) function from baseapp will return the application's name, version and the hash of the last commit of app.cms.

# Query

The Query ABCI message is used to serve queries received from the underlying consensus engine, including queries received via RPC like Tendermint RPC. It is the main entrypoint to build interfaces with the application. The application must respect a few rules when implementing the Query method, which are outlined here.

Each query comes with a path, which contains multiple strings. By convention, the first element of the path (path[0]) contains the category of query (app, p2p, store or custom). The baseapp implementation of the Query(req abci.RequestQuery) method is a simple dispatcher serving these 4 main categories of queries:

  • Application-related queries like querying the application's version, which are served via the handleQueryApp method.
  • Direct queries to the multistore, which are served by the handlerQueryStore method. These direct queryeis are different from custom queries which go through app.queryRouter, and are mainly used by third-party service provider like block explorers.
  • P2P queries, which are served via the handleQueryP2P method. These queries return either app.addrPeerFilter or app.ipPeerFilter that contain the list of peers filtered by address or IP respectively. These lists are first initialized via options in baseapp's constructor.
  • Custom queries, which encompass most queries, are served via the handleQueryCustom method. The handleQueryCustom cache-wraps the multistore before using the queryRoute obtained from app.queryRouter to map the query to the appropriate module's querier.

# Next

Learn more about transactions