Synopsis
This document describesBaseApp
, the abstraction that implements the core functionalities of a Cosmos SDK application.
Pre-requisite Readings
Introduction
BaseApp
is a base type that implements the core of a Cosmos SDK application, namely:
- The Application Blockchain Interface, for the state-machine to communicate with the underlying consensus engine (e.g. CometBFT).
- Service Routers, 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.
BaseApp
is to provide the fundamental layer of a Cosmos 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
Ask AI
type App struct {
/ reference to a BaseApp
*baseapp.BaseApp
/ list of application store keys
/ list of application keepers
/ module manager
}
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 service routers and state
management logic.
Type Definition
TheBaseApp
type holds many important parameters for any Cosmos SDK based application.
Copy
Ask AI
package baseapp
import (
"context"
"fmt"
"maps"
"math"
"slices"
"strconv"
"sync"
"github.com/cockroachdb/errors"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/crypto/tmhash"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/cosmos/gogoproto/proto"
protov2 "google.golang.org/protobuf/proto"
"cosmossdk.io/core/header"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/log"
"cosmossdk.io/store"
storemetrics "cosmossdk.io/store/metrics"
"cosmossdk.io/store/snapshots"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/baseapp/oe"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/mempool"
"github.com/cosmos/cosmos-sdk/types/msgservice"
)
type (
execMode 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 storetypes.CommitMultiStore)
error
)
const (
execModeCheck execMode = iota / Check a transaction
execModeReCheck / Recheck a (pending)
transaction after a commit
execModeSimulate / Simulate a transaction
execModePrepareProposal / Prepare a block proposal
execModeProcessProposal / Process a block proposal
execModeVoteExtension / Extend or verify a pre-commit vote
execModeVerifyVoteExtension / Verify a vote extension
execModeFinalize / Finalize a block proposal
)
var _ servertypes.ABCI = (*BaseApp)(nil)
/ BaseApp reflects the ABCI application implementation.
type BaseApp struct {
/ initialized on creation
mu sync.Mutex / mu protects the fields below.
logger log.Logger
name string / application name from abci.BlockInfo
db dbm.DB / common DB backend
cms storetypes.CommitMultiStore / Main (uncached)
state
qms storetypes.MultiStore / Optional alternative multistore for querying only.
storeLoader StoreLoader / function to handle store loading, may be overridden with SetStoreLoader()
grpcQueryRouter *GRPCQueryRouter / router for redirecting gRPC query calls
msgServiceRouter *MsgServiceRouter / router for redirecting Msg service messages
interfaceRegistry codectypes.InterfaceRegistry
txDecoder sdk.TxDecoder / unmarshal []byte into sdk.Tx
txEncoder sdk.TxEncoder / marshal sdk.Tx into []byte
mempool mempool.Mempool / application side mempool
anteHandler sdk.AnteHandler / ante handler for fee and auth
postHandler sdk.PostHandler / post handler, optional
checkTxHandler sdk.CheckTxHandler / ABCI CheckTx handler
initChainer sdk.InitChainer / ABCI InitChain handler
preBlocker sdk.PreBlocker / logic to run before BeginBlocker
beginBlocker sdk.BeginBlocker / (legacy ABCI)
BeginBlock handler
endBlocker sdk.EndBlocker / (legacy ABCI)
EndBlock handler
processProposal sdk.ProcessProposalHandler / ABCI ProcessProposal handler
prepareProposal sdk.PrepareProposalHandler / ABCI PrepareProposal
extendVote sdk.ExtendVoteHandler / ABCI ExtendVote handler
verifyVoteExt sdk.VerifyVoteExtensionHandler / ABCI VerifyVoteExtension handler
prepareCheckStater sdk.PrepareCheckStater / logic to run during commit using the checkState
precommiter sdk.Precommiter / logic to run during commit using the deliverState
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.
sigverifyTx bool / in the simulation test, since the account does not have a private key, we have to ignore the tx sigverify.
/ manages snapshots, i.e. dumps of app state at certain intervals
snapshotManager *snapshots.Manager
/ volatile states:
/
/ - checkState is set on InitChain and reset on Commit
/ - finalizeBlockState is set on InitChain and FinalizeBlock and set to nil
/ on Commit.
/
/ - checkState: Used for CheckTx, which is set based on the previous block's
/ state. This state is never committed.
/
/ - prepareProposalState: Used for PrepareProposal, which is set based on the
/ previous block's state. This state is never committed. In case of multiple
/ consensus rounds, the state is always reset to the previous block's state.
/
/ - processProposalState: Used for ProcessProposal, which is set based on the
/ the previous block's state. This state is never committed. In case of
/ multiple rounds, the state is always reset to the previous block's state.
/
/ - finalizeBlockState: Used for FinalizeBlock, which is set based on the
/ previous block's state. This state is committed.
checkState *state
prepareProposalState *state
processProposalState *state
finalizeBlockState *state
/ An inter-block write-through cache provided to the context during the ABCI
/ FinalizeBlock call.
interBlockCache storetypes.MultiStorePersistentCache
/ paramStore is used to query for ABCI consensus parameters from an
/ application parameter store.
paramStore ParamStore
/ queryGasLimit defines the maximum gas for queries; unbounded if 0.
queryGasLimit uint64
/ 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
/ initialHeight is the initial height at which we start the BaseApp
initialHeight int64
/ 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
/ minRetainBlocks defines the minimum block height offset from the current
/ block being committed, such that all blocks past this offset are pruned
/ from CometBFT. It is used as part of the process of determining the
/ ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates
/ that no blocks should be pruned.
/
/ Note: CometBFT block pruning is dependant on this parameter in conjunction
/ with the unbonding (safety threshold)
period, state pruning and state sync
/ snapshot parameters to determine the correct minimum value of
/ ResponseCommit.RetainHeight.
minRetainBlocks uint64
/ application's version string
version string
/ application's protocol version that increments on every upgrade
/ if BaseApp is passed to the upgrade keeper's NewKeeper method.
appVersion uint64
/ recovery handler for app.runTx method
runTxRecoveryMiddleware recoveryMiddleware
/ trace set will return full stack traces for errors in ABCI Log field
trace bool
/ indexEvents defines the set of events in the form {
eventType
}.{
attributeKey
},
/ which informs CometBFT what to index. If empty, all events will be indexed.
indexEvents map[string]struct{
}
/ streamingManager for managing instances and configuration of ABCIListener services
streamingManager storetypes.StreamingManager
chainID string
cdc codec.Codec
/ optimisticExec contains the context required for Optimistic Execution,
/ including the goroutine handling.This is experimental and must be enabled
/ by developers.
optimisticExec *oe.OptimisticExecution
/ disableBlockGasMeter will disable the block gas meter if true, block gas meter is tricky to support
/ when executing transactions in parallel.
/ when disabled, the block gas meter in context is a noop one.
/
/ SAFETY: it's safe to do if validators validate the total gas wanted in the `ProcessProposal`, which is the case in the default handler.
disableBlockGasMeter bool
}
/ 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.
func NewBaseApp(
name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp),
) *BaseApp {
app := &BaseApp{
logger: logger.With(log.ModuleKey, "baseapp"),
name: name,
db: db,
cms: store.NewCommitMultiStore(db, logger, storemetrics.NewNoOpMetrics()), / by default we use a no-op metric gather in store
storeLoader: DefaultStoreLoader,
grpcQueryRouter: NewGRPCQueryRouter(),
msgServiceRouter: NewMsgServiceRouter(),
txDecoder: txDecoder,
fauxMerkleMode: false,
sigverifyTx: true,
queryGasLimit: math.MaxUint64,
}
for _, option := range options {
option(app)
}
if app.mempool == nil {
app.SetMempool(mempool.NoOpMempool{
})
}
abciProposalHandler := NewDefaultProposalHandler(app.mempool, app)
if app.prepareProposal == nil {
app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())
}
if app.processProposal == nil {
app.SetProcessProposal(abciProposalHandler.ProcessProposalHandler())
}
if app.extendVote == nil {
app.SetExtendVoteHandler(NoOpExtendVote())
}
if app.verifyVoteExt == nil {
app.SetVerifyVoteExtensionHandler(NoOpVerifyVoteExtensionHandler())
}
if app.interBlockCache != nil {
app.cms.SetInterBlockCache(app.interBlockCache)
}
app.runTxRecoveryMiddleware = newDefaultRecoveryMiddleware()
/ Initialize with an empty interface registry to avoid nil pointer dereference.
/ Unless SetInterfaceRegistry is called with an interface registry with proper address codecs baseapp will panic.
app.cdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry())
protoFiles, err := proto.MergedRegistry()
if err != nil {
logger.Warn("error creating merged proto registry", "error", err)
}
else {
err = msgservice.ValidateProtoAnnotations(protoFiles)
if err != nil {
/ Once we switch to using protoreflect-based antehandlers, we might
/ want to panic here instead of logging a warning.
logger.Warn("error validating merged proto registry annotations", "error", err)
}
}
return app
}
/ Name returns the name of the BaseApp.
func (app *BaseApp)
Name()
string {
return app.name
}
/ AppVersion returns the application's protocol version.
func (app *BaseApp)
AppVersion()
uint64 {
return app.appVersion
}
/ Version returns the application's version string.
func (app *BaseApp)
Version()
string {
return app.version
}
/ Logger returns the logger of the BaseApp.
func (app *BaseApp)
Logger()
log.Logger {
return app.logger
}
/ Trace returns the boolean value for logging error stack traces.
func (app *BaseApp)
Trace()
bool {
return app.trace
}
/ MsgServiceRouter returns the MsgServiceRouter of a BaseApp.
func (app *BaseApp)
MsgServiceRouter() *MsgServiceRouter {
return app.msgServiceRouter
}
/ GRPCQueryRouter returns the GRPCQueryRouter of a BaseApp.
func (app *BaseApp)
GRPCQueryRouter() *GRPCQueryRouter {
return app.grpcQueryRouter
}
/ MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp
/ multistore.
func (app *BaseApp)
MountStores(keys ...storetypes.StoreKey) {
for _, key := range keys {
switch key.(type) {
case *storetypes.KVStoreKey:
if !app.fauxMerkleMode {
app.MountStore(key, storetypes.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, storetypes.StoreTypeDB)
}
case *storetypes.TransientStoreKey:
app.MountStore(key, storetypes.StoreTypeTransient)
case *storetypes.MemoryStoreKey:
app.MountStore(key, storetypes.StoreTypeMemory)
default:
panic(fmt.Sprintf("Unrecognized store key type :%T", key))
}
}
}
/ MountKVStores mounts all IAVL or DB stores to the provided keys in the
/ BaseApp multistore.
func (app *BaseApp)
MountKVStores(keys map[string]*storetypes.KVStoreKey) {
for _, key := range keys {
if !app.fauxMerkleMode {
app.MountStore(key, storetypes.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, storetypes.StoreTypeDB)
}
}
}
/ MountTransientStores mounts all transient stores to the provided keys in
/ the BaseApp multistore.
func (app *BaseApp)
MountTransientStores(keys map[string]*storetypes.TransientStoreKey) {
for _, key := range keys {
app.MountStore(key, storetypes.StoreTypeTransient)
}
}
/ MountMemoryStores mounts all in-memory KVStores with the BaseApp's internal
/ commit multi-store.
func (app *BaseApp)
MountMemoryStores(keys map[string]*storetypes.MemoryStoreKey) {
skeys := slices.Sorted(maps.Keys(keys))
for _, key := range skeys {
memKey := keys[key]
app.MountStore(memKey, storetypes.StoreTypeMemory)
}
}
/ MountStore mounts a store to the provided key in the BaseApp multistore,
/ using the default DB.
func (app *BaseApp)
MountStore(key storetypes.StoreKey, typ storetypes.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()
error {
err := app.storeLoader(app.cms)
if err != nil {
return fmt.Errorf("failed to load latest version: %w", err)
}
return app.Init()
}
/ DefaultStoreLoader will be used by default and loads the latest version
func DefaultStoreLoader(ms storetypes.CommitMultiStore)
error {
return ms.LoadLatestVersion()
}
/ CommitMultiStore returns the root multi-store.
/ App constructor can use this to access the `cms`.
/ UNSAFE: must not be used during the abci life cycle.
func (app *BaseApp)
CommitMultiStore()
storetypes.CommitMultiStore {
return app.cms
}
/ SnapshotManager returns the snapshot manager.
/ application use this to register extra extension snapshotters.
func (app *BaseApp)
SnapshotManager() *snapshots.Manager {
return app.snapshotManager
}
/ LoadVersion loads the BaseApp application version. It will panic if called
/ more than once on a running baseapp.
func (app *BaseApp)
LoadVersion(version int64)
error {
app.logger.Info("NOTICE: this could take a long time to migrate IAVL store to fastnode if you enable Fast Node.\n")
err := app.cms.LoadVersion(version)
if err != nil {
return fmt.Errorf("failed to load version %d: %w", version, err)
}
return app.Init()
}
/ LastCommitID returns the last CommitID of the multistore.
func (app *BaseApp)
LastCommitID()
storetypes.CommitID {
return app.cms.LastCommitID()
}
/ LastBlockHeight returns the last committed block height.
func (app *BaseApp)
LastBlockHeight()
int64 {
return app.cms.LastCommitID().Version
}
/ ChainID returns the chainID of the app.
func (app *BaseApp)
ChainID()
string {
return app.chainID
}
/ AnteHandler returns the AnteHandler of the app.
func (app *BaseApp)
AnteHandler()
sdk.AnteHandler {
return app.anteHandler
}
/ Mempool returns the Mempool of the app.
func (app *BaseApp)
Mempool()
mempool.Mempool {
return app.mempool
}
/ Init initializes the app. It seals the app, preventing any
/ further modifications. In addition, it validates the app against
/ the earlier provided settings. Returns an error if validation fails.
/ nil otherwise. Panics if the app is already sealed.
func (app *BaseApp)
Init()
error {
if app.sealed {
panic("cannot call initFromMainStore: baseapp already sealed")
}
if app.cms == nil {
return errors.New("commit multi-store must not be nil")
}
emptyHeader := cmtproto.Header{
ChainID: app.chainID
}
/ needed for the export command which inits from store but never calls initchain
app.setState(execModeCheck, emptyHeader)
app.Seal()
return app.cms.GetPruning().Validate()
}
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)
setMinRetainBlocks(minRetainBlocks uint64) {
app.minRetainBlocks = minRetainBlocks
}
func (app *BaseApp)
setInterBlockCache(cache storetypes.MultiStorePersistentCache) {
app.interBlockCache = cache
}
func (app *BaseApp)
setTrace(trace bool) {
app.trace = trace
}
func (app *BaseApp)
setIndexEvents(ie []string) {
app.indexEvents = make(map[string]struct{
})
for _, e := range ie {
app.indexEvents[e] = struct{
}{
}
}
}
/ 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
}
/ setState sets the BaseApp's state for the corresponding mode with a branched
/ multi-store (i.e. a CacheMultiStore)
and a new Context with the same
/ multi-store branch, and provided header.
func (app *BaseApp)
setState(mode execMode, h cmtproto.Header) {
ms := app.cms.CacheMultiStore()
headerInfo := header.Info{
Height: h.Height,
Time: h.Time,
ChainID: h.ChainID,
AppHash: h.AppHash,
}
baseState := &state{
ms: ms,
ctx: sdk.NewContext(ms, h, false, app.logger).
WithStreamingManager(app.streamingManager).
WithHeaderInfo(headerInfo),
}
switch mode {
case execModeCheck:
baseState.SetContext(baseState.Context().WithIsCheckTx(true).WithMinGasPrices(app.minGasPrices))
app.checkState = baseState
case execModePrepareProposal:
app.prepareProposalState = baseState
case execModeProcessProposal:
app.processProposalState = baseState
case execModeFinalize:
app.finalizeBlockState = baseState
default:
panic(fmt.Sprintf("invalid runTxMode for setState: %d", mode))
}
}
/ SetCircuitBreaker sets the circuit breaker for the BaseApp.
/ The circuit breaker is checked on every message execution to verify if a transaction should be executed or not.
func (app *BaseApp)
SetCircuitBreaker(cb CircuitBreaker) {
if app.msgServiceRouter == nil {
panic("cannot set circuit breaker with no msg service router set")
}
app.msgServiceRouter.SetCircuit(cb)
}
/ GetConsensusParams returns the current consensus parameters from the BaseApp's
/ ParamStore. If the BaseApp has no ParamStore defined, nil is returned.
func (app *BaseApp)
GetConsensusParams(ctx sdk.Context)
cmtproto.ConsensusParams {
if app.paramStore == nil {
return cmtproto.ConsensusParams{
}
}
cp, err := app.paramStore.Get(ctx)
if err != nil {
/ This could happen while migrating from v0.45/v0.46 to v0.50, we should
/ allow it to happen so during preblock the upgrade plan can be executed
/ and the consensus params set for the first time in the new format.
app.logger.Error("failed to get consensus params", "err", err)
return cmtproto.ConsensusParams{
}
}
return cp
}
/ StoreConsensusParams sets the consensus parameters to the BaseApp's param
/ store.
/
/ NOTE: We're explicitly not storing the CometBFT app_version in the param store.
/ It's stored instead in the x/upgrade store, with its own bump logic.
func (app *BaseApp)
StoreConsensusParams(ctx sdk.Context, cp cmtproto.ConsensusParams)
error {
if app.paramStore == nil {
return errors.New("cannot store consensus params with no params store set")
}
return app.paramStore.Set(ctx, cp)
}
/ AddRunTxRecoveryHandler adds custom app.runTx method panic handlers.
func (app *BaseApp)
AddRunTxRecoveryHandler(handlers ...RecoveryHandler) {
for _, h := range handlers {
app.runTxRecoveryMiddleware = newRecoveryMiddleware(h, app.runTxRecoveryMiddleware)
}
}
/ 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(ctx sdk.Context)
uint64 {
cp := app.GetConsensusParams(ctx)
if cp.Block == nil {
return 0
}
maxGas := cp.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)
validateFinalizeBlockHeight(req *abci.RequestFinalizeBlock)
error {
if req.Height < 1 {
return fmt.Errorf("invalid height: %d", req.Height)
}
lastBlockHeight := app.LastBlockHeight()
/ expectedHeight holds the expected height to validate
var expectedHeight int64
if lastBlockHeight == 0 && app.initialHeight > 1 {
/ In this case, we're validating the first block of the chain, i.e no
/ previous commit. The height we're expecting is the initial height.
expectedHeight = app.initialHeight
}
else {
/ This case can mean two things:
/
/ - Either there was already a previous commit in the store, in which
/ case we increment the version from there.
/ - Or there was no previous commit, in which case we start at version 1.
expectedHeight = lastBlockHeight + 1
}
if req.Height != expectedHeight {
return fmt.Errorf("invalid height: %d; expected: %d", req.Height, expectedHeight)
}
return nil
}
/ validateBasicTxMsgs executes basic validator calls for messages.
func validateBasicTxMsgs(msgs []sdk.Msg)
error {
if len(msgs) == 0 {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "must contain at least one message")
}
for _, msg := range msgs {
m, ok := msg.(sdk.HasValidateBasic)
if !ok {
continue
}
if err := m.ValidateBasic(); err != nil {
return err
}
}
return nil
}
func (app *BaseApp)
getState(mode execMode) *state {
switch mode {
case execModeFinalize:
return app.finalizeBlockState
case execModePrepareProposal:
return app.prepareProposalState
case execModeProcessProposal:
return app.processProposalState
default:
return app.checkState
}
}
func (app *BaseApp)
getBlockGasMeter(ctx sdk.Context)
storetypes.GasMeter {
if app.disableBlockGasMeter {
return noopGasMeter{
}
}
if maxGas := app.GetMaximumBlockGas(ctx); maxGas > 0 {
return storetypes.NewGasMeter(maxGas)
}
return storetypes.NewInfiniteGasMeter()
}
/ retrieve the context for the tx w/ txBytes and other memoized values.
func (app *BaseApp)
getContextForTx(mode execMode, txBytes []byte)
sdk.Context {
app.mu.Lock()
defer app.mu.Unlock()
modeState := app.getState(mode)
if modeState == nil {
panic(fmt.Sprintf("state is nil for mode %v", mode))
}
ctx := modeState.Context().
WithTxBytes(txBytes).
WithGasMeter(storetypes.NewInfiniteGasMeter())
/ WithVoteInfos(app.voteInfos) / TODO: identify if this is needed
ctx = ctx.WithIsSigverifyTx(app.sigverifyTx)
ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx))
if mode == execModeReCheck {
ctx = ctx.WithIsReCheckTx(true)
}
if mode == execModeSimulate {
ctx, _ = ctx.CacheContext()
ctx = ctx.WithExecMode(sdk.ExecMode(execModeSimulate))
}
return ctx
}
/ cacheTxContext returns a new context based off of the provided context with
/ a branched multi-store.
func (app *BaseApp)
cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context, storetypes.CacheMultiStore) {
ms := ctx.MultiStore()
msCache := ms.CacheMultiStore()
if msCache.TracingEnabled() {
msCache = msCache.SetTracingContext(
storetypes.TraceContext(
map[string]any{
"txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)),
},
),
).(storetypes.CacheMultiStore)
}
return ctx.WithMultiStore(msCache), msCache
}
func (app *BaseApp)
preBlock(req *abci.RequestFinalizeBlock) ([]abci.Event, error) {
var events []abci.Event
if app.preBlocker != nil {
ctx := app.finalizeBlockState.Context().WithEventManager(sdk.NewEventManager())
rsp, err := app.preBlocker(ctx, req)
if err != nil {
return nil, err
}
/ rsp.ConsensusParamsChanged is true from preBlocker means ConsensusParams in store get changed
/ write the consensus parameters in store to context
if rsp.ConsensusParamsChanged {
ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx))
/ GasMeter must be set after we get a context with updated consensus params.
gasMeter := app.getBlockGasMeter(ctx)
ctx = ctx.WithBlockGasMeter(gasMeter)
app.finalizeBlockState.SetContext(ctx)
}
events = ctx.EventManager().ABCIEvents()
}
return events, nil
}
func (app *BaseApp)
beginBlock(_ *abci.RequestFinalizeBlock) (sdk.BeginBlock, error) {
var (
resp sdk.BeginBlock
err error
)
if app.beginBlocker != nil {
resp, err = app.beginBlocker(app.finalizeBlockState.Context())
if err != nil {
return resp, err
}
/ append BeginBlock attributes to all events in the EndBlock response
for i, event := range resp.Events {
resp.Events[i].Attributes = append(
event.Attributes,
abci.EventAttribute{
Key: "mode",
Value: "BeginBlock"
},
)
}
resp.Events = sdk.MarkEventsToIndex(resp.Events, app.indexEvents)
}
return resp, nil
}
func (app *BaseApp)
deliverTx(tx []byte) *abci.ExecTxResult {
gInfo := sdk.GasInfo{
}
resultStr := "successful"
var resp *abci.ExecTxResult
defer func() {
telemetry.IncrCounter(1, "tx", "count")
telemetry.IncrCounter(1, "tx", resultStr)
telemetry.SetGauge(float32(gInfo.GasUsed), "tx", "gas", "used")
telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted")
}()
gInfo, result, anteEvents, err := app.runTx(execModeFinalize, tx, nil)
if err != nil {
resultStr = "failed"
resp = sdkerrors.ResponseExecTxResultWithEvents(
err,
gInfo.GasWanted,
gInfo.GasUsed,
sdk.MarkEventsToIndex(anteEvents, app.indexEvents),
app.trace,
)
return resp
}
resp = &abci.ExecTxResult{
GasWanted: int64(gInfo.GasWanted),
GasUsed: int64(gInfo.GasUsed),
Log: result.Log,
Data: result.Data,
Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents),
}
return resp
}
/ endBlock is an application-defined function that is called after transactions
/ have been processed in FinalizeBlock.
func (app *BaseApp)
endBlock(_ context.Context) (sdk.EndBlock, error) {
var endblock sdk.EndBlock
if app.endBlocker != nil {
eb, err := app.endBlocker(app.finalizeBlockState.Context())
if err != nil {
return endblock, err
}
/ append EndBlock attributes to all events in the EndBlock response
for i, event := range eb.Events {
eb.Events[i].Attributes = append(
event.Attributes,
abci.EventAttribute{
Key: "mode",
Value: "EndBlock"
},
)
}
eb.Events = sdk.MarkEventsToIndex(eb.Events, app.indexEvents)
endblock = eb
}
return endblock, nil
}
/ runTx processes a transaction within a given execution mode, encoded transaction
/ bytes, and the decoded transaction itself. All state transitions occur through
/ a cached Context depending on the mode provided. State only gets persisted
/ if all messages get executed successfully and the execution mode is DeliverTx.
/ Note, gas execution info is always returned. A reference to a Result is
/ returned if the tx does not run out of gas and if all the messages are valid
/ and execute successfully. An error is returned otherwise.
/ both txbytes and the decoded tx are passed to runTx to avoid the state machine encoding the tx and decoding the transaction twice
/ passing the decoded tx to runTX is optional, it will be decoded if the tx is nil
func (app *BaseApp)
runTx(mode execMode, txBytes []byte, tx sdk.Tx) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) {
/ 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 == execModeFinalize && ctx.BlockGasMeter().IsOutOfGas() {
return gInfo, nil, nil, errorsmod.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx")
}
defer func() {
if r := recover(); r != nil {
recoveryMW := newOutOfGasRecoveryMiddleware(gasWanted, ctx, app.runTxRecoveryMiddleware)
err, result = processRecovery(r, recoveryMW), nil
ctx.Logger().Error("panic recovered in runTx", "err", err)
}
gInfo = sdk.GasInfo{
GasWanted: gasWanted,
GasUsed: ctx.GasMeter().GasConsumed()
}
}()
blockGasConsumed := false
/ consumeBlockGas makes sure block gas is consumed at most once. It must
/ happen after tx processing, and must be executed even if tx processing
/ fails. Hence, it's execution is deferred.
consumeBlockGas := func() {
if !blockGasConsumed {
blockGasConsumed = true
ctx.BlockGasMeter().ConsumeGas(
ctx.GasMeter().GasConsumedToLimit(), "block gas meter",
)
}
}
/ 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: consumeBlockGas must exist in a separate defer function from the
/ general deferred recovery function to recover from consumeBlockGas as it'll
/ be executed first (deferred statements are executed as stack).
if mode == execModeFinalize {
defer consumeBlockGas()
}
/ if the transaction is not decoded, decode it here
if tx == nil {
tx, err = app.txDecoder(txBytes)
if err != nil {
return sdk.GasInfo{
GasUsed: 0,
GasWanted: 0
}, nil, nil, sdkerrors.ErrTxDecode.Wrap(err.Error())
}
}
msgs := tx.GetMsgs()
if err := validateBasicTxMsgs(msgs); err != nil {
return sdk.GasInfo{
}, nil, nil, err
}
for _, msg := range msgs {
handler := app.msgServiceRouter.Handler(msg)
if handler == nil {
return sdk.GasInfo{
}, nil, nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg)
}
}
if app.anteHandler != nil {
var (
anteCtx sdk.Context
msCache storetypes.CacheMultiStore
)
/ Branch 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)
anteCtx = anteCtx.WithEventManager(sdk.NewEventManager())
newCtx, err := app.anteHandler(anteCtx, tx, mode == execModeSimulate)
if !newCtx.IsZero() {
/ At this point, newCtx.MultiStore()
is a store branch, or something else
/ replaced by the AnteHandler. We want the original multistore.
/
/ Also, in the case of the tx aborting, we need to track gas consumed via
/ the instantiated gas meter in the AnteHandler, so we update the context
/ prior to returning.
ctx = newCtx.WithMultiStore(ms)
}
events := ctx.EventManager().Events()
/ GasMeter expected to be set in AnteHandler
gasWanted = ctx.GasMeter().Limit()
if err != nil {
if mode == execModeReCheck {
/ if the ante handler fails on recheck, we want to remove the tx from the mempool
if mempoolErr := app.mempool.Remove(tx); mempoolErr != nil {
return gInfo, nil, anteEvents, errors.Join(err, mempoolErr)
}
}
return gInfo, nil, nil, err
}
msCache.Write()
anteEvents = events.ToABCIEvents()
}
switch mode {
case execModeCheck:
err = app.mempool.Insert(ctx, tx)
if err != nil {
return gInfo, nil, anteEvents, err
}
case execModeFinalize:
err = app.mempool.Remove(tx)
if err != nil && !errors.Is(err, mempool.ErrTxNotFound) {
return gInfo, nil, anteEvents,
fmt.Errorf("failed to remove tx from mempool: %w", err)
}
}
/ Create a new Context based off of the existing Context with a MultiStore branch
/ in case message processing fails. At this point, the MultiStore
/ is a branch of a branch.
runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes)
/ Attempt to execute all messages and only update state if all messages pass
/ and we're in DeliverTx. Note, runMsgs will never return a reference to a
/ Result if any single message fails or does not have a registered Handler.
msgsV2, err := tx.GetMsgsV2()
if err == nil {
result, err = app.runMsgs(runMsgCtx, msgs, msgsV2, mode)
}
/ Run optional postHandlers (should run regardless of the execution result).
/
/ Note: If the postHandler fails, we also revert the runMsgs state.
if app.postHandler != nil {
/ The runMsgCtx context currently contains events emitted by the ante handler.
/ We clear this to correctly order events without duplicates.
/ Note that the state is still preserved.
postCtx := runMsgCtx.WithEventManager(sdk.NewEventManager())
newCtx, errPostHandler := app.postHandler(postCtx, tx, mode == execModeSimulate, err == nil)
if errPostHandler != nil {
if err == nil {
/ when the msg was handled successfully, return the post handler error only
return gInfo, nil, anteEvents, errPostHandler
}
/ otherwise append to the msg error so that we keep the original error code for better user experience
return gInfo, nil, anteEvents, errorsmod.Wrapf(err, "postHandler: %s", errPostHandler)
}
/ we don't want runTx to panic if runMsgs has failed earlier
if result == nil {
result = &sdk.Result{
}
}
result.Events = append(result.Events, newCtx.EventManager().ABCIEvents()...)
}
if err == nil {
if mode == execModeFinalize {
/ When block gas exceeds, it'll panic and won't commit the cached store.
consumeBlockGas()
msCache.Write()
}
if len(anteEvents) > 0 && (mode == execModeFinalize || mode == execModeSimulate) {
/ append the events in the order of occurrence
result.Events = append(anteEvents, result.Events...)
}
}
return gInfo, result, anteEvents, err
}
/ runMsgs iterates through a list of messages and executes them with the provided
/ Context and execution mode. Messages will only be executed during simulation
/ and DeliverTx. An error is returned if any single message fails or if a
/ Handler does not exist for a given message route. Otherwise, a reference to a
/ Result is returned. The caller must not commit state if an error is returned.
func (app *BaseApp)
runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Message, mode execMode) (*sdk.Result, error) {
events := sdk.EmptyEvents()
var msgResponses []*codectypes.Any
/ NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter.
for i, msg := range msgs {
if mode != execModeFinalize && mode != execModeSimulate {
break
}
handler := app.msgServiceRouter.Handler(msg)
if handler == nil {
return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg)
}
/ ADR 031 request type routing
msgResult, err := handler(ctx, msg)
if err != nil {
return nil, errorsmod.Wrapf(err, "failed to execute message; message index: %d", i)
}
/ create message events
msgEvents, err := createEvents(app.cdc, msgResult.GetEvents(), msg, msgsV2[i])
if err != nil {
return nil, errorsmod.Wrapf(err, "failed to create message events; message index: %d", i)
}
/ append message events and data
/
/ Note: Each message result's data must be length-prefixed in order to
/ separate each result.
for j, event := range msgEvents {
/ append message index to all events
msgEvents[j] = event.AppendAttributes(sdk.NewAttribute("msg_index", strconv.Itoa(i)))
}
events = events.AppendEvents(msgEvents)
/ Each individual sdk.Result that went through the MsgServiceRouter
/ (which should represent 99% of the Msgs now, since everyone should
/ be using protobuf Msgs)
has exactly one Msg response, set inside
/ `WrapServiceResult`. We take that Msg response, and aggregate it
/ into an array.
if len(msgResult.MsgResponses) > 0 {
msgResponse := msgResult.MsgResponses[0]
if msgResponse == nil {
return nil, sdkerrors.ErrLogic.Wrapf("got nil Msg response at index %d for msg %s", i, sdk.MsgTypeURL(msg))
}
msgResponses = append(msgResponses, msgResponse)
}
}
data, err := makeABCIData(msgResponses)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to marshal tx data")
}
return &sdk.Result{
Data: data,
Events: events.ToABCIEvents(),
MsgResponses: msgResponses,
}, nil
}
/ makeABCIData generates the Data field to be sent to ABCI Check/DeliverTx.
func makeABCIData(msgResponses []*codectypes.Any) ([]byte, error) {
return proto.Marshal(&sdk.TxMsgData{
MsgResponses: msgResponses
})
}
func createEvents(cdc codec.Codec, events sdk.Events, msg sdk.Msg, msgV2 protov2.Message) (sdk.Events, error) {
eventMsgName := sdk.MsgTypeURL(msg)
msgEvent := sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, eventMsgName))
/ we set the signer attribute as the sender
signers, err := cdc.GetMsgV2Signers(msgV2)
if err != nil {
return nil, err
}
if len(signers) > 0 && signers[0] != nil {
addrStr, err := cdc.InterfaceRegistry().SigningContext().AddressCodec().BytesToString(signers[0])
if err != nil {
return nil, err
}
msgEvent = msgEvent.AppendAttributes(sdk.NewAttribute(sdk.AttributeKeySender, addrStr))
}
/ verify that events have no module attribute set
if _, found := events.GetAttributes(sdk.AttributeKeyModule); !found {
if moduleName := sdk.GetModuleNameFromTypeURL(eventMsgName); moduleName != "" {
msgEvent = msgEvent.AppendAttributes(sdk.NewAttribute(sdk.AttributeKeyModule, moduleName))
}
}
return sdk.Events{
msgEvent
}.AppendEvents(events), nil
}
/ PrepareProposalVerifyTx performs transaction verification when a proposer is
/ creating a block proposal during PrepareProposal. Any state committed to the
/ PrepareProposal state internally will be discarded. <nil, err> will be
/ returned if the transaction cannot be encoded. <bz, nil> will be returned if
/ the transaction is valid, otherwise <bz, err> will be returned.
func (app *BaseApp)
PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error) {
bz, err := app.txEncoder(tx)
if err != nil {
return nil, err
}
_, _, _, err = app.runTx(execModePrepareProposal, bz, tx)
if err != nil {
return nil, err
}
return bz, nil
}
/ ProcessProposalVerifyTx performs transaction verification when receiving a
/ block proposal during ProcessProposal. Any state committed to the
/ ProcessProposal state internally will be discarded. <nil, err> will be
/ returned if the transaction cannot be decoded. <Tx, nil> will be returned if
/ the transaction is valid, otherwise <Tx, err> will be returned.
func (app *BaseApp)
ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error) {
tx, err := app.txDecoder(txBz)
if err != nil {
return nil, err
}
_, _, _, err = app.runTx(execModeProcessProposal, txBz, tx)
if err != nil {
return nil, err
}
return tx, nil
}
func (app *BaseApp)
TxDecode(txBytes []byte) (sdk.Tx, error) {
return app.txDecoder(txBytes)
}
func (app *BaseApp)
TxEncode(tx sdk.Tx) ([]byte, error) {
return app.txEncoder(tx)
}
func (app *BaseApp)
StreamingManager()
storetypes.StreamingManager {
return app.streamingManager
}
/ Close is called in start cmd to gracefully cleanup resources.
func (app *BaseApp)
Close()
error {
var errs []error
/ Close app.db (opened by cosmos-sdk/server/start.go call to openDB)
if app.db != nil {
app.logger.Info("Closing application.db")
if err := app.db.Close(); err != nil {
errs = append(errs, err)
}
}
/ Close app.snapshotManager
/ - opened when app chains use cosmos-sdk/server/util.go/DefaultBaseappOptions (boilerplate)
/ - which calls cosmos-sdk/server/util.go/GetSnapshotStore
/ - which is passed to baseapp/options.go/SetSnapshot
/ - to set app.snapshotManager = snapshots.NewManager
if app.snapshotManager != nil {
app.logger.Info("Closing snapshots/metadata.db")
if err := app.snapshotManager.Close(); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
/ GetBaseApp returns the pointer to itself.
func (app *BaseApp)
GetBaseApp() *BaseApp {
return app
}
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. TheCommitMultiStore
is a multi-store, meaning a store of stores. Each module of the application uses one or multipleKVStores
in the multi-store to persist their subset of the state.- Database: The
db
is used by theCommitMultiStore
to handle data persistence. Msg
Service Router: ThemsgServiceRouter
facilitates the routing ofsdk.Msg
requests to the appropriate moduleMsg
service for processing. Here asdk.Msg
refers to the transaction component that needs to be processed by a service in order to update the application state, and not to ABCI message which implements the interface between the application and the underlying consensus engine.- gRPC Query Router: The
grpcQueryRouter
facilitates the routing of gRPC queries to the appropriate module for it to be processed. These queries are not ABCI messages themselves, but they are relayed to the relevant module’s gRPCQuery
service. TxDecoder
: It is used to decode raw transaction bytes relayed by the underlying CometBFT engine.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 duringCheckTx/RecheckTx
andFinalizeBlock
.InitChainer
,PreBlocker
,BeginBlocker
andEndBlocker
: These are the functions executed when the application receives theInitChain
andFinalizeBlock
ABCI messages from the underlying CometBFT engine.
checkState
: This state is updated duringCheckTx
, and reset onCommit
.finalizeBlockState
: This state is updated duringFinalizeBlock
, and set tonil
onCommit
and gets re-initialized onFinalizeBlock
.processProposalState
: This state is updated duringProcessProposal
.prepareProposalState
: This state is updated duringPrepareProposal
.
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 differentminGasPrices
. It is used in theAnteHandler
duringCheckTx
, 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 inminGasPrices
(e.g. ifminGasPrices == 1uatom,1photon
, thegas-price
of the transaction must be greater than1uatom
OR1photon
).appVersion
: Version of the application. It is set in the application’s constructor function.
Constructor
Copy
Ask AI
func NewBaseApp(
name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp),
) *BaseApp {
/ ...
}
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
TheBaseApp
maintains four primary volatile states and a root or main state. The main state
is the canonical state of the application and the volatile states, checkState
, prepareProposalState
, processProposalState
and finalizeBlockState
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 four volatile states by using a mechanism called store branching (performed by CacheWrap
function).
The types can be illustrated as follows:

InitChain State Updates
DuringInitChain
, the four volatile states, checkState
, prepareProposalState
, processProposalState
and finalizeBlockState
are set by branching the root CommitMultiStore
. Any subsequent reads and writes happen
on branched versions of the CommitMultiStore
.
To avoid unnecessary roundtrip to the main state, all reads to the branched store are cached.

CheckTx State Updates
DuringCheckTx
, 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 service router
exists for every message in the transaction. Note, when we execute the AnteHandler
, we branch
the already branched 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.

PrepareProposal State Updates
DuringPrepareProposal
, the prepareProposalState
is set by branching the root CommitMultiStore
.
The prepareProposalState
is used for any reads and writes that occur during the PrepareProposal
phase.
The function uses the Select()
method of the mempool to iterate over the transactions. runTx
is then called,
which encodes and validates each transaction and from there the AnteHandler
is executed.
If successful, valid transactions are returned inclusive of the events, tags, and data generated
during the execution of the proposal.
The described behavior is that of the default handler, applications have the flexibility to define their own
custom mempool handlers.

ProcessProposal State Updates
DuringProcessProposal
, the processProposalState
is set based off of the last committed state
from the root store and is used to process a signed proposal received from a validator.
In this state, runTx
is called and the AnteHandler
is executed and the context used in this state is built with information
from the header and the main state, including the minimum gas prices, which are also set.
Again we want to highlight that the described behavior is that of the default handler and applications have the flexibility to define their own
custom mempool handlers.

FinalizeBlock State Updates
DuringFinalizeBlock
, the finalizeBlockState
is set for use during transaction execution and endblock. The
finalizeBlockState
is based off of the last committed state from the root store and is branched.
Note, the finalizeBlockState
is set to nil
on Commit
.
The state flow for transaction execution is nearly identical to CheckTx
except state transitions occur on
the finalizeBlockState
and messages in a transaction are executed. Similarly to CheckTx
, state transitions
occur on a doubly branched state — finalizeBlockState
. Successful message execution results in
writes being committed to finalizeBlockState
. Note, if message execution fails, state transitions from
the AnteHandler are persisted.
Commit State Updates
DuringCommit
all the state transitions that occurred in the finalizeBlockState
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 finalizeBlockState
is set to nil
to be reset on FinalizeBlock
.

ParamStore
DuringInitChain
, the RequestInitChain
provides ConsensusParams
which contains parameters
related to block execution such as maximum gas and size in addition to evidence parameters. If these
parameters are non-nil, they are set in the BaseApp’s ParamStore
. Behind the scenes, the ParamStore
is managed by an x/consensus_params
module. This allows the parameters to be tweaked via
on-chain governance.
Service Routers
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 viaBaseApp
, which holds a msgServiceRouter
for messages, and a grpcQueryRouter
for queries.
Msg
Service Router
sdk.Msg
s need to be routed after they are extracted from transactions, which are sent from the underlying CometBFT engine via the CheckTx
and FinalizeBlock
ABCI messages. To do so, BaseApp
holds a msgServiceRouter
which maps fully-qualified service methods (string
, defined in each module’s Protobuf Msg
service) to the appropriate module’s MsgServer
implementation.
The default msgServiceRouter
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 each route handler inside msgServiceRouter
. For a stateless router that doesn’t want to make use of this, you can just ignore the ctx
.
The application’s msgServiceRouter
is initialized with all the routes using the application’s module manager (via the RegisterServices
method), which itself is initialized with all the application’s modules in the application’s constructor.
gRPC Query Router
Similar tosdk.Msg
s, queries
need to be routed to the appropriate module’s Query
service. To do so, BaseApp
holds a grpcQueryRouter
, which maps modules’ fully-qualified service methods (string
, defined in their Protobuf Query
gRPC) to their QueryServer
implementation. The grpcQueryRouter
is called during the initial stages of query processing, which can be either by directly sending a gRPC query to the gRPC endpoint, or via the Query
ABCI message on the CometBFT RPC endpoint.
Just like the msgServiceRouter
, the grpcQueryRouter
is initialized with all the query routes using the application’s module manager (via the RegisterServices
method), which itself is initialized with all the application’s modules in the application’s constructor.
Main ABCI 2.0 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 CometBFT. 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.
[]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:
Prepare Proposal
ThePrepareProposal
function is part of the new methods introduced in Application Blockchain Interface (ABCI++) in CometBFT and is an important part of the application’s overall governance system. In the Cosmos SDK, it allows the application to have more fine-grained control over the transactions that are processed, and ensures that only valid transactions are committed to the blockchain.
Here is how the PrepareProposal
function can be implemented:
- Extract the
sdk.Msg
s from the transaction. - Perform stateful checks by calling
Validate()
on each of thesdk.Msg
’s. This is done after stateless checks as stateful checks are more computationally expensive. IfValidate()
fails,PrepareProposal
returns before running further checks, which saves resources. - Perform any additional checks that are specific to the application, such as checking account balances, or ensuring that certain conditions are met before a transaction is proposed.hey are processed by the consensus engine, if necessary.
- Return the updated transactions to be processed by the consensus engine
CheckTx()
, PrepareProposal
process sdk.Msg
s, so it can directly update the state. However, unlike FinalizeBlock()
, it does not commit the state updates. It’s important to exercise caution when using PrepareProposal
as incorrect coding could affect the overall liveness of the network.
It’s important to note that PrepareProposal
complements the ProcessProposal
method which is executed after this method. The combination of these two methods means that it is possible to guarantee that no invalid transactions are ever committed. Furthermore, such a setup can give rise to other interesting use cases such as Oracles, threshold decryption and more.
PrepareProposal
returns a response to the underlying consensus engine of type abci.CheckTxResponse
. 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.
Process Proposal
TheProcessProposal
function is called by the BaseApp as part of the ABCI message flow, and is executed during the FinalizeBlock
phase of the consensus process. 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. It allows a validator to perform application-dependent work in a proposed block, enabling features such as immediate block execution, and allows the Application to reject invalid blocks.
The ProcessProposal
function performs several key tasks, including:
- Validating the proposed block by checking all transactions in it.
- Checking the proposed block against the current state of the application, to ensure that it is valid and that it can be executed.
- Updating the application’s state based on the proposal, if it is valid and passes all checks.
- Returning a response to CometBFT indicating the result of the proposal processing.
ProcessProposal
is an important part of the application’s overall governance system. It is used to manage the network’s parameters and other key aspects of its operation. It also ensures that the coherence property is adhered to i.e. all honest validators must accept a proposal by an honest proposer.
It’s important to note that ProcessProposal
complements the PrepareProposal
method which enables the application to have more fine-grained transaction control by allowing it to reorder, drop, delay, modify, and even add transactions as they see necessary. The combination of these two methods means that it is possible to guarantee that no invalid transactions are ever committed. Furthermore, such a setup can give rise to other interesting use cases such as Oracles, threshold decryption and more.
CometBFT calls it when it receives a proposal and the CometBFT algorithm has not locked on a value. The Application cannot modify the proposal at this point but can reject it if it is invalid. If that is the case, CometBFT will prevote nil
on the proposal, which has strong liveness implications for CometBFT. As a general rule, the Application SHOULD accept a prepared proposal passed via ProcessProposal
, even if a part of the proposal is invalid (e.g., an invalid transaction); the Application can ignore the invalid part of the prepared proposal at block execution time.
However, developers must exercise greater caution when using these methods. Incorrectly coding these methods could affect liveness as CometBFT is unable to receive 2/3 valid precommits to finalize a block.
ProcessProposal
returns a response to the underlying consensus engine of type abci.CheckTxResponse
. 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.
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 the checks lightweight because gas fees are not charged for the resources (CPU, data load…) used during the CheckTx
.
In the Cosmos SDK, after decoding transactions, CheckTx()
is implemented
to do the following checks:
- Extract the
sdk.Msg
s from the transaction. - Optionally perform stateless checks by calling
ValidateBasic()
on each of thesdk.Msg
s. This is done first, as stateless checks are less computationally expensive than stateful checks. IfValidateBasic()
fail,CheckTx
returns before running stateful checks, which saves resources. This check is still performed for messages that have not yet migrated to the new message validation mechanism defined in RFC 001 and still have aValidateBasic()
method. - Perform non-module related stateful checks on the account. This step is mainly about checking
that the
sdk.Msg
signatures are valid, that enough fees are provided and that the sending account has enough funds to pay for said fees. Note that no precisegas
counting occurs here, assdk.Msg
s are not processed. Usually, theAnteHandler
will check that thegas
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.
CheckTx
does not process sdk.Msg
s - they only need to be processed when the canonical state needs to be updated, which happens during FinalizeBlock
.
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.CheckTxResponse
.
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. DuringCheckTx
, 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
Ask AI
package ante
import (
"slices"
"time"
errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/codec/legacy"
"github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
)
/ 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 validateBasic, ok := tx.(sdk.HasValidateBasic); ok {
if err := validateBasic.ValidateBasic(); err != nil {
return ctx, err
}
}
return next(ctx, tx, simulate)
}
/ 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 AccountKeeper
}
func NewValidateMemoDecorator(ak 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.(sdk.TxWithMemo)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
}
memoLength := len(memoTx.GetMemo())
if memoLength > 0 {
params := vmd.ak.GetParams(ctx)
if uint64(memoLength) > params.MaxMemoCharacters {
return ctx, errorsmod.Wrapf(sdkerrors.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 legacytx.StdSignature otherwise simulate mode will incorrectly estimate gas cost.
type ConsumeTxSizeGasDecorator struct {
ak AccountKeeper
}
func NewConsumeGasForTxSizeDecorator(ak 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.(authsigning.SigVerifiableTx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid tx type")
}
params := cgts.ak.GetParams(ctx)
ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*storetypes.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, err := sigTx.GetSignaturesV2()
if err != nil {
return ctx, err
}
n := len(sigs)
signers, err := sigTx.GetSigners()
if err != nil {
return sdk.Context{
}, err
}
for i, signer := range signers {
/ if signature is already filled in, no need to simulate gas cost
if i < n && !isIncompleteSignature(sigs[i].Data) {
continue
}
var pubkey cryptotypes.PubKey
acc := cgts.ak.GetAccount(ctx, signer)
/ 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 := legacytx.StdSignature{ /nolint:staticcheck / SA1019: legacytx.StdSignature is deprecated
Signature: simSecp256k1Sig[:],
PubKey: pubkey,
}
sigBz := legacy.Cdc.MustMarshal(simSig)
cost := storetypes.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.LegacyAminoPubKey); ok {
cost *= params.TxSigLimit
}
ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*cost, "txSize")
}
}
return next(ctx, tx, simulate)
}
/ isIncompleteSignature tests whether SignatureData is fully filled in for simulation purposes
func isIncompleteSignature(data signing.SignatureData)
bool {
if data == nil {
return true
}
switch data := data.(type) {
case *signing.SingleSignatureData:
return len(data.Signature) == 0
case *signing.MultiSignatureData:
if len(data.Signatures) == 0 {
return true
}
if slices.ContainsFunc(data.Signatures, isIncompleteSignature) {
return true
}
}
return false
}
type (
/ TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a
/ tx height timeout.
TxTimeoutHeightDecorator struct{
}
/ TxWithTimeoutHeight defines the interface a tx must implement in order for
/ TxHeightTimeoutDecorator to process the tx.
TxWithTimeoutHeight interface {
sdk.Tx
GetTimeoutHeight()
uint64
GetTimeoutTimeStamp()
time.Time
}
)
/ TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a
/ tx height timeout.
func NewTxTimeoutHeightDecorator()
TxTimeoutHeightDecorator {
return TxTimeoutHeightDecorator{
}
}
/ AnteHandle implements an AnteHandler decorator for the TxHeightTimeoutDecorator
/ type where the current block height is checked against the tx's height timeout.
/ If a height timeout is provided (non-zero)
and is less than the current block
/ height, then an error is returned.
func (txh TxTimeoutHeightDecorator)
AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
timeoutTx, ok := tx.(TxWithTimeoutHeight)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "expected tx to implement TxWithTimeoutHeight")
}
timeoutHeight := timeoutTx.GetTimeoutHeight()
if timeoutHeight > 0 && uint64(ctx.BlockHeight()) > timeoutHeight {
return ctx, errorsmod.Wrapf(
sdkerrors.ErrTxTimeoutHeight, "block height: %d, timeout height: %d", ctx.BlockHeight(), timeoutHeight,
)
}
timeoutTimestamp := timeoutTx.GetTimeoutTimeStamp()
blockTime := ctx.BlockHeader().Time
if !timeoutTimestamp.IsZero() && timeoutTimestamp.Unix() != 0 && timeoutTimestamp.Before(blockTime) {
return ctx, errorsmod.Wrapf(
sdkerrors.ErrTxTimeout, "block time: %s, timeout timestamp: %s", blockTime, timeoutTimestamp.String(),
)
}
return next(ctx, tx, simulate)
}
Events ([]cmn.KVPair)
: Key-Value tags for filtering and indexing transactions (eg. by account). Seeevent
s for more.Codespace (string)
: Namespace for the Code.
RecheckTx
AfterCommit
, CheckTx
is run again on all transactions that remain in the node’s local mempool
excluding the transactions that are 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
.
RunTx, AnteHandler, RunMsgs, PostHandler
RunTx
RunTx
is called from CheckTx
/Finalizeblock
to handle the transaction, with execModeCheck
or execModeFinalize
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 execModeFinalize
). This CacheMultiStore
is a branch of the main store, with cache functionality (for query requests), instantiated during FinalizeBlock
for transaction execution 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()
, when available and for backward compatibility, on each sdk.Msg
in the Tx
, which runs preliminary stateless validity checks. If any sdk.Msg
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
/finalizeBlockState
’s context
and context
’s CacheMultiStore
are branched using the cacheTxContext()
function.
Copy
Ask AI
package baseapp
import (
"context"
"fmt"
"maps"
"math"
"slices"
"strconv"
"sync"
"github.com/cockroachdb/errors"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/crypto/tmhash"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/cosmos/gogoproto/proto"
protov2 "google.golang.org/protobuf/proto"
"cosmossdk.io/core/header"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/log"
"cosmossdk.io/store"
storemetrics "cosmossdk.io/store/metrics"
"cosmossdk.io/store/snapshots"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/baseapp/oe"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/mempool"
"github.com/cosmos/cosmos-sdk/types/msgservice"
)
type (
execMode 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 storetypes.CommitMultiStore)
error
)
const (
execModeCheck execMode = iota / Check a transaction
execModeReCheck / Recheck a (pending)
transaction after a commit
execModeSimulate / Simulate a transaction
execModePrepareProposal / Prepare a block proposal
execModeProcessProposal / Process a block proposal
execModeVoteExtension / Extend or verify a pre-commit vote
execModeVerifyVoteExtension / Verify a vote extension
execModeFinalize / Finalize a block proposal
)
var _ servertypes.ABCI = (*BaseApp)(nil)
/ BaseApp reflects the ABCI application implementation.
type BaseApp struct {
/ initialized on creation
mu sync.Mutex / mu protects the fields below.
logger log.Logger
name string / application name from abci.BlockInfo
db dbm.DB / common DB backend
cms storetypes.CommitMultiStore / Main (uncached)
state
qms storetypes.MultiStore / Optional alternative multistore for querying only.
storeLoader StoreLoader / function to handle store loading, may be overridden with SetStoreLoader()
grpcQueryRouter *GRPCQueryRouter / router for redirecting gRPC query calls
msgServiceRouter *MsgServiceRouter / router for redirecting Msg service messages
interfaceRegistry codectypes.InterfaceRegistry
txDecoder sdk.TxDecoder / unmarshal []byte into sdk.Tx
txEncoder sdk.TxEncoder / marshal sdk.Tx into []byte
mempool mempool.Mempool / application side mempool
anteHandler sdk.AnteHandler / ante handler for fee and auth
postHandler sdk.PostHandler / post handler, optional
checkTxHandler sdk.CheckTxHandler / ABCI CheckTx handler
initChainer sdk.InitChainer / ABCI InitChain handler
preBlocker sdk.PreBlocker / logic to run before BeginBlocker
beginBlocker sdk.BeginBlocker / (legacy ABCI)
BeginBlock handler
endBlocker sdk.EndBlocker / (legacy ABCI)
EndBlock handler
processProposal sdk.ProcessProposalHandler / ABCI ProcessProposal handler
prepareProposal sdk.PrepareProposalHandler / ABCI PrepareProposal
extendVote sdk.ExtendVoteHandler / ABCI ExtendVote handler
verifyVoteExt sdk.VerifyVoteExtensionHandler / ABCI VerifyVoteExtension handler
prepareCheckStater sdk.PrepareCheckStater / logic to run during commit using the checkState
precommiter sdk.Precommiter / logic to run during commit using the deliverState
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.
sigverifyTx bool / in the simulation test, since the account does not have a private key, we have to ignore the tx sigverify.
/ manages snapshots, i.e. dumps of app state at certain intervals
snapshotManager *snapshots.Manager
/ volatile states:
/
/ - checkState is set on InitChain and reset on Commit
/ - finalizeBlockState is set on InitChain and FinalizeBlock and set to nil
/ on Commit.
/
/ - checkState: Used for CheckTx, which is set based on the previous block's
/ state. This state is never committed.
/
/ - prepareProposalState: Used for PrepareProposal, which is set based on the
/ previous block's state. This state is never committed. In case of multiple
/ consensus rounds, the state is always reset to the previous block's state.
/
/ - processProposalState: Used for ProcessProposal, which is set based on the
/ the previous block's state. This state is never committed. In case of
/ multiple rounds, the state is always reset to the previous block's state.
/
/ - finalizeBlockState: Used for FinalizeBlock, which is set based on the
/ previous block's state. This state is committed.
checkState *state
prepareProposalState *state
processProposalState *state
finalizeBlockState *state
/ An inter-block write-through cache provided to the context during the ABCI
/ FinalizeBlock call.
interBlockCache storetypes.MultiStorePersistentCache
/ paramStore is used to query for ABCI consensus parameters from an
/ application parameter store.
paramStore ParamStore
/ queryGasLimit defines the maximum gas for queries; unbounded if 0.
queryGasLimit uint64
/ 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
/ initialHeight is the initial height at which we start the BaseApp
initialHeight int64
/ 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
/ minRetainBlocks defines the minimum block height offset from the current
/ block being committed, such that all blocks past this offset are pruned
/ from CometBFT. It is used as part of the process of determining the
/ ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates
/ that no blocks should be pruned.
/
/ Note: CometBFT block pruning is dependant on this parameter in conjunction
/ with the unbonding (safety threshold)
period, state pruning and state sync
/ snapshot parameters to determine the correct minimum value of
/ ResponseCommit.RetainHeight.
minRetainBlocks uint64
/ application's version string
version string
/ application's protocol version that increments on every upgrade
/ if BaseApp is passed to the upgrade keeper's NewKeeper method.
appVersion uint64
/ recovery handler for app.runTx method
runTxRecoveryMiddleware recoveryMiddleware
/ trace set will return full stack traces for errors in ABCI Log field
trace bool
/ indexEvents defines the set of events in the form {
eventType
}.{
attributeKey
},
/ which informs CometBFT what to index. If empty, all events will be indexed.
indexEvents map[string]struct{
}
/ streamingManager for managing instances and configuration of ABCIListener services
streamingManager storetypes.StreamingManager
chainID string
cdc codec.Codec
/ optimisticExec contains the context required for Optimistic Execution,
/ including the goroutine handling.This is experimental and must be enabled
/ by developers.
optimisticExec *oe.OptimisticExecution
/ disableBlockGasMeter will disable the block gas meter if true, block gas meter is tricky to support
/ when executing transactions in parallel.
/ when disabled, the block gas meter in context is a noop one.
/
/ SAFETY: it's safe to do if validators validate the total gas wanted in the `ProcessProposal`, which is the case in the default handler.
disableBlockGasMeter bool
}
/ 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.
func NewBaseApp(
name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp),
) *BaseApp {
app := &BaseApp{
logger: logger.With(log.ModuleKey, "baseapp"),
name: name,
db: db,
cms: store.NewCommitMultiStore(db, logger, storemetrics.NewNoOpMetrics()), / by default we use a no-op metric gather in store
storeLoader: DefaultStoreLoader,
grpcQueryRouter: NewGRPCQueryRouter(),
msgServiceRouter: NewMsgServiceRouter(),
txDecoder: txDecoder,
fauxMerkleMode: false,
sigverifyTx: true,
queryGasLimit: math.MaxUint64,
}
for _, option := range options {
option(app)
}
if app.mempool == nil {
app.SetMempool(mempool.NoOpMempool{
})
}
abciProposalHandler := NewDefaultProposalHandler(app.mempool, app)
if app.prepareProposal == nil {
app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())
}
if app.processProposal == nil {
app.SetProcessProposal(abciProposalHandler.ProcessProposalHandler())
}
if app.extendVote == nil {
app.SetExtendVoteHandler(NoOpExtendVote())
}
if app.verifyVoteExt == nil {
app.SetVerifyVoteExtensionHandler(NoOpVerifyVoteExtensionHandler())
}
if app.interBlockCache != nil {
app.cms.SetInterBlockCache(app.interBlockCache)
}
app.runTxRecoveryMiddleware = newDefaultRecoveryMiddleware()
/ Initialize with an empty interface registry to avoid nil pointer dereference.
/ Unless SetInterfaceRegistry is called with an interface registry with proper address codecs baseapp will panic.
app.cdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry())
protoFiles, err := proto.MergedRegistry()
if err != nil {
logger.Warn("error creating merged proto registry", "error", err)
}
else {
err = msgservice.ValidateProtoAnnotations(protoFiles)
if err != nil {
/ Once we switch to using protoreflect-based antehandlers, we might
/ want to panic here instead of logging a warning.
logger.Warn("error validating merged proto registry annotations", "error", err)
}
}
return app
}
/ Name returns the name of the BaseApp.
func (app *BaseApp)
Name()
string {
return app.name
}
/ AppVersion returns the application's protocol version.
func (app *BaseApp)
AppVersion()
uint64 {
return app.appVersion
}
/ Version returns the application's version string.
func (app *BaseApp)
Version()
string {
return app.version
}
/ Logger returns the logger of the BaseApp.
func (app *BaseApp)
Logger()
log.Logger {
return app.logger
}
/ Trace returns the boolean value for logging error stack traces.
func (app *BaseApp)
Trace()
bool {
return app.trace
}
/ MsgServiceRouter returns the MsgServiceRouter of a BaseApp.
func (app *BaseApp)
MsgServiceRouter() *MsgServiceRouter {
return app.msgServiceRouter
}
/ GRPCQueryRouter returns the GRPCQueryRouter of a BaseApp.
func (app *BaseApp)
GRPCQueryRouter() *GRPCQueryRouter {
return app.grpcQueryRouter
}
/ MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp
/ multistore.
func (app *BaseApp)
MountStores(keys ...storetypes.StoreKey) {
for _, key := range keys {
switch key.(type) {
case *storetypes.KVStoreKey:
if !app.fauxMerkleMode {
app.MountStore(key, storetypes.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, storetypes.StoreTypeDB)
}
case *storetypes.TransientStoreKey:
app.MountStore(key, storetypes.StoreTypeTransient)
case *storetypes.MemoryStoreKey:
app.MountStore(key, storetypes.StoreTypeMemory)
default:
panic(fmt.Sprintf("Unrecognized store key type :%T", key))
}
}
}
/ MountKVStores mounts all IAVL or DB stores to the provided keys in the
/ BaseApp multistore.
func (app *BaseApp)
MountKVStores(keys map[string]*storetypes.KVStoreKey) {
for _, key := range keys {
if !app.fauxMerkleMode {
app.MountStore(key, storetypes.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, storetypes.StoreTypeDB)
}
}
}
/ MountTransientStores mounts all transient stores to the provided keys in
/ the BaseApp multistore.
func (app *BaseApp)
MountTransientStores(keys map[string]*storetypes.TransientStoreKey) {
for _, key := range keys {
app.MountStore(key, storetypes.StoreTypeTransient)
}
}
/ MountMemoryStores mounts all in-memory KVStores with the BaseApp's internal
/ commit multi-store.
func (app *BaseApp)
MountMemoryStores(keys map[string]*storetypes.MemoryStoreKey) {
skeys := slices.Sorted(maps.Keys(keys))
for _, key := range skeys {
memKey := keys[key]
app.MountStore(memKey, storetypes.StoreTypeMemory)
}
}
/ MountStore mounts a store to the provided key in the BaseApp multistore,
/ using the default DB.
func (app *BaseApp)
MountStore(key storetypes.StoreKey, typ storetypes.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()
error {
err := app.storeLoader(app.cms)
if err != nil {
return fmt.Errorf("failed to load latest version: %w", err)
}
return app.Init()
}
/ DefaultStoreLoader will be used by default and loads the latest version
func DefaultStoreLoader(ms storetypes.CommitMultiStore)
error {
return ms.LoadLatestVersion()
}
/ CommitMultiStore returns the root multi-store.
/ App constructor can use this to access the `cms`.
/ UNSAFE: must not be used during the abci life cycle.
func (app *BaseApp)
CommitMultiStore()
storetypes.CommitMultiStore {
return app.cms
}
/ SnapshotManager returns the snapshot manager.
/ application use this to register extra extension snapshotters.
func (app *BaseApp)
SnapshotManager() *snapshots.Manager {
return app.snapshotManager
}
/ LoadVersion loads the BaseApp application version. It will panic if called
/ more than once on a running baseapp.
func (app *BaseApp)
LoadVersion(version int64)
error {
app.logger.Info("NOTICE: this could take a long time to migrate IAVL store to fastnode if you enable Fast Node.\n")
err := app.cms.LoadVersion(version)
if err != nil {
return fmt.Errorf("failed to load version %d: %w", version, err)
}
return app.Init()
}
/ LastCommitID returns the last CommitID of the multistore.
func (app *BaseApp)
LastCommitID()
storetypes.CommitID {
return app.cms.LastCommitID()
}
/ LastBlockHeight returns the last committed block height.
func (app *BaseApp)
LastBlockHeight()
int64 {
return app.cms.LastCommitID().Version
}
/ ChainID returns the chainID of the app.
func (app *BaseApp)
ChainID()
string {
return app.chainID
}
/ AnteHandler returns the AnteHandler of the app.
func (app *BaseApp)
AnteHandler()
sdk.AnteHandler {
return app.anteHandler
}
/ Mempool returns the Mempool of the app.
func (app *BaseApp)
Mempool()
mempool.Mempool {
return app.mempool
}
/ Init initializes the app. It seals the app, preventing any
/ further modifications. In addition, it validates the app against
/ the earlier provided settings. Returns an error if validation fails.
/ nil otherwise. Panics if the app is already sealed.
func (app *BaseApp)
Init()
error {
if app.sealed {
panic("cannot call initFromMainStore: baseapp already sealed")
}
if app.cms == nil {
return errors.New("commit multi-store must not be nil")
}
emptyHeader := cmtproto.Header{
ChainID: app.chainID
}
/ needed for the export command which inits from store but never calls initchain
app.setState(execModeCheck, emptyHeader)
app.Seal()
return app.cms.GetPruning().Validate()
}
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)
setMinRetainBlocks(minRetainBlocks uint64) {
app.minRetainBlocks = minRetainBlocks
}
func (app *BaseApp)
setInterBlockCache(cache storetypes.MultiStorePersistentCache) {
app.interBlockCache = cache
}
func (app *BaseApp)
setTrace(trace bool) {
app.trace = trace
}
func (app *BaseApp)
setIndexEvents(ie []string) {
app.indexEvents = make(map[string]struct{
})
for _, e := range ie {
app.indexEvents[e] = struct{
}{
}
}
}
/ 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
}
/ setState sets the BaseApp's state for the corresponding mode with a branched
/ multi-store (i.e. a CacheMultiStore)
and a new Context with the same
/ multi-store branch, and provided header.
func (app *BaseApp)
setState(mode execMode, h cmtproto.Header) {
ms := app.cms.CacheMultiStore()
headerInfo := header.Info{
Height: h.Height,
Time: h.Time,
ChainID: h.ChainID,
AppHash: h.AppHash,
}
baseState := &state{
ms: ms,
ctx: sdk.NewContext(ms, h, false, app.logger).
WithStreamingManager(app.streamingManager).
WithHeaderInfo(headerInfo),
}
switch mode {
case execModeCheck:
baseState.SetContext(baseState.Context().WithIsCheckTx(true).WithMinGasPrices(app.minGasPrices))
app.checkState = baseState
case execModePrepareProposal:
app.prepareProposalState = baseState
case execModeProcessProposal:
app.processProposalState = baseState
case execModeFinalize:
app.finalizeBlockState = baseState
default:
panic(fmt.Sprintf("invalid runTxMode for setState: %d", mode))
}
}
/ SetCircuitBreaker sets the circuit breaker for the BaseApp.
/ The circuit breaker is checked on every message execution to verify if a transaction should be executed or not.
func (app *BaseApp)
SetCircuitBreaker(cb CircuitBreaker) {
if app.msgServiceRouter == nil {
panic("cannot set circuit breaker with no msg service router set")
}
app.msgServiceRouter.SetCircuit(cb)
}
/ GetConsensusParams returns the current consensus parameters from the BaseApp's
/ ParamStore. If the BaseApp has no ParamStore defined, nil is returned.
func (app *BaseApp)
GetConsensusParams(ctx sdk.Context)
cmtproto.ConsensusParams {
if app.paramStore == nil {
return cmtproto.ConsensusParams{
}
}
cp, err := app.paramStore.Get(ctx)
if err != nil {
/ This could happen while migrating from v0.45/v0.46 to v0.50, we should
/ allow it to happen so during preblock the upgrade plan can be executed
/ and the consensus params set for the first time in the new format.
app.logger.Error("failed to get consensus params", "err", err)
return cmtproto.ConsensusParams{
}
}
return cp
}
/ StoreConsensusParams sets the consensus parameters to the BaseApp's param
/ store.
/
/ NOTE: We're explicitly not storing the CometBFT app_version in the param store.
/ It's stored instead in the x/upgrade store, with its own bump logic.
func (app *BaseApp)
StoreConsensusParams(ctx sdk.Context, cp cmtproto.ConsensusParams)
error {
if app.paramStore == nil {
return errors.New("cannot store consensus params with no params store set")
}
return app.paramStore.Set(ctx, cp)
}
/ AddRunTxRecoveryHandler adds custom app.runTx method panic handlers.
func (app *BaseApp)
AddRunTxRecoveryHandler(handlers ...RecoveryHandler) {
for _, h := range handlers {
app.runTxRecoveryMiddleware = newRecoveryMiddleware(h, app.runTxRecoveryMiddleware)
}
}
/ 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(ctx sdk.Context)
uint64 {
cp := app.GetConsensusParams(ctx)
if cp.Block == nil {
return 0
}
maxGas := cp.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)
validateFinalizeBlockHeight(req *abci.RequestFinalizeBlock)
error {
if req.Height < 1 {
return fmt.Errorf("invalid height: %d", req.Height)
}
lastBlockHeight := app.LastBlockHeight()
/ expectedHeight holds the expected height to validate
var expectedHeight int64
if lastBlockHeight == 0 && app.initialHeight > 1 {
/ In this case, we're validating the first block of the chain, i.e no
/ previous commit. The height we're expecting is the initial height.
expectedHeight = app.initialHeight
}
else {
/ This case can mean two things:
/
/ - Either there was already a previous commit in the store, in which
/ case we increment the version from there.
/ - Or there was no previous commit, in which case we start at version 1.
expectedHeight = lastBlockHeight + 1
}
if req.Height != expectedHeight {
return fmt.Errorf("invalid height: %d; expected: %d", req.Height, expectedHeight)
}
return nil
}
/ validateBasicTxMsgs executes basic validator calls for messages.
func validateBasicTxMsgs(msgs []sdk.Msg)
error {
if len(msgs) == 0 {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "must contain at least one message")
}
for _, msg := range msgs {
m, ok := msg.(sdk.HasValidateBasic)
if !ok {
continue
}
if err := m.ValidateBasic(); err != nil {
return err
}
}
return nil
}
func (app *BaseApp)
getState(mode execMode) *state {
switch mode {
case execModeFinalize:
return app.finalizeBlockState
case execModePrepareProposal:
return app.prepareProposalState
case execModeProcessProposal:
return app.processProposalState
default:
return app.checkState
}
}
func (app *BaseApp)
getBlockGasMeter(ctx sdk.Context)
storetypes.GasMeter {
if app.disableBlockGasMeter {
return noopGasMeter{
}
}
if maxGas := app.GetMaximumBlockGas(ctx); maxGas > 0 {
return storetypes.NewGasMeter(maxGas)
}
return storetypes.NewInfiniteGasMeter()
}
/ retrieve the context for the tx w/ txBytes and other memoized values.
func (app *BaseApp)
getContextForTx(mode execMode, txBytes []byte)
sdk.Context {
app.mu.Lock()
defer app.mu.Unlock()
modeState := app.getState(mode)
if modeState == nil {
panic(fmt.Sprintf("state is nil for mode %v", mode))
}
ctx := modeState.Context().
WithTxBytes(txBytes).
WithGasMeter(storetypes.NewInfiniteGasMeter())
/ WithVoteInfos(app.voteInfos) / TODO: identify if this is needed
ctx = ctx.WithIsSigverifyTx(app.sigverifyTx)
ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx))
if mode == execModeReCheck {
ctx = ctx.WithIsReCheckTx(true)
}
if mode == execModeSimulate {
ctx, _ = ctx.CacheContext()
ctx = ctx.WithExecMode(sdk.ExecMode(execModeSimulate))
}
return ctx
}
/ cacheTxContext returns a new context based off of the provided context with
/ a branched multi-store.
func (app *BaseApp)
cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context, storetypes.CacheMultiStore) {
ms := ctx.MultiStore()
msCache := ms.CacheMultiStore()
if msCache.TracingEnabled() {
msCache = msCache.SetTracingContext(
storetypes.TraceContext(
map[string]any{
"txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)),
},
),
).(storetypes.CacheMultiStore)
}
return ctx.WithMultiStore(msCache), msCache
}
func (app *BaseApp)
preBlock(req *abci.RequestFinalizeBlock) ([]abci.Event, error) {
var events []abci.Event
if app.preBlocker != nil {
ctx := app.finalizeBlockState.Context().WithEventManager(sdk.NewEventManager())
rsp, err := app.preBlocker(ctx, req)
if err != nil {
return nil, err
}
/ rsp.ConsensusParamsChanged is true from preBlocker means ConsensusParams in store get changed
/ write the consensus parameters in store to context
if rsp.ConsensusParamsChanged {
ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx))
/ GasMeter must be set after we get a context with updated consensus params.
gasMeter := app.getBlockGasMeter(ctx)
ctx = ctx.WithBlockGasMeter(gasMeter)
app.finalizeBlockState.SetContext(ctx)
}
events = ctx.EventManager().ABCIEvents()
}
return events, nil
}
func (app *BaseApp)
beginBlock(_ *abci.RequestFinalizeBlock) (sdk.BeginBlock, error) {
var (
resp sdk.BeginBlock
err error
)
if app.beginBlocker != nil {
resp, err = app.beginBlocker(app.finalizeBlockState.Context())
if err != nil {
return resp, err
}
/ append BeginBlock attributes to all events in the EndBlock response
for i, event := range resp.Events {
resp.Events[i].Attributes = append(
event.Attributes,
abci.EventAttribute{
Key: "mode",
Value: "BeginBlock"
},
)
}
resp.Events = sdk.MarkEventsToIndex(resp.Events, app.indexEvents)
}
return resp, nil
}
func (app *BaseApp)
deliverTx(tx []byte) *abci.ExecTxResult {
gInfo := sdk.GasInfo{
}
resultStr := "successful"
var resp *abci.ExecTxResult
defer func() {
telemetry.IncrCounter(1, "tx", "count")
telemetry.IncrCounter(1, "tx", resultStr)
telemetry.SetGauge(float32(gInfo.GasUsed), "tx", "gas", "used")
telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted")
}()
gInfo, result, anteEvents, err := app.runTx(execModeFinalize, tx, nil)
if err != nil {
resultStr = "failed"
resp = sdkerrors.ResponseExecTxResultWithEvents(
err,
gInfo.GasWanted,
gInfo.GasUsed,
sdk.MarkEventsToIndex(anteEvents, app.indexEvents),
app.trace,
)
return resp
}
resp = &abci.ExecTxResult{
GasWanted: int64(gInfo.GasWanted),
GasUsed: int64(gInfo.GasUsed),
Log: result.Log,
Data: result.Data,
Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents),
}
return resp
}
/ endBlock is an application-defined function that is called after transactions
/ have been processed in FinalizeBlock.
func (app *BaseApp)
endBlock(_ context.Context) (sdk.EndBlock, error) {
var endblock sdk.EndBlock
if app.endBlocker != nil {
eb, err := app.endBlocker(app.finalizeBlockState.Context())
if err != nil {
return endblock, err
}
/ append EndBlock attributes to all events in the EndBlock response
for i, event := range eb.Events {
eb.Events[i].Attributes = append(
event.Attributes,
abci.EventAttribute{
Key: "mode",
Value: "EndBlock"
},
)
}
eb.Events = sdk.MarkEventsToIndex(eb.Events, app.indexEvents)
endblock = eb
}
return endblock, nil
}
/ runTx processes a transaction within a given execution mode, encoded transaction
/ bytes, and the decoded transaction itself. All state transitions occur through
/ a cached Context depending on the mode provided. State only gets persisted
/ if all messages get executed successfully and the execution mode is DeliverTx.
/ Note, gas execution info is always returned. A reference to a Result is
/ returned if the tx does not run out of gas and if all the messages are valid
/ and execute successfully. An error is returned otherwise.
/ both txbytes and the decoded tx are passed to runTx to avoid the state machine encoding the tx and decoding the transaction twice
/ passing the decoded tx to runTX is optional, it will be decoded if the tx is nil
func (app *BaseApp)
runTx(mode execMode, txBytes []byte, tx sdk.Tx) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) {
/ 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 == execModeFinalize && ctx.BlockGasMeter().IsOutOfGas() {
return gInfo, nil, nil, errorsmod.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx")
}
defer func() {
if r := recover(); r != nil {
recoveryMW := newOutOfGasRecoveryMiddleware(gasWanted, ctx, app.runTxRecoveryMiddleware)
err, result = processRecovery(r, recoveryMW), nil
ctx.Logger().Error("panic recovered in runTx", "err", err)
}
gInfo = sdk.GasInfo{
GasWanted: gasWanted,
GasUsed: ctx.GasMeter().GasConsumed()
}
}()
blockGasConsumed := false
/ consumeBlockGas makes sure block gas is consumed at most once. It must
/ happen after tx processing, and must be executed even if tx processing
/ fails. Hence, it's execution is deferred.
consumeBlockGas := func() {
if !blockGasConsumed {
blockGasConsumed = true
ctx.BlockGasMeter().ConsumeGas(
ctx.GasMeter().GasConsumedToLimit(), "block gas meter",
)
}
}
/ 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: consumeBlockGas must exist in a separate defer function from the
/ general deferred recovery function to recover from consumeBlockGas as it'll
/ be executed first (deferred statements are executed as stack).
if mode == execModeFinalize {
defer consumeBlockGas()
}
/ if the transaction is not decoded, decode it here
if tx == nil {
tx, err = app.txDecoder(txBytes)
if err != nil {
return sdk.GasInfo{
GasUsed: 0,
GasWanted: 0
}, nil, nil, sdkerrors.ErrTxDecode.Wrap(err.Error())
}
}
msgs := tx.GetMsgs()
if err := validateBasicTxMsgs(msgs); err != nil {
return sdk.GasInfo{
}, nil, nil, err
}
for _, msg := range msgs {
handler := app.msgServiceRouter.Handler(msg)
if handler == nil {
return sdk.GasInfo{
}, nil, nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg)
}
}
if app.anteHandler != nil {
var (
anteCtx sdk.Context
msCache storetypes.CacheMultiStore
)
/ Branch 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)
anteCtx = anteCtx.WithEventManager(sdk.NewEventManager())
newCtx, err := app.anteHandler(anteCtx, tx, mode == execModeSimulate)
if !newCtx.IsZero() {
/ At this point, newCtx.MultiStore()
is a store branch, or something else
/ replaced by the AnteHandler. We want the original multistore.
/
/ Also, in the case of the tx aborting, we need to track gas consumed via
/ the instantiated gas meter in the AnteHandler, so we update the context
/ prior to returning.
ctx = newCtx.WithMultiStore(ms)
}
events := ctx.EventManager().Events()
/ GasMeter expected to be set in AnteHandler
gasWanted = ctx.GasMeter().Limit()
if err != nil {
if mode == execModeReCheck {
/ if the ante handler fails on recheck, we want to remove the tx from the mempool
if mempoolErr := app.mempool.Remove(tx); mempoolErr != nil {
return gInfo, nil, anteEvents, errors.Join(err, mempoolErr)
}
}
return gInfo, nil, nil, err
}
msCache.Write()
anteEvents = events.ToABCIEvents()
}
switch mode {
case execModeCheck:
err = app.mempool.Insert(ctx, tx)
if err != nil {
return gInfo, nil, anteEvents, err
}
case execModeFinalize:
err = app.mempool.Remove(tx)
if err != nil && !errors.Is(err, mempool.ErrTxNotFound) {
return gInfo, nil, anteEvents,
fmt.Errorf("failed to remove tx from mempool: %w", err)
}
}
/ Create a new Context based off of the existing Context with a MultiStore branch
/ in case message processing fails. At this point, the MultiStore
/ is a branch of a branch.
runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes)
/ Attempt to execute all messages and only update state if all messages pass
/ and we're in DeliverTx. Note, runMsgs will never return a reference to a
/ Result if any single message fails or does not have a registered Handler.
msgsV2, err := tx.GetMsgsV2()
if err == nil {
result, err = app.runMsgs(runMsgCtx, msgs, msgsV2, mode)
}
/ Run optional postHandlers (should run regardless of the execution result).
/
/ Note: If the postHandler fails, we also revert the runMsgs state.
if app.postHandler != nil {
/ The runMsgCtx context currently contains events emitted by the ante handler.
/ We clear this to correctly order events without duplicates.
/ Note that the state is still preserved.
postCtx := runMsgCtx.WithEventManager(sdk.NewEventManager())
newCtx, errPostHandler := app.postHandler(postCtx, tx, mode == execModeSimulate, err == nil)
if errPostHandler != nil {
if err == nil {
/ when the msg was handled successfully, return the post handler error only
return gInfo, nil, anteEvents, errPostHandler
}
/ otherwise append to the msg error so that we keep the original error code for better user experience
return gInfo, nil, anteEvents, errorsmod.Wrapf(err, "postHandler: %s", errPostHandler)
}
/ we don't want runTx to panic if runMsgs has failed earlier
if result == nil {
result = &sdk.Result{
}
}
result.Events = append(result.Events, newCtx.EventManager().ABCIEvents()...)
}
if err == nil {
if mode == execModeFinalize {
/ When block gas exceeds, it'll panic and won't commit the cached store.
consumeBlockGas()
msCache.Write()
}
if len(anteEvents) > 0 && (mode == execModeFinalize || mode == execModeSimulate) {
/ append the events in the order of occurrence
result.Events = append(anteEvents, result.Events...)
}
}
return gInfo, result, anteEvents, err
}
/ runMsgs iterates through a list of messages and executes them with the provided
/ Context and execution mode. Messages will only be executed during simulation
/ and DeliverTx. An error is returned if any single message fails or if a
/ Handler does not exist for a given message route. Otherwise, a reference to a
/ Result is returned. The caller must not commit state if an error is returned.
func (app *BaseApp)
runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Message, mode execMode) (*sdk.Result, error) {
events := sdk.EmptyEvents()
var msgResponses []*codectypes.Any
/ NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter.
for i, msg := range msgs {
if mode != execModeFinalize && mode != execModeSimulate {
break
}
handler := app.msgServiceRouter.Handler(msg)
if handler == nil {
return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg)
}
/ ADR 031 request type routing
msgResult, err := handler(ctx, msg)
if err != nil {
return nil, errorsmod.Wrapf(err, "failed to execute message; message index: %d", i)
}
/ create message events
msgEvents, err := createEvents(app.cdc, msgResult.GetEvents(), msg, msgsV2[i])
if err != nil {
return nil, errorsmod.Wrapf(err, "failed to create message events; message index: %d", i)
}
/ append message events and data
/
/ Note: Each message result's data must be length-prefixed in order to
/ separate each result.
for j, event := range msgEvents {
/ append message index to all events
msgEvents[j] = event.AppendAttributes(sdk.NewAttribute("msg_index", strconv.Itoa(i)))
}
events = events.AppendEvents(msgEvents)
/ Each individual sdk.Result that went through the MsgServiceRouter
/ (which should represent 99% of the Msgs now, since everyone should
/ be using protobuf Msgs)
has exactly one Msg response, set inside
/ `WrapServiceResult`. We take that Msg response, and aggregate it
/ into an array.
if len(msgResult.MsgResponses) > 0 {
msgResponse := msgResult.MsgResponses[0]
if msgResponse == nil {
return nil, sdkerrors.ErrLogic.Wrapf("got nil Msg response at index %d for msg %s", i, sdk.MsgTypeURL(msg))
}
msgResponses = append(msgResponses, msgResponse)
}
}
data, err := makeABCIData(msgResponses)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to marshal tx data")
}
return &sdk.Result{
Data: data,
Events: events.ToABCIEvents(),
MsgResponses: msgResponses,
}, nil
}
/ makeABCIData generates the Data field to be sent to ABCI Check/DeliverTx.
func makeABCIData(msgResponses []*codectypes.Any) ([]byte, error) {
return proto.Marshal(&sdk.TxMsgData{
MsgResponses: msgResponses
})
}
func createEvents(cdc codec.Codec, events sdk.Events, msg sdk.Msg, msgV2 protov2.Message) (sdk.Events, error) {
eventMsgName := sdk.MsgTypeURL(msg)
msgEvent := sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, eventMsgName))
/ we set the signer attribute as the sender
signers, err := cdc.GetMsgV2Signers(msgV2)
if err != nil {
return nil, err
}
if len(signers) > 0 && signers[0] != nil {
addrStr, err := cdc.InterfaceRegistry().SigningContext().AddressCodec().BytesToString(signers[0])
if err != nil {
return nil, err
}
msgEvent = msgEvent.AppendAttributes(sdk.NewAttribute(sdk.AttributeKeySender, addrStr))
}
/ verify that events have no module attribute set
if _, found := events.GetAttributes(sdk.AttributeKeyModule); !found {
if moduleName := sdk.GetModuleNameFromTypeURL(eventMsgName); moduleName != "" {
msgEvent = msgEvent.AppendAttributes(sdk.NewAttribute(sdk.AttributeKeyModule, moduleName))
}
}
return sdk.Events{
msgEvent
}.AppendEvents(events), nil
}
/ PrepareProposalVerifyTx performs transaction verification when a proposer is
/ creating a block proposal during PrepareProposal. Any state committed to the
/ PrepareProposal state internally will be discarded. <nil, err> will be
/ returned if the transaction cannot be encoded. <bz, nil> will be returned if
/ the transaction is valid, otherwise <bz, err> will be returned.
func (app *BaseApp)
PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error) {
bz, err := app.txEncoder(tx)
if err != nil {
return nil, err
}
_, _, _, err = app.runTx(execModePrepareProposal, bz, tx)
if err != nil {
return nil, err
}
return bz, nil
}
/ ProcessProposalVerifyTx performs transaction verification when receiving a
/ block proposal during ProcessProposal. Any state committed to the
/ ProcessProposal state internally will be discarded. <nil, err> will be
/ returned if the transaction cannot be decoded. <Tx, nil> will be returned if
/ the transaction is valid, otherwise <Tx, err> will be returned.
func (app *BaseApp)
ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error) {
tx, err := app.txDecoder(txBz)
if err != nil {
return nil, err
}
_, _, _, err = app.runTx(execModeProcessProposal, txBz, tx)
if err != nil {
return nil, err
}
return tx, nil
}
func (app *BaseApp)
TxDecode(txBytes []byte) (sdk.Tx, error) {
return app.txDecoder(txBytes)
}
func (app *BaseApp)
TxEncode(tx sdk.Tx) ([]byte, error) {
return app.txEncoder(tx)
}
func (app *BaseApp)
StreamingManager()
storetypes.StreamingManager {
return app.streamingManager
}
/ Close is called in start cmd to gracefully cleanup resources.
func (app *BaseApp)
Close()
error {
var errs []error
/ Close app.db (opened by cosmos-sdk/server/start.go call to openDB)
if app.db != nil {
app.logger.Info("Closing application.db")
if err := app.db.Close(); err != nil {
errs = append(errs, err)
}
}
/ Close app.snapshotManager
/ - opened when app chains use cosmos-sdk/server/util.go/DefaultBaseappOptions (boilerplate)
/ - which calls cosmos-sdk/server/util.go/GetSnapshotStore
/ - which is passed to baseapp/options.go/SetSnapshot
/ - to set app.snapshotManager = snapshots.NewManager
if app.snapshotManager != nil {
app.logger.Info("Closing snapshots/metadata.db")
if err := app.snapshotManager.Close(); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
/ GetBaseApp returns the pointer to itself.
func (app *BaseApp)
GetBaseApp() *BaseApp {
return app
}
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 sdk.Msg
s in the Tx
. In preparation of this step, just like with the anteHandler
, both the checkState
/finalizeBlockState
’s context
and context
’s CacheMultiStore
are branched using the cacheTxContext()
function.
AnteHandler
TheAnteHandler
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
Ask AI
package types
/ 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)
/ PostHandler like AnteHandler but it executes after RunMsgs. Runs on success
/ or failure and enables use cases like gas refunding.
type PostHandler func(ctx Context, tx Tx, simulate, success bool) (newCtx Context, err error)
/ AnteDecorator wraps the next AnteHandler to perform custom pre-processing.
type AnteDecorator interface {
AnteHandle(ctx Context, tx Tx, simulate bool, next AnteHandler) (newCtx Context, err error)
}
/ PostDecorator wraps the next PostHandler to perform custom post-processing.
type PostDecorator interface {
PostHandle(ctx Context, tx Tx, simulate, success bool, next PostHandler) (newCtx Context, err error)
}
/ ChainAnteDecorators 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.
/ Returns nil when no AnteDecorator are supplied.
func ChainAnteDecorators(chain ...AnteDecorator)
AnteHandler {
if len(chain) == 0 {
return nil
}
handlerChain := make([]AnteHandler, len(chain)+1)
/ set the terminal AnteHandler decorator
handlerChain[len(chain)] = func(ctx Context, tx Tx, simulate bool) (Context, error) {
return ctx, nil
}
for i := range chain {
ii := i
handlerChain[ii] = func(ctx Context, tx Tx, simulate bool) (Context, error) {
return chain[ii].AnteHandle(ctx, tx, simulate, handlerChain[ii+1])
}
}
return handlerChain[0]
}
/ ChainPostDecorators chains PostDecorators together with each PostDecorator
/ wrapping over the decorators further along chain and returns a single PostHandler.
/
/ 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.
func ChainPostDecorators(chain ...PostDecorator)
PostHandler {
if len(chain) == 0 {
return nil
}
handlerChain := make([]PostHandler, len(chain)+1)
/ set the terminal PostHandler decorator
handlerChain[len(chain)] = func(ctx Context, tx Tx, simulate, success bool) (Context, error) {
return ctx, nil
}
for i := range chain {
ii := i
handlerChain[ii] = func(ctx Context, tx Tx, simulate, success bool) (Context, error) {
return chain[ii].PostHandle(ctx, tx, simulate, success, handlerChain[ii+1])
}
}
return handlerChain[0]
}
/ 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 \ \ \ /
/
/ Deprecated: Terminator is retired (ref https://github.com/cosmos/cosmos-sdk/pull/16076).
type Terminator struct{
}
/ AnteHandle returns the provided Context and nil error
func (t Terminator)
AnteHandle(ctx Context, _ Tx, _ bool, _ AnteHandler) (Context, error) {
return ctx, nil
}
/ PostHandle returns the provided Context and nil error
func (t Terminator)
PostHandle(ctx Context, _ Tx, _, _ bool, _ PostHandler) (Context, error) {
return ctx, nil
}
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 incentivization of stakeholders via the collection of transaction fees.
BaseApp
holds an anteHandler
as parameter that is initialized in the application’s constructor. The most widely used anteHandler
is 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 execModeFinalize
to actually process the sdk.Msg
s.
First, it retrieves the sdk.Msg
’s fully-qualified type name, by checking the type_url
of the Protobuf Any
representing the sdk.Msg
. Then, using the application’s msgServiceRouter
, it checks for the existence of Msg
service method related to that type_url
. At this point, if mode == runTxModeCheck
, RunMsgs
returns. Otherwise, if mode == execModeFinalize
, the Msg
service RPC is executed, before RunMsgs
returns.
PostHandler
PostHandler
is similar to AnteHandler
, but it, as the name suggests, executes custom post tx processing logic after RunMsgs
is called. PostHandler
receives the Result
of the RunMsgs
in order to enable this customizable behavior.
Like AnteHandler
s, PostHandler
s are theoretically optional.
Other use cases like unused gas refund can also be enabled by PostHandler
s.
Copy
Ask AI
package posthandler
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
/ HandlerOptions are the options required for constructing a default SDK PostHandler.
type HandlerOptions struct{
}
/ NewPostHandler returns an empty PostHandler chain.
func NewPostHandler(_ HandlerOptions) (sdk.PostHandler, error) {
postDecorators := []sdk.PostDecorator{
}
return sdk.ChainPostDecorators(postDecorators...), nil
}
PostHandler
s fail, the state from runMsgs
is also reverted, effectively making the transaction fail.
Other ABCI Messages
InitChain
TheInitChain
ABCI message is sent from the underlying CometBFT engine when the chain is first started. It is mainly used to initialize parameters and state like:
- Consensus Parameters via
setConsensusParams
. checkState
andfinalizeBlockState
viasetState
.- The block gas meter, with infinite gas to process genesis transactions.
InitChain(req abci.InitChainRequest)
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.
FinalizeBlock
TheFinalizeBlock
ABCI message is sent from the underlying CometBFT engine when a block proposal created by the correct proposer is received. The previous BeginBlock, DeliverTx and Endblock
calls are private methods on the BaseApp struct.
Copy
Ask AI
package baseapp
import (
"context"
"fmt"
"sort"
"strings"
"time"
"github.com/cockroachdb/errors"
abci "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
"github.com/cosmos/gogoproto/proto"
"google.golang.org/grpc/codes"
grpcstatus "google.golang.org/grpc/status"
coreheader "cosmossdk.io/core/header"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/store/rootmulti"
snapshottypes "cosmossdk.io/store/snapshots/types"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
/ Supported ABCI Query prefixes and paths
const (
QueryPathApp = "app"
QueryPathCustom = "custom"
QueryPathP2P = "p2p"
QueryPathStore = "store"
QueryPathBroadcastTx = "/cosmos.tx.v1beta1.Service/BroadcastTx"
)
func (app *BaseApp)
InitChain(req *abci.RequestInitChain) (*abci.ResponseInitChain, error) {
if req.ChainId != app.chainID {
return nil, fmt.Errorf("invalid chain-id on InitChain; expected: %s, got: %s", app.chainID, req.ChainId)
}
/ On a new chain, we consider the init chain block height as 0, even though
/ req.InitialHeight is 1 by default.
initHeader := cmtproto.Header{
ChainID: req.ChainId,
Time: req.Time
}
app.logger.Info("InitChain", "initialHeight", req.InitialHeight, "chainID", req.ChainId)
/ Set the initial height, which will be used to determine if we are proposing
/ or processing the first block or not.
app.initialHeight = req.InitialHeight
if app.initialHeight == 0 { / If initial height is 0, set it to 1
app.initialHeight = 1
}
/ if req.InitialHeight is > 1, then we set the initial version on all stores
if req.InitialHeight > 1 {
initHeader.Height = req.InitialHeight
if err := app.cms.SetInitialVersion(req.InitialHeight); err != nil {
return nil, err
}
}
/ initialize states with a correct header
app.setState(execModeFinalize, initHeader)
app.setState(execModeCheck, initHeader)
/ Store the consensus params in the BaseApp's param store. Note, this must be
/ done after the finalizeBlockState and context have been set as it's persisted
/ to state.
if req.ConsensusParams != nil {
err := app.StoreConsensusParams(app.finalizeBlockState.Context(), *req.ConsensusParams)
if err != nil {
return nil, err
}
}
defer func() {
/ InitChain represents the state of the application BEFORE the first block,
/ i.e. the genesis block. This means that when processing the app's InitChain
/ handler, the block height is zero by default. However, after Commit is called
/ the height needs to reflect the true block height.
initHeader.Height = req.InitialHeight
app.checkState.SetContext(app.checkState.Context().WithBlockHeader(initHeader).
WithHeaderInfo(coreheader.Info{
ChainID: req.ChainId,
Height: req.InitialHeight,
Time: req.Time,
}))
app.finalizeBlockState.SetContext(app.finalizeBlockState.Context().WithBlockHeader(initHeader).
WithHeaderInfo(coreheader.Info{
ChainID: req.ChainId,
Height: req.InitialHeight,
Time: req.Time,
}))
}()
if app.initChainer == nil {
return &abci.ResponseInitChain{
}, nil
}
/ add block gas meter for any genesis transactions (allow infinite gas)
app.finalizeBlockState.SetContext(app.finalizeBlockState.Context().WithBlockGasMeter(storetypes.NewInfiniteGasMeter()))
res, err := app.initChainer(app.finalizeBlockState.Context(), req)
if err != nil {
return nil, err
}
if len(req.Validators) > 0 {
if len(req.Validators) != len(res.Validators) {
return nil, fmt.Errorf(
"len(RequestInitChain.Validators) != len(GenesisValidators) (%d != %d)",
len(req.Validators), len(res.Validators),
)
}
sort.Sort(abci.ValidatorUpdates(req.Validators))
sort.Sort(abci.ValidatorUpdates(res.Validators))
for i := range res.Validators {
if !proto.Equal(&res.Validators[i], &req.Validators[i]) {
return nil, fmt.Errorf("genesisValidators[%d] != req.Validators[%d] ", i, i)
}
}
}
/ NOTE: We don't commit, but FinalizeBlock for block InitialHeight starts from
/ this FinalizeBlockState.
return &abci.ResponseInitChain{
ConsensusParams: res.ConsensusParams,
Validators: res.Validators,
AppHash: app.LastCommitID().Hash,
}, nil
}
func (app *BaseApp)
Info(_ *abci.RequestInfo) (*abci.ResponseInfo, error) {
lastCommitID := app.cms.LastCommitID()
return &abci.ResponseInfo{
Data: app.name,
Version: app.version,
AppVersion: app.appVersion,
LastBlockHeight: lastCommitID.Version,
LastBlockAppHash: lastCommitID.Hash,
}, nil
}
/ Query implements the ABCI interface. It delegates to CommitMultiStore if it
/ implements Queryable.
func (app *BaseApp)
Query(_ context.Context, req *abci.RequestQuery) (resp *abci.ResponseQuery, err error) {
/ add panic recovery for all queries
/
/ Ref: https://github.com/cosmos/cosmos-sdk/pull/8039
defer func() {
if r := recover(); r != nil {
resp = sdkerrors.QueryResult(errorsmod.Wrapf(sdkerrors.ErrPanic, "%v", r), app.trace)
}
}()
/ when a client did not provide a query height, manually inject the latest
if req.Height == 0 {
req.Height = app.LastBlockHeight()
}
telemetry.IncrCounter(1, "query", "count")
telemetry.IncrCounter(1, "query", req.Path)
defer telemetry.MeasureSince(telemetry.Now(), req.Path)
if req.Path == QueryPathBroadcastTx {
return sdkerrors.QueryResult(errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "can't route a broadcast tx message"), app.trace), nil
}
/ handle gRPC routes first rather than calling splitPath because '/' characters
/ are used as part of gRPC paths
if grpcHandler := app.grpcQueryRouter.Route(req.Path); grpcHandler != nil {
return app.handleQueryGRPC(grpcHandler, req), nil
}
path := SplitABCIQueryPath(req.Path)
if len(path) == 0 {
return sdkerrors.QueryResult(errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "no query path provided"), app.trace), nil
}
switch path[0] {
case QueryPathApp:
/ "/app" prefix for special application queries
resp = handleQueryApp(app, path, req)
case QueryPathStore:
resp = handleQueryStore(app, path, *req)
case QueryPathP2P:
resp = handleQueryP2P(app, path)
default:
resp = sdkerrors.QueryResult(errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "unknown query path"), app.trace)
}
return resp, nil
}
/ ListSnapshots implements the ABCI interface. It delegates to app.snapshotManager if set.
func (app *BaseApp)
ListSnapshots(req *abci.RequestListSnapshots) (*abci.ResponseListSnapshots, error) {
resp := &abci.ResponseListSnapshots{
Snapshots: []*abci.Snapshot{
}}
if app.snapshotManager == nil {
return resp, nil
}
snapshots, err := app.snapshotManager.List()
if err != nil {
app.logger.Error("failed to list snapshots", "err", err)
return nil, err
}
for _, snapshot := range snapshots {
abciSnapshot, err := snapshot.ToABCI()
if err != nil {
app.logger.Error("failed to convert ABCI snapshots", "err", err)
return nil, err
}
resp.Snapshots = append(resp.Snapshots, &abciSnapshot)
}
return resp, nil
}
/ LoadSnapshotChunk implements the ABCI interface. It delegates to app.snapshotManager if set.
func (app *BaseApp)
LoadSnapshotChunk(req *abci.RequestLoadSnapshotChunk) (*abci.ResponseLoadSnapshotChunk, error) {
if app.snapshotManager == nil {
return &abci.ResponseLoadSnapshotChunk{
}, nil
}
chunk, err := app.snapshotManager.LoadChunk(req.Height, req.Format, req.Chunk)
if err != nil {
app.logger.Error(
"failed to load snapshot chunk",
"height", req.Height,
"format", req.Format,
"chunk", req.Chunk,
"err", err,
)
return nil, err
}
return &abci.ResponseLoadSnapshotChunk{
Chunk: chunk
}, nil
}
/ OfferSnapshot implements the ABCI interface. It delegates to app.snapshotManager if set.
func (app *BaseApp)
OfferSnapshot(req *abci.RequestOfferSnapshot) (*abci.ResponseOfferSnapshot, error) {
if app.snapshotManager == nil {
app.logger.Error("snapshot manager not configured")
return &abci.ResponseOfferSnapshot{
Result: abci.ResponseOfferSnapshot_ABORT
}, nil
}
if req.Snapshot == nil {
app.logger.Error("received nil snapshot")
return &abci.ResponseOfferSnapshot{
Result: abci.ResponseOfferSnapshot_REJECT
}, nil
}
snapshot, err := snapshottypes.SnapshotFromABCI(req.Snapshot)
if err != nil {
app.logger.Error("failed to decode snapshot metadata", "err", err)
return &abci.ResponseOfferSnapshot{
Result: abci.ResponseOfferSnapshot_REJECT
}, nil
}
err = app.snapshotManager.Restore(snapshot)
switch {
case err == nil:
return &abci.ResponseOfferSnapshot{
Result: abci.ResponseOfferSnapshot_ACCEPT
}, nil
case errors.Is(err, snapshottypes.ErrUnknownFormat):
return &abci.ResponseOfferSnapshot{
Result: abci.ResponseOfferSnapshot_REJECT_FORMAT
}, nil
case errors.Is(err, snapshottypes.ErrInvalidMetadata):
app.logger.Error(
"rejecting invalid snapshot",
"height", req.Snapshot.Height,
"format", req.Snapshot.Format,
"err", err,
)
return &abci.ResponseOfferSnapshot{
Result: abci.ResponseOfferSnapshot_REJECT
}, nil
default:
/ CometBFT errors are defined here: https://github.com/cometbft/cometbft/blob/main/statesync/syncer.go
/ It may happen that in case of a CometBFT error, such as a timeout (which occurs after two minutes),
/ the process is aborted. This is done intentionally because deleting the database programmatically
/ can lead to more complicated situations.
app.logger.Error(
"failed to restore snapshot",
"height", req.Snapshot.Height,
"format", req.Snapshot.Format,
"err", err,
)
/ We currently don't support resetting the IAVL stores and retrying a
/ different snapshot, so we ask CometBFT to abort all snapshot restoration.
return &abci.ResponseOfferSnapshot{
Result: abci.ResponseOfferSnapshot_ABORT
}, nil
}
}
/ ApplySnapshotChunk implements the ABCI interface. It delegates to app.snapshotManager if set.
func (app *BaseApp)
ApplySnapshotChunk(req *abci.RequestApplySnapshotChunk) (*abci.ResponseApplySnapshotChunk, error) {
if app.snapshotManager == nil {
app.logger.Error("snapshot manager not configured")
return &abci.ResponseApplySnapshotChunk{
Result: abci.ResponseApplySnapshotChunk_ABORT
}, nil
}
_, err := app.snapshotManager.RestoreChunk(req.Chunk)
switch {
case err == nil:
return &abci.ResponseApplySnapshotChunk{
Result: abci.ResponseApplySnapshotChunk_ACCEPT
}, nil
case errors.Is(err, snapshottypes.ErrChunkHashMismatch):
app.logger.Error(
"chunk checksum mismatch; rejecting sender and requesting refetch",
"chunk", req.Index,
"sender", req.Sender,
"err", err,
)
return &abci.ResponseApplySnapshotChunk{
Result: abci.ResponseApplySnapshotChunk_RETRY,
RefetchChunks: []uint32{
req.Index
},
RejectSenders: []string{
req.Sender
},
}, nil
default:
app.logger.Error("failed to restore snapshot", "err", err)
return &abci.ResponseApplySnapshotChunk{
Result: abci.ResponseApplySnapshotChunk_ABORT
}, nil
}
}
/ CheckTx implements the ABCI interface and executes a tx in CheckTx mode. In
/ CheckTx mode, messages are not executed. This means messages are only validated
/ and only the AnteHandler is executed. State is persisted to the BaseApp's
/ internal CheckTx state if the AnteHandler passes. Otherwise, the ResponseCheckTx
/ will contain relevant error information. Regardless of tx execution outcome,
/ the ResponseCheckTx will contain relevant gas execution context.
func (app *BaseApp)
CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) {
var mode execMode
switch req.Type {
case abci.CheckTxType_New:
mode = execModeCheck
case abci.CheckTxType_Recheck:
mode = execModeReCheck
default:
return nil, fmt.Errorf("unknown RequestCheckTx type: %s", req.Type)
}
if app.checkTxHandler == nil {
gInfo, result, anteEvents, err := app.runTx(mode, req.Tx, nil)
if err != nil {
return sdkerrors.ResponseCheckTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, anteEvents, app.trace), nil
}
return &abci.ResponseCheckTx{
GasWanted: int64(gInfo.GasWanted), / TODO: Should type accept unsigned ints?
GasUsed: int64(gInfo.GasUsed), / TODO: Should type accept unsigned ints?
Log: result.Log,
Data: result.Data,
Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents),
}, nil
}
/ Create wrapper to avoid users overriding the execution mode
runTx := func(txBytes []byte, tx sdk.Tx) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) {
return app.runTx(mode, txBytes, tx)
}
return app.checkTxHandler(runTx, req)
}
/ PrepareProposal implements the PrepareProposal ABCI method and returns a
/ ResponsePrepareProposal object to the client. The PrepareProposal method is
/ responsible for allowing the block proposer to perform application-dependent
/ work in a block before proposing it.
/
/ Transactions can be modified, removed, or added by the application. Since the
/ application maintains its own local mempool, it will ignore the transactions
/ provided to it in RequestPrepareProposal. Instead, it will determine which
/ transactions to return based on the mempool's semantics and the MaxTxBytes
/ provided by the client's request.
/
/ Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md
/ Ref: https://github.com/cometbft/cometbft/blob/main/spec/abci/abci%2B%2B_basic_concepts.md
func (app *BaseApp)
PrepareProposal(req *abci.RequestPrepareProposal) (resp *abci.ResponsePrepareProposal, err error) {
if app.prepareProposal == nil {
return nil, errors.New("PrepareProposal handler not set")
}
/ Always reset state given that PrepareProposal can timeout and be called
/ again in a subsequent round.
header := cmtproto.Header{
ChainID: app.chainID,
Height: req.Height,
Time: req.Time,
ProposerAddress: req.ProposerAddress,
NextValidatorsHash: req.NextValidatorsHash,
AppHash: app.LastCommitID().Hash,
}
app.setState(execModePrepareProposal, header)
/ CometBFT must never call PrepareProposal with a height of 0.
/
/ Ref: https://github.com/cometbft/cometbft/blob/059798a4f5b0c9f52aa8655fa619054a0154088c/spec/core/state.md?plain=1#L37-L38
if req.Height < 1 {
return nil, errors.New("PrepareProposal called with invalid height")
}
app.prepareProposalState.SetContext(app.getContextForProposal(app.prepareProposalState.Context(), req.Height).
WithVoteInfos(toVoteInfo(req.LocalLastCommit.Votes)). / this is a set of votes that are not finalized yet, wait for commit
WithBlockHeight(req.Height).
WithBlockTime(req.Time).
WithProposer(req.ProposerAddress).
WithExecMode(sdk.ExecModePrepareProposal).
WithCometInfo(prepareProposalInfo{
req
}).
WithHeaderInfo(coreheader.Info{
ChainID: app.chainID,
Height: req.Height,
Time: req.Time,
}))
app.prepareProposalState.SetContext(app.prepareProposalState.Context().
WithConsensusParams(app.GetConsensusParams(app.prepareProposalState.Context())).
WithBlockGasMeter(app.getBlockGasMeter(app.prepareProposalState.Context())))
defer func() {
if err := recover(); err != nil {
app.logger.Error(
"panic recovered in PrepareProposal",
"height", req.Height,
"time", req.Time,
"panic", err,
)
resp = &abci.ResponsePrepareProposal{
Txs: req.Txs
}
}
}()
resp, err = app.prepareProposal(app.prepareProposalState.Context(), req)
if err != nil {
app.logger.Error("failed to prepare proposal", "height", req.Height, "time", req.Time, "err", err)
return &abci.ResponsePrepareProposal{
Txs: req.Txs
}, nil
}
return resp, nil
}
/ ProcessProposal implements the ProcessProposal ABCI method and returns a
/ ResponseProcessProposal object to the client. The ProcessProposal method is
/ responsible for allowing execution of application-dependent work in a proposed
/ block. Note, the application defines the exact implementation details of
/ ProcessProposal. In general, the application must at the very least ensure
/ that all transactions are valid. If all transactions are valid, then we inform
/ CometBFT that the Status is ACCEPT. However, the application is also able
/ to implement optimizations such as executing the entire proposed block
/ immediately.
/
/ If a panic is detected during execution of an application's ProcessProposal
/ handler, it will be recovered and we will reject the proposal.
/
/ Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md
/ Ref: https://github.com/cometbft/cometbft/blob/main/spec/abci/abci%2B%2B_basic_concepts.md
func (app *BaseApp)
ProcessProposal(req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) {
if app.processProposal == nil {
return nil, errors.New("ProcessProposal handler not set")
}
/ CometBFT must never call ProcessProposal with a height of 0.
/ Ref: https://github.com/cometbft/cometbft/blob/059798a4f5b0c9f52aa8655fa619054a0154088c/spec/core/state.md?plain=1#L37-L38
if req.Height < 1 {
return nil, errors.New("ProcessProposal called with invalid height")
}
/ Always reset state given that ProcessProposal can timeout and be called
/ again in a subsequent round.
header := cmtproto.Header{
ChainID: app.chainID,
Height: req.Height,
Time: req.Time,
ProposerAddress: req.ProposerAddress,
NextValidatorsHash: req.NextValidatorsHash,
AppHash: app.LastCommitID().Hash,
}
app.setState(execModeProcessProposal, header)
/ Since the application can get access to FinalizeBlock state and write to it,
/ we must be sure to reset it in case ProcessProposal timeouts and is called
/ again in a subsequent round. However, we only want to do this after we've
/ processed the first block, as we want to avoid overwriting the finalizeState
/ after state changes during InitChain.
if req.Height > app.initialHeight {
/ abort any running OE
app.optimisticExec.Abort()
app.setState(execModeFinalize, header)
}
app.processProposalState.SetContext(app.getContextForProposal(app.processProposalState.Context(), req.Height).
WithVoteInfos(req.ProposedLastCommit.Votes). / this is a set of votes that are not finalized yet, wait for commit
WithBlockHeight(req.Height).
WithBlockTime(req.Time).
WithHeaderHash(req.Hash).
WithProposer(req.ProposerAddress).
WithCometInfo(cometInfo{
ProposerAddress: req.ProposerAddress,
ValidatorsHash: req.NextValidatorsHash,
Misbehavior: req.Misbehavior,
LastCommit: req.ProposedLastCommit
}).
WithExecMode(sdk.ExecModeProcessProposal).
WithHeaderInfo(coreheader.Info{
ChainID: app.chainID,
Height: req.Height,
Time: req.Time,
}))
app.processProposalState.SetContext(app.processProposalState.Context().
WithConsensusParams(app.GetConsensusParams(app.processProposalState.Context())).
WithBlockGasMeter(app.getBlockGasMeter(app.processProposalState.Context())))
defer func() {
if err := recover(); err != nil {
app.logger.Error(
"panic recovered in ProcessProposal",
"height", req.Height,
"time", req.Time,
"hash", fmt.Sprintf("%X", req.Hash),
"panic", err,
)
resp = &abci.ResponseProcessProposal{
Status: abci.ResponseProcessProposal_REJECT
}
}
}()
resp, err = app.processProposal(app.processProposalState.Context(), req)
if err != nil {
app.logger.Error("failed to process proposal", "height", req.Height, "time", req.Time, "hash", fmt.Sprintf("%X", req.Hash), "err", err)
return &abci.ResponseProcessProposal{
Status: abci.ResponseProcessProposal_REJECT
}, nil
}
/ Only execute optimistic execution if the proposal is accepted, OE is
/ enabled and the block height is greater than the initial height. During
/ the first block we'll be carrying state from InitChain, so it would be
/ impossible for us to easily revert.
/ After the first block has been processed, the next blocks will get executed
/ optimistically, so that when the ABCI client calls `FinalizeBlock` the app
/ can have a response ready.
if resp.Status == abci.ResponseProcessProposal_ACCEPT &&
app.optimisticExec.Enabled() &&
req.Height > app.initialHeight {
app.optimisticExec.Execute(req)
}
return resp, nil
}
/ ExtendVote implements the ExtendVote ABCI method and returns a ResponseExtendVote.
/ It calls the application's ExtendVote handler which is responsible for performing
/ application-specific business logic when sending a pre-commit for the NEXT
/ block height. The extensions response may be non-deterministic but must always
/ be returned, even if empty.
/
/ Agreed upon vote extensions are made available to the proposer of the next
/ height and are committed in the subsequent height, i.e. H+2. An error is
/ returned if vote extensions are not enabled or if extendVote fails or panics.
func (app *BaseApp)
ExtendVote(_ context.Context, req *abci.RequestExtendVote) (resp *abci.ResponseExtendVote, err error) {
/ Always reset state given that ExtendVote and VerifyVoteExtension can timeout
/ and be called again in a subsequent round.
var ctx sdk.Context
/ If we're extending the vote for the initial height, we need to use the
/ finalizeBlockState context, otherwise we don't get the uncommitted data
/ from InitChain.
if req.Height == app.initialHeight {
ctx, _ = app.finalizeBlockState.Context().CacheContext()
}
else {
emptyHeader := cmtproto.Header{
ChainID: app.chainID,
Height: req.Height
}
ms := app.cms.CacheMultiStore()
ctx = sdk.NewContext(ms, emptyHeader, false, app.logger).WithStreamingManager(app.streamingManager)
}
if app.extendVote == nil {
return nil, errors.New("application ExtendVote handler not set")
}
/ If vote extensions are not enabled, as a safety precaution, we return an
/ error.
cp := app.GetConsensusParams(ctx)
/ Note: In this case, we do want to extend vote if the height is equal or
/ greater than VoteExtensionsEnableHeight. This defers from the check done
/ in ValidateVoteExtensions and PrepareProposal in which we'll check for
/ vote extensions on VoteExtensionsEnableHeight+1.
extsEnabled := cp.Abci != nil && req.Height >= cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0
if !extsEnabled {
return nil, fmt.Errorf("vote extensions are not enabled; unexpected call to ExtendVote at height %d", req.Height)
}
ctx = ctx.
WithConsensusParams(cp).
WithBlockGasMeter(storetypes.NewInfiniteGasMeter()).
WithBlockHeight(req.Height).
WithHeaderHash(req.Hash).
WithExecMode(sdk.ExecModeVoteExtension).
WithHeaderInfo(coreheader.Info{
ChainID: app.chainID,
Height: req.Height,
Hash: req.Hash,
})
/ add a deferred recover handler in case extendVote panics
defer func() {
if r := recover(); r != nil {
app.logger.Error(
"panic recovered in ExtendVote",
"height", req.Height,
"hash", fmt.Sprintf("%X", req.Hash),
"panic", err,
)
err = fmt.Errorf("recovered application panic in ExtendVote: %v", r)
}
}()
resp, err = app.extendVote(ctx, req)
if err != nil {
app.logger.Error("failed to extend vote", "height", req.Height, "hash", fmt.Sprintf("%X", req.Hash), "err", err)
return &abci.ResponseExtendVote{
VoteExtension: []byte{
}}, nil
}
return resp, err
}
/ VerifyVoteExtension implements the VerifyVoteExtension ABCI method and returns
/ a ResponseVerifyVoteExtension. It calls the applications' VerifyVoteExtension
/ handler which is responsible for performing application-specific business
/ logic in verifying a vote extension from another validator during the pre-commit
/ phase. The response MUST be deterministic. An error is returned if vote
/ extensions are not enabled or if verifyVoteExt fails or panics.
func (app *BaseApp)
VerifyVoteExtension(req *abci.RequestVerifyVoteExtension) (resp *abci.ResponseVerifyVoteExtension, err error) {
if app.verifyVoteExt == nil {
return nil, errors.New("application VerifyVoteExtension handler not set")
}
var ctx sdk.Context
/ If we're verifying the vote for the initial height, we need to use the
/ finalizeBlockState context, otherwise we don't get the uncommitted data
/ from InitChain.
if req.Height == app.initialHeight {
ctx, _ = app.finalizeBlockState.Context().CacheContext()
}
else {
emptyHeader := cmtproto.Header{
ChainID: app.chainID,
Height: req.Height
}
ms := app.cms.CacheMultiStore()
ctx = sdk.NewContext(ms, emptyHeader, false, app.logger).WithStreamingManager(app.streamingManager)
}
/ If vote extensions are not enabled, as a safety precaution, we return an
/ error.
cp := app.GetConsensusParams(ctx)
/ Note: we verify votes extensions on VoteExtensionsEnableHeight+1. Check
/ comment in ExtendVote and ValidateVoteExtensions for more details.
extsEnabled := cp.Abci != nil && req.Height >= cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0
if !extsEnabled {
return nil, fmt.Errorf("vote extensions are not enabled; unexpected call to VerifyVoteExtension at height %d", req.Height)
}
/ add a deferred recover handler in case verifyVoteExt panics
defer func() {
if r := recover(); r != nil {
app.logger.Error(
"panic recovered in VerifyVoteExtension",
"height", req.Height,
"hash", fmt.Sprintf("%X", req.Hash),
"validator", fmt.Sprintf("%X", req.ValidatorAddress),
"panic", r,
)
err = fmt.Errorf("recovered application panic in VerifyVoteExtension: %v", r)
}
}()
ctx = ctx.
WithConsensusParams(cp).
WithBlockGasMeter(storetypes.NewInfiniteGasMeter()).
WithBlockHeight(req.Height).
WithHeaderHash(req.Hash).
WithExecMode(sdk.ExecModeVerifyVoteExtension).
WithHeaderInfo(coreheader.Info{
ChainID: app.chainID,
Height: req.Height,
Hash: req.Hash,
})
resp, err = app.verifyVoteExt(ctx, req)
if err != nil {
app.logger.Error("failed to verify vote extension", "height", req.Height, "err", err)
return &abci.ResponseVerifyVoteExtension{
Status: abci.ResponseVerifyVoteExtension_REJECT
}, nil
}
return resp, err
}
/ internalFinalizeBlock executes the block, called by the Optimistic
/ Execution flow or by the FinalizeBlock ABCI method. The context received is
/ only used to handle early cancellation, for anything related to state app.finalizeBlockState.Context()
/ must be used.
func (app *BaseApp)
internalFinalizeBlock(ctx context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) {
var events []abci.Event
if err := app.checkHalt(req.Height, req.Time); err != nil {
return nil, err
}
if err := app.validateFinalizeBlockHeight(req); err != nil {
return nil, err
}
if app.cms.TracingEnabled() {
app.cms.SetTracingContext(storetypes.TraceContext(
map[string]any{"blockHeight": req.Height
},
))
}
header := cmtproto.Header{
ChainID: app.chainID,
Height: req.Height,
Time: req.Time,
ProposerAddress: req.ProposerAddress,
NextValidatorsHash: req.NextValidatorsHash,
AppHash: app.LastCommitID().Hash,
}
/ finalizeBlockState should be set on InitChain or ProcessProposal. If it is
/ nil, it means we are replaying this block and we need to set the state here
/ given that during block replay ProcessProposal is not executed by CometBFT.
if app.finalizeBlockState == nil {
app.setState(execModeFinalize, header)
}
/ Context is now updated with Header information.
app.finalizeBlockState.SetContext(app.finalizeBlockState.Context().
WithBlockHeader(header).
WithHeaderHash(req.Hash).
WithHeaderInfo(coreheader.Info{
ChainID: app.chainID,
Height: req.Height,
Time: req.Time,
Hash: req.Hash,
AppHash: app.LastCommitID().Hash,
}).
WithConsensusParams(app.GetConsensusParams(app.finalizeBlockState.Context())).
WithVoteInfos(req.DecidedLastCommit.Votes).
WithExecMode(sdk.ExecModeFinalize).
WithCometInfo(cometInfo{
Misbehavior: req.Misbehavior,
ValidatorsHash: req.NextValidatorsHash,
ProposerAddress: req.ProposerAddress,
LastCommit: req.DecidedLastCommit,
}))
/ GasMeter must be set after we get a context with updated consensus params.
gasMeter := app.getBlockGasMeter(app.finalizeBlockState.Context())
app.finalizeBlockState.SetContext(app.finalizeBlockState.Context().WithBlockGasMeter(gasMeter))
if app.checkState != nil {
app.checkState.SetContext(app.checkState.Context().
WithBlockGasMeter(gasMeter).
WithHeaderHash(req.Hash))
}
preblockEvents, err := app.preBlock(req)
if err != nil {
return nil, err
}
events = append(events, preblockEvents...)
beginBlock, err := app.beginBlock(req)
if err != nil {
return nil, err
}
/ First check for an abort signal after beginBlock, as it's the first place
/ we spend any significant amount of time.
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
/ continue
}
events = append(events, beginBlock.Events...)
/ Reset the gas meter so that the AnteHandlers aren't required to
gasMeter = app.getBlockGasMeter(app.finalizeBlockState.Context())
app.finalizeBlockState.SetContext(app.finalizeBlockState.Context().WithBlockGasMeter(gasMeter))
/ Iterate over all raw transactions in the proposal and attempt to execute
/ them, gathering the execution results.
/
/ NOTE: Not all raw transactions may adhere to the sdk.Tx interface, e.g.
/ vote extensions, so skip those.
txResults := make([]*abci.ExecTxResult, 0, len(req.Txs))
for _, rawTx := range req.Txs {
var response *abci.ExecTxResult
if _, err := app.txDecoder(rawTx); err == nil {
response = app.deliverTx(rawTx)
}
else {
/ In the case where a transaction included in a block proposal is malformed,
/ we still want to return a default response to comet. This is because comet
/ expects a response for each transaction included in a block proposal.
response = sdkerrors.ResponseExecTxResultWithEvents(
sdkerrors.ErrTxDecode,
0,
0,
nil,
false,
)
}
/ check after every tx if we should abort
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
/ continue
}
txResults = append(txResults, response)
}
if app.finalizeBlockState.ms.TracingEnabled() {
app.finalizeBlockState.ms = app.finalizeBlockState.ms.SetTracingContext(nil).(storetypes.CacheMultiStore)
}
endBlock, err := app.endBlock(app.finalizeBlockState.Context())
if err != nil {
return nil, err
}
/ check after endBlock if we should abort, to avoid propagating the result
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
/ continue
}
events = append(events, endBlock.Events...)
cp := app.GetConsensusParams(app.finalizeBlockState.Context())
return &abci.ResponseFinalizeBlock{
Events: events,
TxResults: txResults,
ValidatorUpdates: endBlock.ValidatorUpdates,
ConsensusParamUpdates: &cp,
}, nil
}
/ FinalizeBlock will execute the block proposal provided by RequestFinalizeBlock.
/ Specifically, it will execute an application's BeginBlock (if defined), followed
/ by the transactions in the proposal, finally followed by the application's
/ EndBlock (if defined).
/
/ For each raw transaction, i.e. a byte slice, BaseApp will only execute it if
/ it adheres to the sdk.Tx interface. Otherwise, the raw transaction will be
/ skipped. This is to support compatibility with proposers injecting vote
/ extensions into the proposal, which should not themselves be executed in cases
/ where they adhere to the sdk.Tx interface.
func (app *BaseApp)
FinalizeBlock(req *abci.RequestFinalizeBlock) (res *abci.ResponseFinalizeBlock, err error) {
defer func() {
if res == nil {
return
}
/ call the streaming service hooks with the FinalizeBlock messages
for _, streamingListener := range app.streamingManager.ABCIListeners {
if err := streamingListener.ListenFinalizeBlock(app.finalizeBlockState.Context(), *req, *res); err != nil {
app.logger.Error("ListenFinalizeBlock listening hook failed", "height", req.Height, "err", err)
}
}
}()
if app.optimisticExec.Initialized() {
/ check if the hash we got is the same as the one we are executing
aborted := app.optimisticExec.AbortIfNeeded(req.Hash)
/ Wait for the OE to finish, regardless of whether it was aborted or not
res, err = app.optimisticExec.WaitResult()
/ only return if we are not aborting
if !aborted {
if res != nil {
res.AppHash = app.workingHash()
}
return res, err
}
/ if it was aborted, we need to reset the state
app.finalizeBlockState = nil
app.optimisticExec.Reset()
}
/ if no OE is running, just run the block (this is either a block replay or a OE that got aborted)
res, err = app.internalFinalizeBlock(context.Background(), req)
if res != nil {
res.AppHash = app.workingHash()
}
return res, err
}
/ checkHalt checkes if height or time exceeds halt-height or halt-time respectively.
func (app *BaseApp)
checkHalt(height int64, time time.Time)
error {
var halt bool
switch {
case app.haltHeight > 0 && uint64(height) >= app.haltHeight:
halt = true
case app.haltTime > 0 && time.Unix() >= int64(app.haltTime):
halt = true
}
if halt {
return fmt.Errorf("halt per configuration height %d time %d", app.haltHeight, app.haltTime)
}
return nil
}
/ Commit implements the ABCI interface. It will commit all state that exists in
/ the deliver state's multi-store and includes the resulting commit ID in the
/ returned abci.ResponseCommit. Commit will set the check state based on the
/ latest header and reset the deliver state. Also, if a non-zero halt height is
/ defined in config, Commit will execute a deferred function call to check
/ against that height and gracefully halt if it matches the latest committed
/ height.
func (app *BaseApp)
Commit() (*abci.ResponseCommit, error) {
header := app.finalizeBlockState.Context().BlockHeader()
retainHeight := app.GetBlockRetentionHeight(header.Height)
if app.precommiter != nil {
app.precommiter(app.finalizeBlockState.Context())
}
rms, ok := app.cms.(*rootmulti.Store)
if ok {
rms.SetCommitHeader(header)
}
app.cms.Commit()
resp := &abci.ResponseCommit{
RetainHeight: retainHeight,
}
abciListeners := app.streamingManager.ABCIListeners
if len(abciListeners) > 0 {
ctx := app.finalizeBlockState.Context()
blockHeight := ctx.BlockHeight()
changeSet := app.cms.PopStateCache()
for _, abciListener := range abciListeners {
if err := abciListener.ListenCommit(ctx, *resp, changeSet); err != nil {
app.logger.Error("Commit listening hook failed", "height", blockHeight, "err", err)
}
}
}
/ Reset the CheckTx state to the latest committed.
/
/ NOTE: This is safe because CometBFT holds a lock on the mempool for
/ Commit. Use the header from this latest block.
app.setState(execModeCheck, header)
app.finalizeBlockState = nil
if app.prepareCheckStater != nil {
app.prepareCheckStater(app.checkState.Context())
}
/ The SnapshotIfApplicable method will create the snapshot by starting the goroutine
app.snapshotManager.SnapshotIfApplicable(header.Height)
return resp, nil
}
/ workingHash gets the apphash that will be finalized in commit.
/ These writes will be persisted to the root multi-store (app.cms)
and flushed to
/ disk in the Commit phase. This means when the ABCI client requests Commit(), the application
/ state transitions will be flushed to disk and as a result, but we already have
/ an application Merkle root.
func (app *BaseApp)
workingHash() []byte {
/ Write the FinalizeBlock state into branched storage and commit the MultiStore.
/ The write to the FinalizeBlock state writes all state transitions to the root
/ MultiStore (app.cms)
so when Commit()
is called it persists those values.
app.finalizeBlockState.ms.Write()
/ Get the hash of all writes in order to return the apphash to the comet in finalizeBlock.
commitHash := app.cms.WorkingHash()
app.logger.Debug("hash of all writes", "workingHash", fmt.Sprintf("%X", commitHash))
return commitHash
}
func handleQueryApp(app *BaseApp, path []string, req *abci.RequestQuery) *abci.ResponseQuery {
if len(path) >= 2 {
switch path[1] {
case "simulate":
txBytes := req.Data
gInfo, res, err := app.Simulate(txBytes)
if err != nil {
return sdkerrors.QueryResult(errorsmod.Wrap(err, "failed to simulate tx"), app.trace)
}
simRes := &sdk.SimulationResponse{
GasInfo: gInfo,
Result: res,
}
bz, err := codec.ProtoMarshalJSON(simRes, app.interfaceRegistry)
if err != nil {
return sdkerrors.QueryResult(errorsmod.Wrap(err, "failed to JSON encode simulation response"), app.trace)
}
return &abci.ResponseQuery{
Codespace: sdkerrors.RootCodespace,
Height: req.Height,
Value: bz,
}
case "version":
return &abci.ResponseQuery{
Codespace: sdkerrors.RootCodespace,
Height: req.Height,
Value: []byte(app.version),
}
default:
return sdkerrors.QueryResult(errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unknown query: %s", path), app.trace)
}
}
return sdkerrors.QueryResult(
errorsmod.Wrap(
sdkerrors.ErrUnknownRequest,
"expected second parameter to be either 'simulate' or 'version', neither was present",
), app.trace)
}
func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) *abci.ResponseQuery {
/ "/store" prefix for store queries
queryable, ok := app.cms.(storetypes.Queryable)
if !ok {
return sdkerrors.QueryResult(errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "multi-store does not support queries"), app.trace)
}
req.Path = "/" + strings.Join(path[1:], "/")
if req.Height <= 1 && req.Prove {
return sdkerrors.QueryResult(
errorsmod.Wrap(
sdkerrors.ErrInvalidRequest,
"cannot query with proof when height <= 1; please provide a valid height",
), app.trace)
}
sdkReq := storetypes.RequestQuery(req)
resp, err := queryable.Query(&sdkReq)
if err != nil {
return sdkerrors.QueryResult(err, app.trace)
}
resp.Height = req.Height
abciResp := abci.ResponseQuery(*resp)
return &abciResp
}
func handleQueryP2P(app *BaseApp, path []string) *abci.ResponseQuery {
/ "/p2p" prefix for p2p queries
if len(path) < 4 {
return sdkerrors.QueryResult(errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "path should be p2p filter <addr|id> <parameter>"), app.trace)
}
var resp *abci.ResponseQuery
cmd, typ, arg := path[1], path[2], path[3]
switch cmd {
case "filter":
switch typ {
case "addr":
resp = app.FilterPeerByAddrPort(arg)
case "id":
resp = app.FilterPeerByID(arg)
}
default:
resp = sdkerrors.QueryResult(errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "expected second parameter to be 'filter'"), app.trace)
}
return resp
}
/ SplitABCIQueryPath splits a string path using the delimiter '/'.
/
/ e.g. "this/is/funny" becomes []string{"this", "is", "funny"
}
func SplitABCIQueryPath(requestPath string) (path []string) {
path = strings.Split(requestPath, "/")
/ first element is empty string
if len(path) > 0 && path[0] == "" {
path = path[1:]
}
return path
}
/ FilterPeerByAddrPort filters peers by address/port.
func (app *BaseApp)
FilterPeerByAddrPort(info string) *abci.ResponseQuery {
if app.addrPeerFilter != nil {
return app.addrPeerFilter(info)
}
return &abci.ResponseQuery{
}
}
/ FilterPeerByID filters peers by node ID.
func (app *BaseApp)
FilterPeerByID(info string) *abci.ResponseQuery {
if app.idPeerFilter != nil {
return app.idPeerFilter(info)
}
return &abci.ResponseQuery{
}
}
/ getContextForProposal returns the correct Context for PrepareProposal and
/ ProcessProposal. We use finalizeBlockState on the first block to be able to
/ access any state changes made in InitChain.
func (app *BaseApp)
getContextForProposal(ctx sdk.Context, height int64)
sdk.Context {
if height == app.initialHeight {
ctx, _ = app.finalizeBlockState.Context().CacheContext()
/ clear all context data set during InitChain to avoid inconsistent behavior
ctx = ctx.WithBlockHeader(cmtproto.Header{
}).WithHeaderInfo(coreheader.Info{
})
return ctx
}
return ctx
}
func (app *BaseApp)
handleQueryGRPC(handler GRPCQueryHandler, req *abci.RequestQuery) *abci.ResponseQuery {
ctx, err := app.CreateQueryContext(req.Height, req.Prove)
if err != nil {
return sdkerrors.QueryResult(err, app.trace)
}
resp, err := handler(ctx, req)
if err != nil {
resp = sdkerrors.QueryResult(gRPCErrorToSDKError(err), app.trace)
resp.Height = req.Height
return resp
}
return resp
}
func gRPCErrorToSDKError(err error)
error {
status, ok := grpcstatus.FromError(err)
if !ok {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, err.Error())
}
switch status.Code() {
case codes.NotFound:
return errorsmod.Wrap(sdkerrors.ErrKeyNotFound, err.Error())
case codes.InvalidArgument:
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, err.Error())
case codes.FailedPrecondition:
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, err.Error())
case codes.Unauthenticated:
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, err.Error())
default:
return errorsmod.Wrap(sdkerrors.ErrUnknownRequest, err.Error())
}
}
func checkNegativeHeight(height int64)
error {
if height < 0 {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "cannot query with height < 0; please provide a valid height")
}
return nil
}
/ CreateQueryContext creates a new sdk.Context for a query, taking as args
/ the block height and whether the query needs a proof or not.
func (app *BaseApp)
CreateQueryContext(height int64, prove bool) (sdk.Context, error) {
return app.CreateQueryContextWithCheckHeader(height, prove, true)
}
/ CreateQueryContextWithCheckHeader creates a new sdk.Context for a query, taking as args
/ the block height, whether the query needs a proof or not, and whether to check the header or not.
func (app *BaseApp)
CreateQueryContextWithCheckHeader(height int64, prove, checkHeader bool) (sdk.Context, error) {
if err := checkNegativeHeight(height); err != nil {
return sdk.Context{
}, err
}
/ use custom query multi-store if provided
qms := app.qms
if qms == nil {
qms = app.cms.(storetypes.MultiStore)
}
lastBlockHeight := qms.LatestVersion()
if lastBlockHeight == 0 {
return sdk.Context{
}, errorsmod.Wrapf(sdkerrors.ErrInvalidHeight, "%s is not ready; please wait for first block", app.Name())
}
if height > lastBlockHeight {
return sdk.Context{
},
errorsmod.Wrap(
sdkerrors.ErrInvalidHeight,
"cannot query with height in the future; please provide a valid height",
)
}
if height == 1 && prove {
return sdk.Context{
},
errorsmod.Wrap(
sdkerrors.ErrInvalidRequest,
"cannot query with proof when height <= 1; please provide a valid height",
)
}
var header *cmtproto.Header
isLatest := height == 0
for _, state := range []*state{
app.checkState,
app.finalizeBlockState,
} {
if state != nil {
/ branch the commit multi-store for safety
h := state.Context().BlockHeader()
if isLatest {
lastBlockHeight = qms.LatestVersion()
}
if !checkHeader || !isLatest || isLatest && h.Height == lastBlockHeight {
header = &h
break
}
}
}
if header == nil {
return sdk.Context{
},
errorsmod.Wrapf(
sdkerrors.ErrInvalidHeight,
"context did not contain latest block height in either check state or finalize block state (%d)", lastBlockHeight,
)
}
/ when a client did not provide a query height, manually inject the latest
if isLatest {
height = lastBlockHeight
}
cacheMS, err := qms.CacheMultiStoreWithVersion(height)
if err != nil {
return sdk.Context{
},
errorsmod.Wrapf(
sdkerrors.ErrNotFound,
"failed to load state at height %d; %s (latest height: %d)", height, err, lastBlockHeight,
)
}
/ branch the commit multi-store for safety
ctx := sdk.NewContext(cacheMS, *header, true, app.logger).
WithMinGasPrices(app.minGasPrices).
WithGasMeter(storetypes.NewGasMeter(app.queryGasLimit)).
WithBlockHeader(*header).
WithBlockHeight(height)
if !isLatest {
rms, ok := app.cms.(*rootmulti.Store)
if ok {
cInfo, err := rms.GetCommitInfo(height)
if cInfo != nil && err == nil {
ctx = ctx.WithBlockHeight(height).WithBlockTime(cInfo.Timestamp)
}
}
}
return ctx, nil
}
/ GetBlockRetentionHeight returns the height for which all blocks below this height
/ are pruned from CometBFT. Given a commitment height and a non-zero local
/ minRetainBlocks configuration, the retentionHeight is the smallest height that
/ satisfies:
/
/ - Unbonding (safety threshold)
time: The block interval in which validators
/ can be economically punished for misbehavior. Blocks in this interval must be
/ auditable e.g. by the light client.
/
/ - Logical store snapshot interval: The block interval at which the underlying
/ logical store database is persisted to disk, e.g. every 10000 heights. Blocks
/ since the last IAVL snapshot must be available for replay on application restart.
/
/ - State sync snapshots: Blocks since the oldest available snapshot must be
/ available for state sync nodes to catch up (oldest because a node may be
/ restoring an old snapshot while a new snapshot was taken).
/
/ - Local (minRetainBlocks)
config: Archive nodes may want to retain more or
/ all blocks, e.g. via a local config option min-retain-blocks. There may also
/ be a need to vary retention for other nodes, e.g. sentry nodes which do not
/ need historical blocks.
func (app *BaseApp)
GetBlockRetentionHeight(commitHeight int64)
int64 {
/ If minRetainBlocks is zero, pruning is disabled and we return 0
/ If commitHeight is less than or equal to minRetainBlocks, return 0 since there are not enough
/ blocks to trigger pruning yet. This ensures we keep all blocks until we have at least minRetainBlocks.
retentionBlockWindow := commitHeight - int64(app.minRetainBlocks)
if app.minRetainBlocks == 0 || retentionBlockWindow <= 0 {
return 0
}
minNonZero := func(x, y int64)
int64 {
switch {
case x == 0:
return y
case y == 0:
return x
case x < y:
return x
default:
return y
}
}
/ Define retentionHeight as the minimum value that satisfies all non-zero
/ constraints. All blocks below (commitHeight-retentionHeight)
are pruned
/ from CometBFT.
var retentionHeight int64
/ Define the number of blocks needed to protect against misbehaving validators
/ which allows light clients to operate safely. Note, we piggy back of the
/ evidence parameters instead of computing an estimated number of blocks based
/ on the unbonding period and block commitment time as the two should be
/ equivalent.
cp := app.GetConsensusParams(app.finalizeBlockState.Context())
if cp.Evidence != nil && cp.Evidence.MaxAgeNumBlocks > 0 {
retentionHeight = commitHeight - cp.Evidence.MaxAgeNumBlocks
}
if app.snapshotManager != nil {
snapshotRetentionHeights := app.snapshotManager.GetSnapshotBlockRetentionHeights()
if snapshotRetentionHeights > 0 {
retentionHeight = minNonZero(retentionHeight, commitHeight-snapshotRetentionHeights)
}
}
retentionHeight = minNonZero(retentionHeight, retentionBlockWindow)
if retentionHeight <= 0 {
/ prune nothing in the case of a non-positive height
return 0
}
return retentionHeight
}
/ toVoteInfo converts the new ExtendedVoteInfo to VoteInfo.
func toVoteInfo(votes []abci.ExtendedVoteInfo) []abci.VoteInfo {
legacyVotes := make([]abci.VoteInfo, len(votes))
for i, vote := range votes {
legacyVotes[i] = abci.VoteInfo{
Validator: abci.Validator{
Address: vote.Validator.Address,
Power: vote.Validator.Power,
},
BlockIdFlag: vote.BlockIdFlag,
}
}
return legacyVotes
}
PreBlock
- Run the application’s
preBlocker()
, which mainly runs thePreBlocker()
method of each of the modules.
BeginBlock
-
Initialize
finalizeBlockState
with the latest header using thereq abci.FinalizeBlockRequest
passed as parameter via thesetState
function.This function also resets the main gas meter.CopyAsk AIpackage baseapp import ( "context" "fmt" "maps" "math" "slices" "strconv" "sync" "github.com/cockroachdb/errors" abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/crypto/tmhash" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/gogoproto/proto" protov2 "google.golang.org/protobuf/proto" "cosmossdk.io/core/header" errorsmod "cosmossdk.io/errors" "cosmossdk.io/log" "cosmossdk.io/store" storemetrics "cosmossdk.io/store/metrics" "cosmossdk.io/store/snapshots" storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/baseapp/oe" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/mempool" "github.com/cosmos/cosmos-sdk/types/msgservice" ) type ( execMode 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 storetypes.CommitMultiStore) error ) const ( execModeCheck execMode = iota / Check a transaction execModeReCheck / Recheck a (pending) transaction after a commit execModeSimulate / Simulate a transaction execModePrepareProposal / Prepare a block proposal execModeProcessProposal / Process a block proposal execModeVoteExtension / Extend or verify a pre-commit vote execModeVerifyVoteExtension / Verify a vote extension execModeFinalize / Finalize a block proposal ) var _ servertypes.ABCI = (*BaseApp)(nil) / BaseApp reflects the ABCI application implementation. type BaseApp struct { / initialized on creation mu sync.Mutex / mu protects the fields below. logger log.Logger name string / application name from abci.BlockInfo db dbm.DB / common DB backend cms storetypes.CommitMultiStore / Main (uncached) state qms storetypes.MultiStore / Optional alternative multistore for querying only. storeLoader StoreLoader / function to handle store loading, may be overridden with SetStoreLoader() grpcQueryRouter *GRPCQueryRouter / router for redirecting gRPC query calls msgServiceRouter *MsgServiceRouter / router for redirecting Msg service messages interfaceRegistry codectypes.InterfaceRegistry txDecoder sdk.TxDecoder / unmarshal []byte into sdk.Tx txEncoder sdk.TxEncoder / marshal sdk.Tx into []byte mempool mempool.Mempool / application side mempool anteHandler sdk.AnteHandler / ante handler for fee and auth postHandler sdk.PostHandler / post handler, optional checkTxHandler sdk.CheckTxHandler / ABCI CheckTx handler initChainer sdk.InitChainer / ABCI InitChain handler preBlocker sdk.PreBlocker / logic to run before BeginBlocker beginBlocker sdk.BeginBlocker / (legacy ABCI) BeginBlock handler endBlocker sdk.EndBlocker / (legacy ABCI) EndBlock handler processProposal sdk.ProcessProposalHandler / ABCI ProcessProposal handler prepareProposal sdk.PrepareProposalHandler / ABCI PrepareProposal extendVote sdk.ExtendVoteHandler / ABCI ExtendVote handler verifyVoteExt sdk.VerifyVoteExtensionHandler / ABCI VerifyVoteExtension handler prepareCheckStater sdk.PrepareCheckStater / logic to run during commit using the checkState precommiter sdk.Precommiter / logic to run during commit using the deliverState 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. sigverifyTx bool / in the simulation test, since the account does not have a private key, we have to ignore the tx sigverify. / manages snapshots, i.e. dumps of app state at certain intervals snapshotManager *snapshots.Manager / volatile states: / / - checkState is set on InitChain and reset on Commit / - finalizeBlockState is set on InitChain and FinalizeBlock and set to nil / on Commit. / / - checkState: Used for CheckTx, which is set based on the previous block's / state. This state is never committed. / / - prepareProposalState: Used for PrepareProposal, which is set based on the / previous block's state. This state is never committed. In case of multiple / consensus rounds, the state is always reset to the previous block's state. / / - processProposalState: Used for ProcessProposal, which is set based on the / the previous block's state. This state is never committed. In case of / multiple rounds, the state is always reset to the previous block's state. / / - finalizeBlockState: Used for FinalizeBlock, which is set based on the / previous block's state. This state is committed. checkState *state prepareProposalState *state processProposalState *state finalizeBlockState *state / An inter-block write-through cache provided to the context during the ABCI / FinalizeBlock call. interBlockCache storetypes.MultiStorePersistentCache / paramStore is used to query for ABCI consensus parameters from an / application parameter store. paramStore ParamStore / queryGasLimit defines the maximum gas for queries; unbounded if 0. queryGasLimit uint64 / 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 / initialHeight is the initial height at which we start the BaseApp initialHeight int64 / 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 / minRetainBlocks defines the minimum block height offset from the current / block being committed, such that all blocks past this offset are pruned / from CometBFT. It is used as part of the process of determining the / ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates / that no blocks should be pruned. / / Note: CometBFT block pruning is dependant on this parameter in conjunction / with the unbonding (safety threshold) period, state pruning and state sync / snapshot parameters to determine the correct minimum value of / ResponseCommit.RetainHeight. minRetainBlocks uint64 / application's version string version string / application's protocol version that increments on every upgrade / if BaseApp is passed to the upgrade keeper's NewKeeper method. appVersion uint64 / recovery handler for app.runTx method runTxRecoveryMiddleware recoveryMiddleware / trace set will return full stack traces for errors in ABCI Log field trace bool / indexEvents defines the set of events in the form { eventType }.{ attributeKey }, / which informs CometBFT what to index. If empty, all events will be indexed. indexEvents map[string]struct{ } / streamingManager for managing instances and configuration of ABCIListener services streamingManager storetypes.StreamingManager chainID string cdc codec.Codec / optimisticExec contains the context required for Optimistic Execution, / including the goroutine handling.This is experimental and must be enabled / by developers. optimisticExec *oe.OptimisticExecution / disableBlockGasMeter will disable the block gas meter if true, block gas meter is tricky to support / when executing transactions in parallel. / when disabled, the block gas meter in context is a noop one. / / SAFETY: it's safe to do if validators validate the total gas wanted in the `ProcessProposal`, which is the case in the default handler. disableBlockGasMeter bool } / 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. func NewBaseApp( name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp), ) *BaseApp { app := &BaseApp{ logger: logger.With(log.ModuleKey, "baseapp"), name: name, db: db, cms: store.NewCommitMultiStore(db, logger, storemetrics.NewNoOpMetrics()), / by default we use a no-op metric gather in store storeLoader: DefaultStoreLoader, grpcQueryRouter: NewGRPCQueryRouter(), msgServiceRouter: NewMsgServiceRouter(), txDecoder: txDecoder, fauxMerkleMode: false, sigverifyTx: true, queryGasLimit: math.MaxUint64, } for _, option := range options { option(app) } if app.mempool == nil { app.SetMempool(mempool.NoOpMempool{ }) } abciProposalHandler := NewDefaultProposalHandler(app.mempool, app) if app.prepareProposal == nil { app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler()) } if app.processProposal == nil { app.SetProcessProposal(abciProposalHandler.ProcessProposalHandler()) } if app.extendVote == nil { app.SetExtendVoteHandler(NoOpExtendVote()) } if app.verifyVoteExt == nil { app.SetVerifyVoteExtensionHandler(NoOpVerifyVoteExtensionHandler()) } if app.interBlockCache != nil { app.cms.SetInterBlockCache(app.interBlockCache) } app.runTxRecoveryMiddleware = newDefaultRecoveryMiddleware() / Initialize with an empty interface registry to avoid nil pointer dereference. / Unless SetInterfaceRegistry is called with an interface registry with proper address codecs baseapp will panic. app.cdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) protoFiles, err := proto.MergedRegistry() if err != nil { logger.Warn("error creating merged proto registry", "error", err) } else { err = msgservice.ValidateProtoAnnotations(protoFiles) if err != nil { / Once we switch to using protoreflect-based antehandlers, we might / want to panic here instead of logging a warning. logger.Warn("error validating merged proto registry annotations", "error", err) } } return app } / Name returns the name of the BaseApp. func (app *BaseApp) Name() string { return app.name } / AppVersion returns the application's protocol version. func (app *BaseApp) AppVersion() uint64 { return app.appVersion } / Version returns the application's version string. func (app *BaseApp) Version() string { return app.version } / Logger returns the logger of the BaseApp. func (app *BaseApp) Logger() log.Logger { return app.logger } / Trace returns the boolean value for logging error stack traces. func (app *BaseApp) Trace() bool { return app.trace } / MsgServiceRouter returns the MsgServiceRouter of a BaseApp. func (app *BaseApp) MsgServiceRouter() *MsgServiceRouter { return app.msgServiceRouter } / GRPCQueryRouter returns the GRPCQueryRouter of a BaseApp. func (app *BaseApp) GRPCQueryRouter() *GRPCQueryRouter { return app.grpcQueryRouter } / MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp / multistore. func (app *BaseApp) MountStores(keys ...storetypes.StoreKey) { for _, key := range keys { switch key.(type) { case *storetypes.KVStoreKey: if !app.fauxMerkleMode { app.MountStore(key, storetypes.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, storetypes.StoreTypeDB) } case *storetypes.TransientStoreKey: app.MountStore(key, storetypes.StoreTypeTransient) case *storetypes.MemoryStoreKey: app.MountStore(key, storetypes.StoreTypeMemory) default: panic(fmt.Sprintf("Unrecognized store key type :%T", key)) } } } / MountKVStores mounts all IAVL or DB stores to the provided keys in the / BaseApp multistore. func (app *BaseApp) MountKVStores(keys map[string]*storetypes.KVStoreKey) { for _, key := range keys { if !app.fauxMerkleMode { app.MountStore(key, storetypes.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, storetypes.StoreTypeDB) } } } / MountTransientStores mounts all transient stores to the provided keys in / the BaseApp multistore. func (app *BaseApp) MountTransientStores(keys map[string]*storetypes.TransientStoreKey) { for _, key := range keys { app.MountStore(key, storetypes.StoreTypeTransient) } } / MountMemoryStores mounts all in-memory KVStores with the BaseApp's internal / commit multi-store. func (app *BaseApp) MountMemoryStores(keys map[string]*storetypes.MemoryStoreKey) { skeys := slices.Sorted(maps.Keys(keys)) for _, key := range skeys { memKey := keys[key] app.MountStore(memKey, storetypes.StoreTypeMemory) } } / MountStore mounts a store to the provided key in the BaseApp multistore, / using the default DB. func (app *BaseApp) MountStore(key storetypes.StoreKey, typ storetypes.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() error { err := app.storeLoader(app.cms) if err != nil { return fmt.Errorf("failed to load latest version: %w", err) } return app.Init() } / DefaultStoreLoader will be used by default and loads the latest version func DefaultStoreLoader(ms storetypes.CommitMultiStore) error { return ms.LoadLatestVersion() } / CommitMultiStore returns the root multi-store. / App constructor can use this to access the `cms`. / UNSAFE: must not be used during the abci life cycle. func (app *BaseApp) CommitMultiStore() storetypes.CommitMultiStore { return app.cms } / SnapshotManager returns the snapshot manager. / application use this to register extra extension snapshotters. func (app *BaseApp) SnapshotManager() *snapshots.Manager { return app.snapshotManager } / LoadVersion loads the BaseApp application version. It will panic if called / more than once on a running baseapp. func (app *BaseApp) LoadVersion(version int64) error { app.logger.Info("NOTICE: this could take a long time to migrate IAVL store to fastnode if you enable Fast Node.\n") err := app.cms.LoadVersion(version) if err != nil { return fmt.Errorf("failed to load version %d: %w", version, err) } return app.Init() } / LastCommitID returns the last CommitID of the multistore. func (app *BaseApp) LastCommitID() storetypes.CommitID { return app.cms.LastCommitID() } / LastBlockHeight returns the last committed block height. func (app *BaseApp) LastBlockHeight() int64 { return app.cms.LastCommitID().Version } / ChainID returns the chainID of the app. func (app *BaseApp) ChainID() string { return app.chainID } / AnteHandler returns the AnteHandler of the app. func (app *BaseApp) AnteHandler() sdk.AnteHandler { return app.anteHandler } / Mempool returns the Mempool of the app. func (app *BaseApp) Mempool() mempool.Mempool { return app.mempool } / Init initializes the app. It seals the app, preventing any / further modifications. In addition, it validates the app against / the earlier provided settings. Returns an error if validation fails. / nil otherwise. Panics if the app is already sealed. func (app *BaseApp) Init() error { if app.sealed { panic("cannot call initFromMainStore: baseapp already sealed") } if app.cms == nil { return errors.New("commit multi-store must not be nil") } emptyHeader := cmtproto.Header{ ChainID: app.chainID } / needed for the export command which inits from store but never calls initchain app.setState(execModeCheck, emptyHeader) app.Seal() return app.cms.GetPruning().Validate() } 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) setMinRetainBlocks(minRetainBlocks uint64) { app.minRetainBlocks = minRetainBlocks } func (app *BaseApp) setInterBlockCache(cache storetypes.MultiStorePersistentCache) { app.interBlockCache = cache } func (app *BaseApp) setTrace(trace bool) { app.trace = trace } func (app *BaseApp) setIndexEvents(ie []string) { app.indexEvents = make(map[string]struct{ }) for _, e := range ie { app.indexEvents[e] = struct{ }{ } } } / 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 } / setState sets the BaseApp's state for the corresponding mode with a branched / multi-store (i.e. a CacheMultiStore) and a new Context with the same / multi-store branch, and provided header. func (app *BaseApp) setState(mode execMode, h cmtproto.Header) { ms := app.cms.CacheMultiStore() headerInfo := header.Info{ Height: h.Height, Time: h.Time, ChainID: h.ChainID, AppHash: h.AppHash, } baseState := &state{ ms: ms, ctx: sdk.NewContext(ms, h, false, app.logger). WithStreamingManager(app.streamingManager). WithHeaderInfo(headerInfo), } switch mode { case execModeCheck: baseState.SetContext(baseState.Context().WithIsCheckTx(true).WithMinGasPrices(app.minGasPrices)) app.checkState = baseState case execModePrepareProposal: app.prepareProposalState = baseState case execModeProcessProposal: app.processProposalState = baseState case execModeFinalize: app.finalizeBlockState = baseState default: panic(fmt.Sprintf("invalid runTxMode for setState: %d", mode)) } } / SetCircuitBreaker sets the circuit breaker for the BaseApp. / The circuit breaker is checked on every message execution to verify if a transaction should be executed or not. func (app *BaseApp) SetCircuitBreaker(cb CircuitBreaker) { if app.msgServiceRouter == nil { panic("cannot set circuit breaker with no msg service router set") } app.msgServiceRouter.SetCircuit(cb) } / GetConsensusParams returns the current consensus parameters from the BaseApp's / ParamStore. If the BaseApp has no ParamStore defined, nil is returned. func (app *BaseApp) GetConsensusParams(ctx sdk.Context) cmtproto.ConsensusParams { if app.paramStore == nil { return cmtproto.ConsensusParams{ } } cp, err := app.paramStore.Get(ctx) if err != nil { / This could happen while migrating from v0.45/v0.46 to v0.50, we should / allow it to happen so during preblock the upgrade plan can be executed / and the consensus params set for the first time in the new format. app.logger.Error("failed to get consensus params", "err", err) return cmtproto.ConsensusParams{ } } return cp } / StoreConsensusParams sets the consensus parameters to the BaseApp's param / store. / / NOTE: We're explicitly not storing the CometBFT app_version in the param store. / It's stored instead in the x/upgrade store, with its own bump logic. func (app *BaseApp) StoreConsensusParams(ctx sdk.Context, cp cmtproto.ConsensusParams) error { if app.paramStore == nil { return errors.New("cannot store consensus params with no params store set") } return app.paramStore.Set(ctx, cp) } / AddRunTxRecoveryHandler adds custom app.runTx method panic handlers. func (app *BaseApp) AddRunTxRecoveryHandler(handlers ...RecoveryHandler) { for _, h := range handlers { app.runTxRecoveryMiddleware = newRecoveryMiddleware(h, app.runTxRecoveryMiddleware) } } / 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(ctx sdk.Context) uint64 { cp := app.GetConsensusParams(ctx) if cp.Block == nil { return 0 } maxGas := cp.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) validateFinalizeBlockHeight(req *abci.RequestFinalizeBlock) error { if req.Height < 1 { return fmt.Errorf("invalid height: %d", req.Height) } lastBlockHeight := app.LastBlockHeight() / expectedHeight holds the expected height to validate var expectedHeight int64 if lastBlockHeight == 0 && app.initialHeight > 1 { / In this case, we're validating the first block of the chain, i.e no / previous commit. The height we're expecting is the initial height. expectedHeight = app.initialHeight } else { / This case can mean two things: / / - Either there was already a previous commit in the store, in which / case we increment the version from there. / - Or there was no previous commit, in which case we start at version 1. expectedHeight = lastBlockHeight + 1 } if req.Height != expectedHeight { return fmt.Errorf("invalid height: %d; expected: %d", req.Height, expectedHeight) } return nil } / validateBasicTxMsgs executes basic validator calls for messages. func validateBasicTxMsgs(msgs []sdk.Msg) error { if len(msgs) == 0 { return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "must contain at least one message") } for _, msg := range msgs { m, ok := msg.(sdk.HasValidateBasic) if !ok { continue } if err := m.ValidateBasic(); err != nil { return err } } return nil } func (app *BaseApp) getState(mode execMode) *state { switch mode { case execModeFinalize: return app.finalizeBlockState case execModePrepareProposal: return app.prepareProposalState case execModeProcessProposal: return app.processProposalState default: return app.checkState } } func (app *BaseApp) getBlockGasMeter(ctx sdk.Context) storetypes.GasMeter { if app.disableBlockGasMeter { return noopGasMeter{ } } if maxGas := app.GetMaximumBlockGas(ctx); maxGas > 0 { return storetypes.NewGasMeter(maxGas) } return storetypes.NewInfiniteGasMeter() } / retrieve the context for the tx w/ txBytes and other memoized values. func (app *BaseApp) getContextForTx(mode execMode, txBytes []byte) sdk.Context { app.mu.Lock() defer app.mu.Unlock() modeState := app.getState(mode) if modeState == nil { panic(fmt.Sprintf("state is nil for mode %v", mode)) } ctx := modeState.Context(). WithTxBytes(txBytes). WithGasMeter(storetypes.NewInfiniteGasMeter()) / WithVoteInfos(app.voteInfos) / TODO: identify if this is needed ctx = ctx.WithIsSigverifyTx(app.sigverifyTx) ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx)) if mode == execModeReCheck { ctx = ctx.WithIsReCheckTx(true) } if mode == execModeSimulate { ctx, _ = ctx.CacheContext() ctx = ctx.WithExecMode(sdk.ExecMode(execModeSimulate)) } return ctx } / cacheTxContext returns a new context based off of the provided context with / a branched multi-store. func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context, storetypes.CacheMultiStore) { ms := ctx.MultiStore() msCache := ms.CacheMultiStore() if msCache.TracingEnabled() { msCache = msCache.SetTracingContext( storetypes.TraceContext( map[string]any{ "txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)), }, ), ).(storetypes.CacheMultiStore) } return ctx.WithMultiStore(msCache), msCache } func (app *BaseApp) preBlock(req *abci.RequestFinalizeBlock) ([]abci.Event, error) { var events []abci.Event if app.preBlocker != nil { ctx := app.finalizeBlockState.Context().WithEventManager(sdk.NewEventManager()) rsp, err := app.preBlocker(ctx, req) if err != nil { return nil, err } / rsp.ConsensusParamsChanged is true from preBlocker means ConsensusParams in store get changed / write the consensus parameters in store to context if rsp.ConsensusParamsChanged { ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx)) / GasMeter must be set after we get a context with updated consensus params. gasMeter := app.getBlockGasMeter(ctx) ctx = ctx.WithBlockGasMeter(gasMeter) app.finalizeBlockState.SetContext(ctx) } events = ctx.EventManager().ABCIEvents() } return events, nil } func (app *BaseApp) beginBlock(_ *abci.RequestFinalizeBlock) (sdk.BeginBlock, error) { var ( resp sdk.BeginBlock err error ) if app.beginBlocker != nil { resp, err = app.beginBlocker(app.finalizeBlockState.Context()) if err != nil { return resp, err } / append BeginBlock attributes to all events in the EndBlock response for i, event := range resp.Events { resp.Events[i].Attributes = append( event.Attributes, abci.EventAttribute{ Key: "mode", Value: "BeginBlock" }, ) } resp.Events = sdk.MarkEventsToIndex(resp.Events, app.indexEvents) } return resp, nil } func (app *BaseApp) deliverTx(tx []byte) *abci.ExecTxResult { gInfo := sdk.GasInfo{ } resultStr := "successful" var resp *abci.ExecTxResult defer func() { telemetry.IncrCounter(1, "tx", "count") telemetry.IncrCounter(1, "tx", resultStr) telemetry.SetGauge(float32(gInfo.GasUsed), "tx", "gas", "used") telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted") }() gInfo, result, anteEvents, err := app.runTx(execModeFinalize, tx, nil) if err != nil { resultStr = "failed" resp = sdkerrors.ResponseExecTxResultWithEvents( err, gInfo.GasWanted, gInfo.GasUsed, sdk.MarkEventsToIndex(anteEvents, app.indexEvents), app.trace, ) return resp } resp = &abci.ExecTxResult{ GasWanted: int64(gInfo.GasWanted), GasUsed: int64(gInfo.GasUsed), Log: result.Log, Data: result.Data, Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents), } return resp } / endBlock is an application-defined function that is called after transactions / have been processed in FinalizeBlock. func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) { var endblock sdk.EndBlock if app.endBlocker != nil { eb, err := app.endBlocker(app.finalizeBlockState.Context()) if err != nil { return endblock, err } / append EndBlock attributes to all events in the EndBlock response for i, event := range eb.Events { eb.Events[i].Attributes = append( event.Attributes, abci.EventAttribute{ Key: "mode", Value: "EndBlock" }, ) } eb.Events = sdk.MarkEventsToIndex(eb.Events, app.indexEvents) endblock = eb } return endblock, nil } / runTx processes a transaction within a given execution mode, encoded transaction / bytes, and the decoded transaction itself. All state transitions occur through / a cached Context depending on the mode provided. State only gets persisted / if all messages get executed successfully and the execution mode is DeliverTx. / Note, gas execution info is always returned. A reference to a Result is / returned if the tx does not run out of gas and if all the messages are valid / and execute successfully. An error is returned otherwise. / both txbytes and the decoded tx are passed to runTx to avoid the state machine encoding the tx and decoding the transaction twice / passing the decoded tx to runTX is optional, it will be decoded if the tx is nil func (app *BaseApp) runTx(mode execMode, txBytes []byte, tx sdk.Tx) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { / 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 == execModeFinalize && ctx.BlockGasMeter().IsOutOfGas() { return gInfo, nil, nil, errorsmod.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx") } defer func() { if r := recover(); r != nil { recoveryMW := newOutOfGasRecoveryMiddleware(gasWanted, ctx, app.runTxRecoveryMiddleware) err, result = processRecovery(r, recoveryMW), nil ctx.Logger().Error("panic recovered in runTx", "err", err) } gInfo = sdk.GasInfo{ GasWanted: gasWanted, GasUsed: ctx.GasMeter().GasConsumed() } }() blockGasConsumed := false / consumeBlockGas makes sure block gas is consumed at most once. It must / happen after tx processing, and must be executed even if tx processing / fails. Hence, it's execution is deferred. consumeBlockGas := func() { if !blockGasConsumed { blockGasConsumed = true ctx.BlockGasMeter().ConsumeGas( ctx.GasMeter().GasConsumedToLimit(), "block gas meter", ) } } / 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: consumeBlockGas must exist in a separate defer function from the / general deferred recovery function to recover from consumeBlockGas as it'll / be executed first (deferred statements are executed as stack). if mode == execModeFinalize { defer consumeBlockGas() } / if the transaction is not decoded, decode it here if tx == nil { tx, err = app.txDecoder(txBytes) if err != nil { return sdk.GasInfo{ GasUsed: 0, GasWanted: 0 }, nil, nil, sdkerrors.ErrTxDecode.Wrap(err.Error()) } } msgs := tx.GetMsgs() if err := validateBasicTxMsgs(msgs); err != nil { return sdk.GasInfo{ }, nil, nil, err } for _, msg := range msgs { handler := app.msgServiceRouter.Handler(msg) if handler == nil { return sdk.GasInfo{ }, nil, nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg) } } if app.anteHandler != nil { var ( anteCtx sdk.Context msCache storetypes.CacheMultiStore ) / Branch 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) anteCtx = anteCtx.WithEventManager(sdk.NewEventManager()) newCtx, err := app.anteHandler(anteCtx, tx, mode == execModeSimulate) if !newCtx.IsZero() { / At this point, newCtx.MultiStore() is a store branch, or something else / replaced by the AnteHandler. We want the original multistore. / / Also, in the case of the tx aborting, we need to track gas consumed via / the instantiated gas meter in the AnteHandler, so we update the context / prior to returning. ctx = newCtx.WithMultiStore(ms) } events := ctx.EventManager().Events() / GasMeter expected to be set in AnteHandler gasWanted = ctx.GasMeter().Limit() if err != nil { if mode == execModeReCheck { / if the ante handler fails on recheck, we want to remove the tx from the mempool if mempoolErr := app.mempool.Remove(tx); mempoolErr != nil { return gInfo, nil, anteEvents, errors.Join(err, mempoolErr) } } return gInfo, nil, nil, err } msCache.Write() anteEvents = events.ToABCIEvents() } switch mode { case execModeCheck: err = app.mempool.Insert(ctx, tx) if err != nil { return gInfo, nil, anteEvents, err } case execModeFinalize: err = app.mempool.Remove(tx) if err != nil && !errors.Is(err, mempool.ErrTxNotFound) { return gInfo, nil, anteEvents, fmt.Errorf("failed to remove tx from mempool: %w", err) } } / Create a new Context based off of the existing Context with a MultiStore branch / in case message processing fails. At this point, the MultiStore / is a branch of a branch. runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes) / Attempt to execute all messages and only update state if all messages pass / and we're in DeliverTx. Note, runMsgs will never return a reference to a / Result if any single message fails or does not have a registered Handler. msgsV2, err := tx.GetMsgsV2() if err == nil { result, err = app.runMsgs(runMsgCtx, msgs, msgsV2, mode) } / Run optional postHandlers (should run regardless of the execution result). / / Note: If the postHandler fails, we also revert the runMsgs state. if app.postHandler != nil { / The runMsgCtx context currently contains events emitted by the ante handler. / We clear this to correctly order events without duplicates. / Note that the state is still preserved. postCtx := runMsgCtx.WithEventManager(sdk.NewEventManager()) newCtx, errPostHandler := app.postHandler(postCtx, tx, mode == execModeSimulate, err == nil) if errPostHandler != nil { if err == nil { / when the msg was handled successfully, return the post handler error only return gInfo, nil, anteEvents, errPostHandler } / otherwise append to the msg error so that we keep the original error code for better user experience return gInfo, nil, anteEvents, errorsmod.Wrapf(err, "postHandler: %s", errPostHandler) } / we don't want runTx to panic if runMsgs has failed earlier if result == nil { result = &sdk.Result{ } } result.Events = append(result.Events, newCtx.EventManager().ABCIEvents()...) } if err == nil { if mode == execModeFinalize { / When block gas exceeds, it'll panic and won't commit the cached store. consumeBlockGas() msCache.Write() } if len(anteEvents) > 0 && (mode == execModeFinalize || mode == execModeSimulate) { / append the events in the order of occurrence result.Events = append(anteEvents, result.Events...) } } return gInfo, result, anteEvents, err } / runMsgs iterates through a list of messages and executes them with the provided / Context and execution mode. Messages will only be executed during simulation / and DeliverTx. An error is returned if any single message fails or if a / Handler does not exist for a given message route. Otherwise, a reference to a / Result is returned. The caller must not commit state if an error is returned. func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Message, mode execMode) (*sdk.Result, error) { events := sdk.EmptyEvents() var msgResponses []*codectypes.Any / NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter. for i, msg := range msgs { if mode != execModeFinalize && mode != execModeSimulate { break } handler := app.msgServiceRouter.Handler(msg) if handler == nil { return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg) } / ADR 031 request type routing msgResult, err := handler(ctx, msg) if err != nil { return nil, errorsmod.Wrapf(err, "failed to execute message; message index: %d", i) } / create message events msgEvents, err := createEvents(app.cdc, msgResult.GetEvents(), msg, msgsV2[i]) if err != nil { return nil, errorsmod.Wrapf(err, "failed to create message events; message index: %d", i) } / append message events and data / / Note: Each message result's data must be length-prefixed in order to / separate each result. for j, event := range msgEvents { / append message index to all events msgEvents[j] = event.AppendAttributes(sdk.NewAttribute("msg_index", strconv.Itoa(i))) } events = events.AppendEvents(msgEvents) / Each individual sdk.Result that went through the MsgServiceRouter / (which should represent 99% of the Msgs now, since everyone should / be using protobuf Msgs) has exactly one Msg response, set inside / `WrapServiceResult`. We take that Msg response, and aggregate it / into an array. if len(msgResult.MsgResponses) > 0 { msgResponse := msgResult.MsgResponses[0] if msgResponse == nil { return nil, sdkerrors.ErrLogic.Wrapf("got nil Msg response at index %d for msg %s", i, sdk.MsgTypeURL(msg)) } msgResponses = append(msgResponses, msgResponse) } } data, err := makeABCIData(msgResponses) if err != nil { return nil, errorsmod.Wrap(err, "failed to marshal tx data") } return &sdk.Result{ Data: data, Events: events.ToABCIEvents(), MsgResponses: msgResponses, }, nil } / makeABCIData generates the Data field to be sent to ABCI Check/DeliverTx. func makeABCIData(msgResponses []*codectypes.Any) ([]byte, error) { return proto.Marshal(&sdk.TxMsgData{ MsgResponses: msgResponses }) } func createEvents(cdc codec.Codec, events sdk.Events, msg sdk.Msg, msgV2 protov2.Message) (sdk.Events, error) { eventMsgName := sdk.MsgTypeURL(msg) msgEvent := sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, eventMsgName)) / we set the signer attribute as the sender signers, err := cdc.GetMsgV2Signers(msgV2) if err != nil { return nil, err } if len(signers) > 0 && signers[0] != nil { addrStr, err := cdc.InterfaceRegistry().SigningContext().AddressCodec().BytesToString(signers[0]) if err != nil { return nil, err } msgEvent = msgEvent.AppendAttributes(sdk.NewAttribute(sdk.AttributeKeySender, addrStr)) } / verify that events have no module attribute set if _, found := events.GetAttributes(sdk.AttributeKeyModule); !found { if moduleName := sdk.GetModuleNameFromTypeURL(eventMsgName); moduleName != "" { msgEvent = msgEvent.AppendAttributes(sdk.NewAttribute(sdk.AttributeKeyModule, moduleName)) } } return sdk.Events{ msgEvent }.AppendEvents(events), nil } / PrepareProposalVerifyTx performs transaction verification when a proposer is / creating a block proposal during PrepareProposal. Any state committed to the / PrepareProposal state internally will be discarded. <nil, err> will be / returned if the transaction cannot be encoded. <bz, nil> will be returned if / the transaction is valid, otherwise <bz, err> will be returned. func (app *BaseApp) PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error) { bz, err := app.txEncoder(tx) if err != nil { return nil, err } _, _, _, err = app.runTx(execModePrepareProposal, bz, tx) if err != nil { return nil, err } return bz, nil } / ProcessProposalVerifyTx performs transaction verification when receiving a / block proposal during ProcessProposal. Any state committed to the / ProcessProposal state internally will be discarded. <nil, err> will be / returned if the transaction cannot be decoded. <Tx, nil> will be returned if / the transaction is valid, otherwise <Tx, err> will be returned. func (app *BaseApp) ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error) { tx, err := app.txDecoder(txBz) if err != nil { return nil, err } _, _, _, err = app.runTx(execModeProcessProposal, txBz, tx) if err != nil { return nil, err } return tx, nil } func (app *BaseApp) TxDecode(txBytes []byte) (sdk.Tx, error) { return app.txDecoder(txBytes) } func (app *BaseApp) TxEncode(tx sdk.Tx) ([]byte, error) { return app.txEncoder(tx) } func (app *BaseApp) StreamingManager() storetypes.StreamingManager { return app.streamingManager } / Close is called in start cmd to gracefully cleanup resources. func (app *BaseApp) Close() error { var errs []error / Close app.db (opened by cosmos-sdk/server/start.go call to openDB) if app.db != nil { app.logger.Info("Closing application.db") if err := app.db.Close(); err != nil { errs = append(errs, err) } } / Close app.snapshotManager / - opened when app chains use cosmos-sdk/server/util.go/DefaultBaseappOptions (boilerplate) / - which calls cosmos-sdk/server/util.go/GetSnapshotStore / - which is passed to baseapp/options.go/SetSnapshot / - to set app.snapshotManager = snapshots.NewManager if app.snapshotManager != nil { app.logger.Info("Closing snapshots/metadata.db") if err := app.snapshotManager.Close(); err != nil { errs = append(errs, err) } } return errors.Join(errs...) } / GetBaseApp returns the pointer to itself. func (app *BaseApp) GetBaseApp() *BaseApp { return app }
-
Initialize the block gas meter with the
maxGas
limit. Thegas
consumed within the block cannot go abovemaxGas
. This parameter is defined in the application’s consensus parameters. -
Run the application’s
beginBlocker()
, which mainly runs theBeginBlocker()
method of each of the 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 theContext
so that it can be used during transaction execution and EndBlock.
Transaction Execution
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 the transactions in FinalizeBlock message to the application for each transaction in a sequential order. Before the first transaction of a given block is processed, a volatile state calledfinalizeBlockState
is initialized during FinalizeBlock. This state is updated each time a transaction is processed via FinalizeBlock
, and committed to the main state when the block is committed, after what it is set to nil
.
Copy
Ask AI
package baseapp
import (
"context"
"fmt"
"maps"
"math"
"slices"
"strconv"
"sync"
"github.com/cockroachdb/errors"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/crypto/tmhash"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/cosmos/gogoproto/proto"
protov2 "google.golang.org/protobuf/proto"
"cosmossdk.io/core/header"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/log"
"cosmossdk.io/store"
storemetrics "cosmossdk.io/store/metrics"
"cosmossdk.io/store/snapshots"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/baseapp/oe"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/mempool"
"github.com/cosmos/cosmos-sdk/types/msgservice"
)
type (
execMode 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 storetypes.CommitMultiStore)
error
)
const (
execModeCheck execMode = iota / Check a transaction
execModeReCheck / Recheck a (pending)
transaction after a commit
execModeSimulate / Simulate a transaction
execModePrepareProposal / Prepare a block proposal
execModeProcessProposal / Process a block proposal
execModeVoteExtension / Extend or verify a pre-commit vote
execModeVerifyVoteExtension / Verify a vote extension
execModeFinalize / Finalize a block proposal
)
var _ servertypes.ABCI = (*BaseApp)(nil)
/ BaseApp reflects the ABCI application implementation.
type BaseApp struct {
/ initialized on creation
mu sync.Mutex / mu protects the fields below.
logger log.Logger
name string / application name from abci.BlockInfo
db dbm.DB / common DB backend
cms storetypes.CommitMultiStore / Main (uncached)
state
qms storetypes.MultiStore / Optional alternative multistore for querying only.
storeLoader StoreLoader / function to handle store loading, may be overridden with SetStoreLoader()
grpcQueryRouter *GRPCQueryRouter / router for redirecting gRPC query calls
msgServiceRouter *MsgServiceRouter / router for redirecting Msg service messages
interfaceRegistry codectypes.InterfaceRegistry
txDecoder sdk.TxDecoder / unmarshal []byte into sdk.Tx
txEncoder sdk.TxEncoder / marshal sdk.Tx into []byte
mempool mempool.Mempool / application side mempool
anteHandler sdk.AnteHandler / ante handler for fee and auth
postHandler sdk.PostHandler / post handler, optional
checkTxHandler sdk.CheckTxHandler / ABCI CheckTx handler
initChainer sdk.InitChainer / ABCI InitChain handler
preBlocker sdk.PreBlocker / logic to run before BeginBlocker
beginBlocker sdk.BeginBlocker / (legacy ABCI)
BeginBlock handler
endBlocker sdk.EndBlocker / (legacy ABCI)
EndBlock handler
processProposal sdk.ProcessProposalHandler / ABCI ProcessProposal handler
prepareProposal sdk.PrepareProposalHandler / ABCI PrepareProposal
extendVote sdk.ExtendVoteHandler / ABCI ExtendVote handler
verifyVoteExt sdk.VerifyVoteExtensionHandler / ABCI VerifyVoteExtension handler
prepareCheckStater sdk.PrepareCheckStater / logic to run during commit using the checkState
precommiter sdk.Precommiter / logic to run during commit using the deliverState
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.
sigverifyTx bool / in the simulation test, since the account does not have a private key, we have to ignore the tx sigverify.
/ manages snapshots, i.e. dumps of app state at certain intervals
snapshotManager *snapshots.Manager
/ volatile states:
/
/ - checkState is set on InitChain and reset on Commit
/ - finalizeBlockState is set on InitChain and FinalizeBlock and set to nil
/ on Commit.
/
/ - checkState: Used for CheckTx, which is set based on the previous block's
/ state. This state is never committed.
/
/ - prepareProposalState: Used for PrepareProposal, which is set based on the
/ previous block's state. This state is never committed. In case of multiple
/ consensus rounds, the state is always reset to the previous block's state.
/
/ - processProposalState: Used for ProcessProposal, which is set based on the
/ the previous block's state. This state is never committed. In case of
/ multiple rounds, the state is always reset to the previous block's state.
/
/ - finalizeBlockState: Used for FinalizeBlock, which is set based on the
/ previous block's state. This state is committed.
checkState *state
prepareProposalState *state
processProposalState *state
finalizeBlockState *state
/ An inter-block write-through cache provided to the context during the ABCI
/ FinalizeBlock call.
interBlockCache storetypes.MultiStorePersistentCache
/ paramStore is used to query for ABCI consensus parameters from an
/ application parameter store.
paramStore ParamStore
/ queryGasLimit defines the maximum gas for queries; unbounded if 0.
queryGasLimit uint64
/ 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
/ initialHeight is the initial height at which we start the BaseApp
initialHeight int64
/ 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
/ minRetainBlocks defines the minimum block height offset from the current
/ block being committed, such that all blocks past this offset are pruned
/ from CometBFT. It is used as part of the process of determining the
/ ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates
/ that no blocks should be pruned.
/
/ Note: CometBFT block pruning is dependant on this parameter in conjunction
/ with the unbonding (safety threshold)
period, state pruning and state sync
/ snapshot parameters to determine the correct minimum value of
/ ResponseCommit.RetainHeight.
minRetainBlocks uint64
/ application's version string
version string
/ application's protocol version that increments on every upgrade
/ if BaseApp is passed to the upgrade keeper's NewKeeper method.
appVersion uint64
/ recovery handler for app.runTx method
runTxRecoveryMiddleware recoveryMiddleware
/ trace set will return full stack traces for errors in ABCI Log field
trace bool
/ indexEvents defines the set of events in the form {
eventType
}.{
attributeKey
},
/ which informs CometBFT what to index. If empty, all events will be indexed.
indexEvents map[string]struct{
}
/ streamingManager for managing instances and configuration of ABCIListener services
streamingManager storetypes.StreamingManager
chainID string
cdc codec.Codec
/ optimisticExec contains the context required for Optimistic Execution,
/ including the goroutine handling.This is experimental and must be enabled
/ by developers.
optimisticExec *oe.OptimisticExecution
/ disableBlockGasMeter will disable the block gas meter if true, block gas meter is tricky to support
/ when executing transactions in parallel.
/ when disabled, the block gas meter in context is a noop one.
/
/ SAFETY: it's safe to do if validators validate the total gas wanted in the `ProcessProposal`, which is the case in the default handler.
disableBlockGasMeter bool
}
/ 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.
func NewBaseApp(
name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp),
) *BaseApp {
app := &BaseApp{
logger: logger.With(log.ModuleKey, "baseapp"),
name: name,
db: db,
cms: store.NewCommitMultiStore(db, logger, storemetrics.NewNoOpMetrics()), / by default we use a no-op metric gather in store
storeLoader: DefaultStoreLoader,
grpcQueryRouter: NewGRPCQueryRouter(),
msgServiceRouter: NewMsgServiceRouter(),
txDecoder: txDecoder,
fauxMerkleMode: false,
sigverifyTx: true,
queryGasLimit: math.MaxUint64,
}
for _, option := range options {
option(app)
}
if app.mempool == nil {
app.SetMempool(mempool.NoOpMempool{
})
}
abciProposalHandler := NewDefaultProposalHandler(app.mempool, app)
if app.prepareProposal == nil {
app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())
}
if app.processProposal == nil {
app.SetProcessProposal(abciProposalHandler.ProcessProposalHandler())
}
if app.extendVote == nil {
app.SetExtendVoteHandler(NoOpExtendVote())
}
if app.verifyVoteExt == nil {
app.SetVerifyVoteExtensionHandler(NoOpVerifyVoteExtensionHandler())
}
if app.interBlockCache != nil {
app.cms.SetInterBlockCache(app.interBlockCache)
}
app.runTxRecoveryMiddleware = newDefaultRecoveryMiddleware()
/ Initialize with an empty interface registry to avoid nil pointer dereference.
/ Unless SetInterfaceRegistry is called with an interface registry with proper address codecs baseapp will panic.
app.cdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry())
protoFiles, err := proto.MergedRegistry()
if err != nil {
logger.Warn("error creating merged proto registry", "error", err)
}
else {
err = msgservice.ValidateProtoAnnotations(protoFiles)
if err != nil {
/ Once we switch to using protoreflect-based antehandlers, we might
/ want to panic here instead of logging a warning.
logger.Warn("error validating merged proto registry annotations", "error", err)
}
}
return app
}
/ Name returns the name of the BaseApp.
func (app *BaseApp)
Name()
string {
return app.name
}
/ AppVersion returns the application's protocol version.
func (app *BaseApp)
AppVersion()
uint64 {
return app.appVersion
}
/ Version returns the application's version string.
func (app *BaseApp)
Version()
string {
return app.version
}
/ Logger returns the logger of the BaseApp.
func (app *BaseApp)
Logger()
log.Logger {
return app.logger
}
/ Trace returns the boolean value for logging error stack traces.
func (app *BaseApp)
Trace()
bool {
return app.trace
}
/ MsgServiceRouter returns the MsgServiceRouter of a BaseApp.
func (app *BaseApp)
MsgServiceRouter() *MsgServiceRouter {
return app.msgServiceRouter
}
/ GRPCQueryRouter returns the GRPCQueryRouter of a BaseApp.
func (app *BaseApp)
GRPCQueryRouter() *GRPCQueryRouter {
return app.grpcQueryRouter
}
/ MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp
/ multistore.
func (app *BaseApp)
MountStores(keys ...storetypes.StoreKey) {
for _, key := range keys {
switch key.(type) {
case *storetypes.KVStoreKey:
if !app.fauxMerkleMode {
app.MountStore(key, storetypes.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, storetypes.StoreTypeDB)
}
case *storetypes.TransientStoreKey:
app.MountStore(key, storetypes.StoreTypeTransient)
case *storetypes.MemoryStoreKey:
app.MountStore(key, storetypes.StoreTypeMemory)
default:
panic(fmt.Sprintf("Unrecognized store key type :%T", key))
}
}
}
/ MountKVStores mounts all IAVL or DB stores to the provided keys in the
/ BaseApp multistore.
func (app *BaseApp)
MountKVStores(keys map[string]*storetypes.KVStoreKey) {
for _, key := range keys {
if !app.fauxMerkleMode {
app.MountStore(key, storetypes.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, storetypes.StoreTypeDB)
}
}
}
/ MountTransientStores mounts all transient stores to the provided keys in
/ the BaseApp multistore.
func (app *BaseApp)
MountTransientStores(keys map[string]*storetypes.TransientStoreKey) {
for _, key := range keys {
app.MountStore(key, storetypes.StoreTypeTransient)
}
}
/ MountMemoryStores mounts all in-memory KVStores with the BaseApp's internal
/ commit multi-store.
func (app *BaseApp)
MountMemoryStores(keys map[string]*storetypes.MemoryStoreKey) {
skeys := slices.Sorted(maps.Keys(keys))
for _, key := range skeys {
memKey := keys[key]
app.MountStore(memKey, storetypes.StoreTypeMemory)
}
}
/ MountStore mounts a store to the provided key in the BaseApp multistore,
/ using the default DB.
func (app *BaseApp)
MountStore(key storetypes.StoreKey, typ storetypes.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()
error {
err := app.storeLoader(app.cms)
if err != nil {
return fmt.Errorf("failed to load latest version: %w", err)
}
return app.Init()
}
/ DefaultStoreLoader will be used by default and loads the latest version
func DefaultStoreLoader(ms storetypes.CommitMultiStore)
error {
return ms.LoadLatestVersion()
}
/ CommitMultiStore returns the root multi-store.
/ App constructor can use this to access the `cms`.
/ UNSAFE: must not be used during the abci life cycle.
func (app *BaseApp)
CommitMultiStore()
storetypes.CommitMultiStore {
return app.cms
}
/ SnapshotManager returns the snapshot manager.
/ application use this to register extra extension snapshotters.
func (app *BaseApp)
SnapshotManager() *snapshots.Manager {
return app.snapshotManager
}
/ LoadVersion loads the BaseApp application version. It will panic if called
/ more than once on a running baseapp.
func (app *BaseApp)
LoadVersion(version int64)
error {
app.logger.Info("NOTICE: this could take a long time to migrate IAVL store to fastnode if you enable Fast Node.\n")
err := app.cms.LoadVersion(version)
if err != nil {
return fmt.Errorf("failed to load version %d: %w", version, err)
}
return app.Init()
}
/ LastCommitID returns the last CommitID of the multistore.
func (app *BaseApp)
LastCommitID()
storetypes.CommitID {
return app.cms.LastCommitID()
}
/ LastBlockHeight returns the last committed block height.
func (app *BaseApp)
LastBlockHeight()
int64 {
return app.cms.LastCommitID().Version
}
/ ChainID returns the chainID of the app.
func (app *BaseApp)
ChainID()
string {
return app.chainID
}
/ AnteHandler returns the AnteHandler of the app.
func (app *BaseApp)
AnteHandler()
sdk.AnteHandler {
return app.anteHandler
}
/ Mempool returns the Mempool of the app.
func (app *BaseApp)
Mempool()
mempool.Mempool {
return app.mempool
}
/ Init initializes the app. It seals the app, preventing any
/ further modifications. In addition, it validates the app against
/ the earlier provided settings. Returns an error if validation fails.
/ nil otherwise. Panics if the app is already sealed.
func (app *BaseApp)
Init()
error {
if app.sealed {
panic("cannot call initFromMainStore: baseapp already sealed")
}
if app.cms == nil {
return errors.New("commit multi-store must not be nil")
}
emptyHeader := cmtproto.Header{
ChainID: app.chainID
}
/ needed for the export command which inits from store but never calls initchain
app.setState(execModeCheck, emptyHeader)
app.Seal()
return app.cms.GetPruning().Validate()
}
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)
setMinRetainBlocks(minRetainBlocks uint64) {
app.minRetainBlocks = minRetainBlocks
}
func (app *BaseApp)
setInterBlockCache(cache storetypes.MultiStorePersistentCache) {
app.interBlockCache = cache
}
func (app *BaseApp)
setTrace(trace bool) {
app.trace = trace
}
func (app *BaseApp)
setIndexEvents(ie []string) {
app.indexEvents = make(map[string]struct{
})
for _, e := range ie {
app.indexEvents[e] = struct{
}{
}
}
}
/ 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
}
/ setState sets the BaseApp's state for the corresponding mode with a branched
/ multi-store (i.e. a CacheMultiStore)
and a new Context with the same
/ multi-store branch, and provided header.
func (app *BaseApp)
setState(mode execMode, h cmtproto.Header) {
ms := app.cms.CacheMultiStore()
headerInfo := header.Info{
Height: h.Height,
Time: h.Time,
ChainID: h.ChainID,
AppHash: h.AppHash,
}
baseState := &state{
ms: ms,
ctx: sdk.NewContext(ms, h, false, app.logger).
WithStreamingManager(app.streamingManager).
WithHeaderInfo(headerInfo),
}
switch mode {
case execModeCheck:
baseState.SetContext(baseState.Context().WithIsCheckTx(true).WithMinGasPrices(app.minGasPrices))
app.checkState = baseState
case execModePrepareProposal:
app.prepareProposalState = baseState
case execModeProcessProposal:
app.processProposalState = baseState
case execModeFinalize:
app.finalizeBlockState = baseState
default:
panic(fmt.Sprintf("invalid runTxMode for setState: %d", mode))
}
}
/ SetCircuitBreaker sets the circuit breaker for the BaseApp.
/ The circuit breaker is checked on every message execution to verify if a transaction should be executed or not.
func (app *BaseApp)
SetCircuitBreaker(cb CircuitBreaker) {
if app.msgServiceRouter == nil {
panic("cannot set circuit breaker with no msg service router set")
}
app.msgServiceRouter.SetCircuit(cb)
}
/ GetConsensusParams returns the current consensus parameters from the BaseApp's
/ ParamStore. If the BaseApp has no ParamStore defined, nil is returned.
func (app *BaseApp)
GetConsensusParams(ctx sdk.Context)
cmtproto.ConsensusParams {
if app.paramStore == nil {
return cmtproto.ConsensusParams{
}
}
cp, err := app.paramStore.Get(ctx)
if err != nil {
/ This could happen while migrating from v0.45/v0.46 to v0.50, we should
/ allow it to happen so during preblock the upgrade plan can be executed
/ and the consensus params set for the first time in the new format.
app.logger.Error("failed to get consensus params", "err", err)
return cmtproto.ConsensusParams{
}
}
return cp
}
/ StoreConsensusParams sets the consensus parameters to the BaseApp's param
/ store.
/
/ NOTE: We're explicitly not storing the CometBFT app_version in the param store.
/ It's stored instead in the x/upgrade store, with its own bump logic.
func (app *BaseApp)
StoreConsensusParams(ctx sdk.Context, cp cmtproto.ConsensusParams)
error {
if app.paramStore == nil {
return errors.New("cannot store consensus params with no params store set")
}
return app.paramStore.Set(ctx, cp)
}
/ AddRunTxRecoveryHandler adds custom app.runTx method panic handlers.
func (app *BaseApp)
AddRunTxRecoveryHandler(handlers ...RecoveryHandler) {
for _, h := range handlers {
app.runTxRecoveryMiddleware = newRecoveryMiddleware(h, app.runTxRecoveryMiddleware)
}
}
/ 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(ctx sdk.Context)
uint64 {
cp := app.GetConsensusParams(ctx)
if cp.Block == nil {
return 0
}
maxGas := cp.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)
validateFinalizeBlockHeight(req *abci.RequestFinalizeBlock)
error {
if req.Height < 1 {
return fmt.Errorf("invalid height: %d", req.Height)
}
lastBlockHeight := app.LastBlockHeight()
/ expectedHeight holds the expected height to validate
var expectedHeight int64
if lastBlockHeight == 0 && app.initialHeight > 1 {
/ In this case, we're validating the first block of the chain, i.e no
/ previous commit. The height we're expecting is the initial height.
expectedHeight = app.initialHeight
}
else {
/ This case can mean two things:
/
/ - Either there was already a previous commit in the store, in which
/ case we increment the version from there.
/ - Or there was no previous commit, in which case we start at version 1.
expectedHeight = lastBlockHeight + 1
}
if req.Height != expectedHeight {
return fmt.Errorf("invalid height: %d; expected: %d", req.Height, expectedHeight)
}
return nil
}
/ validateBasicTxMsgs executes basic validator calls for messages.
func validateBasicTxMsgs(msgs []sdk.Msg)
error {
if len(msgs) == 0 {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "must contain at least one message")
}
for _, msg := range msgs {
m, ok := msg.(sdk.HasValidateBasic)
if !ok {
continue
}
if err := m.ValidateBasic(); err != nil {
return err
}
}
return nil
}
func (app *BaseApp)
getState(mode execMode) *state {
switch mode {
case execModeFinalize:
return app.finalizeBlockState
case execModePrepareProposal:
return app.prepareProposalState
case execModeProcessProposal:
return app.processProposalState
default:
return app.checkState
}
}
func (app *BaseApp)
getBlockGasMeter(ctx sdk.Context)
storetypes.GasMeter {
if app.disableBlockGasMeter {
return noopGasMeter{
}
}
if maxGas := app.GetMaximumBlockGas(ctx); maxGas > 0 {
return storetypes.NewGasMeter(maxGas)
}
return storetypes.NewInfiniteGasMeter()
}
/ retrieve the context for the tx w/ txBytes and other memoized values.
func (app *BaseApp)
getContextForTx(mode execMode, txBytes []byte)
sdk.Context {
app.mu.Lock()
defer app.mu.Unlock()
modeState := app.getState(mode)
if modeState == nil {
panic(fmt.Sprintf("state is nil for mode %v", mode))
}
ctx := modeState.Context().
WithTxBytes(txBytes).
WithGasMeter(storetypes.NewInfiniteGasMeter())
/ WithVoteInfos(app.voteInfos) / TODO: identify if this is needed
ctx = ctx.WithIsSigverifyTx(app.sigverifyTx)
ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx))
if mode == execModeReCheck {
ctx = ctx.WithIsReCheckTx(true)
}
if mode == execModeSimulate {
ctx, _ = ctx.CacheContext()
ctx = ctx.WithExecMode(sdk.ExecMode(execModeSimulate))
}
return ctx
}
/ cacheTxContext returns a new context based off of the provided context with
/ a branched multi-store.
func (app *BaseApp)
cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context, storetypes.CacheMultiStore) {
ms := ctx.MultiStore()
msCache := ms.CacheMultiStore()
if msCache.TracingEnabled() {
msCache = msCache.SetTracingContext(
storetypes.TraceContext(
map[string]any{
"txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)),
},
),
).(storetypes.CacheMultiStore)
}
return ctx.WithMultiStore(msCache), msCache
}
func (app *BaseApp)
preBlock(req *abci.RequestFinalizeBlock) ([]abci.Event, error) {
var events []abci.Event
if app.preBlocker != nil {
ctx := app.finalizeBlockState.Context().WithEventManager(sdk.NewEventManager())
rsp, err := app.preBlocker(ctx, req)
if err != nil {
return nil, err
}
/ rsp.ConsensusParamsChanged is true from preBlocker means ConsensusParams in store get changed
/ write the consensus parameters in store to context
if rsp.ConsensusParamsChanged {
ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx))
/ GasMeter must be set after we get a context with updated consensus params.
gasMeter := app.getBlockGasMeter(ctx)
ctx = ctx.WithBlockGasMeter(gasMeter)
app.finalizeBlockState.SetContext(ctx)
}
events = ctx.EventManager().ABCIEvents()
}
return events, nil
}
func (app *BaseApp)
beginBlock(_ *abci.RequestFinalizeBlock) (sdk.BeginBlock, error) {
var (
resp sdk.BeginBlock
err error
)
if app.beginBlocker != nil {
resp, err = app.beginBlocker(app.finalizeBlockState.Context())
if err != nil {
return resp, err
}
/ append BeginBlock attributes to all events in the EndBlock response
for i, event := range resp.Events {
resp.Events[i].Attributes = append(
event.Attributes,
abci.EventAttribute{
Key: "mode",
Value: "BeginBlock"
},
)
}
resp.Events = sdk.MarkEventsToIndex(resp.Events, app.indexEvents)
}
return resp, nil
}
func (app *BaseApp)
deliverTx(tx []byte) *abci.ExecTxResult {
gInfo := sdk.GasInfo{
}
resultStr := "successful"
var resp *abci.ExecTxResult
defer func() {
telemetry.IncrCounter(1, "tx", "count")
telemetry.IncrCounter(1, "tx", resultStr)
telemetry.SetGauge(float32(gInfo.GasUsed), "tx", "gas", "used")
telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted")
}()
gInfo, result, anteEvents, err := app.runTx(execModeFinalize, tx, nil)
if err != nil {
resultStr = "failed"
resp = sdkerrors.ResponseExecTxResultWithEvents(
err,
gInfo.GasWanted,
gInfo.GasUsed,
sdk.MarkEventsToIndex(anteEvents, app.indexEvents),
app.trace,
)
return resp
}
resp = &abci.ExecTxResult{
GasWanted: int64(gInfo.GasWanted),
GasUsed: int64(gInfo.GasUsed),
Log: result.Log,
Data: result.Data,
Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents),
}
return resp
}
/ endBlock is an application-defined function that is called after transactions
/ have been processed in FinalizeBlock.
func (app *BaseApp)
endBlock(_ context.Context) (sdk.EndBlock, error) {
var endblock sdk.EndBlock
if app.endBlocker != nil {
eb, err := app.endBlocker(app.finalizeBlockState.Context())
if err != nil {
return endblock, err
}
/ append EndBlock attributes to all events in the EndBlock response
for i, event := range eb.Events {
eb.Events[i].Attributes = append(
event.Attributes,
abci.EventAttribute{
Key: "mode",
Value: "EndBlock"
},
)
}
eb.Events = sdk.MarkEventsToIndex(eb.Events, app.indexEvents)
endblock = eb
}
return endblock, nil
}
/ runTx processes a transaction within a given execution mode, encoded transaction
/ bytes, and the decoded transaction itself. All state transitions occur through
/ a cached Context depending on the mode provided. State only gets persisted
/ if all messages get executed successfully and the execution mode is DeliverTx.
/ Note, gas execution info is always returned. A reference to a Result is
/ returned if the tx does not run out of gas and if all the messages are valid
/ and execute successfully. An error is returned otherwise.
/ both txbytes and the decoded tx are passed to runTx to avoid the state machine encoding the tx and decoding the transaction twice
/ passing the decoded tx to runTX is optional, it will be decoded if the tx is nil
func (app *BaseApp)
runTx(mode execMode, txBytes []byte, tx sdk.Tx) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) {
/ 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 == execModeFinalize && ctx.BlockGasMeter().IsOutOfGas() {
return gInfo, nil, nil, errorsmod.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx")
}
defer func() {
if r := recover(); r != nil {
recoveryMW := newOutOfGasRecoveryMiddleware(gasWanted, ctx, app.runTxRecoveryMiddleware)
err, result = processRecovery(r, recoveryMW), nil
ctx.Logger().Error("panic recovered in runTx", "err", err)
}
gInfo = sdk.GasInfo{
GasWanted: gasWanted,
GasUsed: ctx.GasMeter().GasConsumed()
}
}()
blockGasConsumed := false
/ consumeBlockGas makes sure block gas is consumed at most once. It must
/ happen after tx processing, and must be executed even if tx processing
/ fails. Hence, it's execution is deferred.
consumeBlockGas := func() {
if !blockGasConsumed {
blockGasConsumed = true
ctx.BlockGasMeter().ConsumeGas(
ctx.GasMeter().GasConsumedToLimit(), "block gas meter",
)
}
}
/ 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: consumeBlockGas must exist in a separate defer function from the
/ general deferred recovery function to recover from consumeBlockGas as it'll
/ be executed first (deferred statements are executed as stack).
if mode == execModeFinalize {
defer consumeBlockGas()
}
/ if the transaction is not decoded, decode it here
if tx == nil {
tx, err = app.txDecoder(txBytes)
if err != nil {
return sdk.GasInfo{
GasUsed: 0,
GasWanted: 0
}, nil, nil, sdkerrors.ErrTxDecode.Wrap(err.Error())
}
}
msgs := tx.GetMsgs()
if err := validateBasicTxMsgs(msgs); err != nil {
return sdk.GasInfo{
}, nil, nil, err
}
for _, msg := range msgs {
handler := app.msgServiceRouter.Handler(msg)
if handler == nil {
return sdk.GasInfo{
}, nil, nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg)
}
}
if app.anteHandler != nil {
var (
anteCtx sdk.Context
msCache storetypes.CacheMultiStore
)
/ Branch 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)
anteCtx = anteCtx.WithEventManager(sdk.NewEventManager())
newCtx, err := app.anteHandler(anteCtx, tx, mode == execModeSimulate)
if !newCtx.IsZero() {
/ At this point, newCtx.MultiStore()
is a store branch, or something else
/ replaced by the AnteHandler. We want the original multistore.
/
/ Also, in the case of the tx aborting, we need to track gas consumed via
/ the instantiated gas meter in the AnteHandler, so we update the context
/ prior to returning.
ctx = newCtx.WithMultiStore(ms)
}
events := ctx.EventManager().Events()
/ GasMeter expected to be set in AnteHandler
gasWanted = ctx.GasMeter().Limit()
if err != nil {
if mode == execModeReCheck {
/ if the ante handler fails on recheck, we want to remove the tx from the mempool
if mempoolErr := app.mempool.Remove(tx); mempoolErr != nil {
return gInfo, nil, anteEvents, errors.Join(err, mempoolErr)
}
}
return gInfo, nil, nil, err
}
msCache.Write()
anteEvents = events.ToABCIEvents()
}
switch mode {
case execModeCheck:
err = app.mempool.Insert(ctx, tx)
if err != nil {
return gInfo, nil, anteEvents, err
}
case execModeFinalize:
err = app.mempool.Remove(tx)
if err != nil && !errors.Is(err, mempool.ErrTxNotFound) {
return gInfo, nil, anteEvents,
fmt.Errorf("failed to remove tx from mempool: %w", err)
}
}
/ Create a new Context based off of the existing Context with a MultiStore branch
/ in case message processing fails. At this point, the MultiStore
/ is a branch of a branch.
runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes)
/ Attempt to execute all messages and only update state if all messages pass
/ and we're in DeliverTx. Note, runMsgs will never return a reference to a
/ Result if any single message fails or does not have a registered Handler.
msgsV2, err := tx.GetMsgsV2()
if err == nil {
result, err = app.runMsgs(runMsgCtx, msgs, msgsV2, mode)
}
/ Run optional postHandlers (should run regardless of the execution result).
/
/ Note: If the postHandler fails, we also revert the runMsgs state.
if app.postHandler != nil {
/ The runMsgCtx context currently contains events emitted by the ante handler.
/ We clear this to correctly order events without duplicates.
/ Note that the state is still preserved.
postCtx := runMsgCtx.WithEventManager(sdk.NewEventManager())
newCtx, errPostHandler := app.postHandler(postCtx, tx, mode == execModeSimulate, err == nil)
if errPostHandler != nil {
if err == nil {
/ when the msg was handled successfully, return the post handler error only
return gInfo, nil, anteEvents, errPostHandler
}
/ otherwise append to the msg error so that we keep the original error code for better user experience
return gInfo, nil, anteEvents, errorsmod.Wrapf(err, "postHandler: %s", errPostHandler)
}
/ we don't want runTx to panic if runMsgs has failed earlier
if result == nil {
result = &sdk.Result{
}
}
result.Events = append(result.Events, newCtx.EventManager().ABCIEvents()...)
}
if err == nil {
if mode == execModeFinalize {
/ When block gas exceeds, it'll panic and won't commit the cached store.
consumeBlockGas()
msCache.Write()
}
if len(anteEvents) > 0 && (mode == execModeFinalize || mode == execModeSimulate) {
/ append the events in the order of occurrence
result.Events = append(anteEvents, result.Events...)
}
}
return gInfo, result, anteEvents, err
}
/ runMsgs iterates through a list of messages and executes them with the provided
/ Context and execution mode. Messages will only be executed during simulation
/ and DeliverTx. An error is returned if any single message fails or if a
/ Handler does not exist for a given message route. Otherwise, a reference to a
/ Result is returned. The caller must not commit state if an error is returned.
func (app *BaseApp)
runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Message, mode execMode) (*sdk.Result, error) {
events := sdk.EmptyEvents()
var msgResponses []*codectypes.Any
/ NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter.
for i, msg := range msgs {
if mode != execModeFinalize && mode != execModeSimulate {
break
}
handler := app.msgServiceRouter.Handler(msg)
if handler == nil {
return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg)
}
/ ADR 031 request type routing
msgResult, err := handler(ctx, msg)
if err != nil {
return nil, errorsmod.Wrapf(err, "failed to execute message; message index: %d", i)
}
/ create message events
msgEvents, err := createEvents(app.cdc, msgResult.GetEvents(), msg, msgsV2[i])
if err != nil {
return nil, errorsmod.Wrapf(err, "failed to create message events; message index: %d", i)
}
/ append message events and data
/
/ Note: Each message result's data must be length-prefixed in order to
/ separate each result.
for j, event := range msgEvents {
/ append message index to all events
msgEvents[j] = event.AppendAttributes(sdk.NewAttribute("msg_index", strconv.Itoa(i)))
}
events = events.AppendEvents(msgEvents)
/ Each individual sdk.Result that went through the MsgServiceRouter
/ (which should represent 99% of the Msgs now, since everyone should
/ be using protobuf Msgs)
has exactly one Msg response, set inside
/ `WrapServiceResult`. We take that Msg response, and aggregate it
/ into an array.
if len(msgResult.MsgResponses) > 0 {
msgResponse := msgResult.MsgResponses[0]
if msgResponse == nil {
return nil, sdkerrors.ErrLogic.Wrapf("got nil Msg response at index %d for msg %s", i, sdk.MsgTypeURL(msg))
}
msgResponses = append(msgResponses, msgResponse)
}
}
data, err := makeABCIData(msgResponses)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to marshal tx data")
}
return &sdk.Result{
Data: data,
Events: events.ToABCIEvents(),
MsgResponses: msgResponses,
}, nil
}
/ makeABCIData generates the Data field to be sent to ABCI Check/DeliverTx.
func makeABCIData(msgResponses []*codectypes.Any) ([]byte, error) {
return proto.Marshal(&sdk.TxMsgData{
MsgResponses: msgResponses
})
}
func createEvents(cdc codec.Codec, events sdk.Events, msg sdk.Msg, msgV2 protov2.Message) (sdk.Events, error) {
eventMsgName := sdk.MsgTypeURL(msg)
msgEvent := sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, eventMsgName))
/ we set the signer attribute as the sender
signers, err := cdc.GetMsgV2Signers(msgV2)
if err != nil {
return nil, err
}
if len(signers) > 0 && signers[0] != nil {
addrStr, err := cdc.InterfaceRegistry().SigningContext().AddressCodec().BytesToString(signers[0])
if err != nil {
return nil, err
}
msgEvent = msgEvent.AppendAttributes(sdk.NewAttribute(sdk.AttributeKeySender, addrStr))
}
/ verify that events have no module attribute set
if _, found := events.GetAttributes(sdk.AttributeKeyModule); !found {
if moduleName := sdk.GetModuleNameFromTypeURL(eventMsgName); moduleName != "" {
msgEvent = msgEvent.AppendAttributes(sdk.NewAttribute(sdk.AttributeKeyModule, moduleName))
}
}
return sdk.Events{
msgEvent
}.AppendEvents(events), nil
}
/ PrepareProposalVerifyTx performs transaction verification when a proposer is
/ creating a block proposal during PrepareProposal. Any state committed to the
/ PrepareProposal state internally will be discarded. <nil, err> will be
/ returned if the transaction cannot be encoded. <bz, nil> will be returned if
/ the transaction is valid, otherwise <bz, err> will be returned.
func (app *BaseApp)
PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error) {
bz, err := app.txEncoder(tx)
if err != nil {
return nil, err
}
_, _, _, err = app.runTx(execModePrepareProposal, bz, tx)
if err != nil {
return nil, err
}
return bz, nil
}
/ ProcessProposalVerifyTx performs transaction verification when receiving a
/ block proposal during ProcessProposal. Any state committed to the
/ ProcessProposal state internally will be discarded. <nil, err> will be
/ returned if the transaction cannot be decoded. <Tx, nil> will be returned if
/ the transaction is valid, otherwise <Tx, err> will be returned.
func (app *BaseApp)
ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error) {
tx, err := app.txDecoder(txBz)
if err != nil {
return nil, err
}
_, _, _, err = app.runTx(execModeProcessProposal, txBz, tx)
if err != nil {
return nil, err
}
return tx, nil
}
func (app *BaseApp)
TxDecode(txBytes []byte) (sdk.Tx, error) {
return app.txDecoder(txBytes)
}
func (app *BaseApp)
TxEncode(tx sdk.Tx) ([]byte, error) {
return app.txEncoder(tx)
}
func (app *BaseApp)
StreamingManager()
storetypes.StreamingManager {
return app.streamingManager
}
/ Close is called in start cmd to gracefully cleanup resources.
func (app *BaseApp)
Close()
error {
var errs []error
/ Close app.db (opened by cosmos-sdk/server/start.go call to openDB)
if app.db != nil {
app.logger.Info("Closing application.db")
if err := app.db.Close(); err != nil {
errs = append(errs, err)
}
}
/ Close app.snapshotManager
/ - opened when app chains use cosmos-sdk/server/util.go/DefaultBaseappOptions (boilerplate)
/ - which calls cosmos-sdk/server/util.go/GetSnapshotStore
/ - which is passed to baseapp/options.go/SetSnapshot
/ - to set app.snapshotManager = snapshots.NewManager
if app.snapshotManager != nil {
app.logger.Info("Closing snapshots/metadata.db")
if err := app.snapshotManager.Close(); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
/ GetBaseApp returns the pointer to itself.
func (app *BaseApp)
GetBaseApp() *BaseApp {
return app
}
FinalizeBlock
performs the exact same steps as CheckTx
, with a little caveat at step 3 and the addition of a fifth step:
- The
AnteHandler
does not check that the transaction’sgas-prices
is sufficient. That is because themin-gas-prices
valuegas-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 incentivized to do so, as they earn a bonus on the total fee of the block they propose. - For each
sdk.Msg
in the transaction, route to the appropriate module’s ProtobufMsg
service. Additional stateful checks are performed, and the branched multistore held infinalizeBlockState
’scontext
is updated by the module’skeeper
. If theMsg
service returns successfully, the branched multistore held incontext
is written tofinalizeBlockState
CacheMultiStore
.
GasConsumed
. You can find the default cost of each operation:
Copy
Ask AI
package types
import (
"fmt"
"math"
)
/ Gas consumption descriptors.
const (
GasIterNextCostFlatDesc = "IterNextFlat"
GasValuePerByteDesc = "ValuePerByte"
GasWritePerByteDesc = "WritePerByte"
GasReadPerByteDesc = "ReadPerByte"
GasWriteCostFlatDesc = "WriteFlat"
GasReadCostFlatDesc = "ReadFlat"
GasHasDesc = "Has"
GasDeleteDesc = "Delete"
)
/ Gas measured by the SDK
type Gas = uint64
/ ErrorNegativeGasConsumed defines an error thrown when the amount of gas refunded results in a
/ negative gas consumed amount.
type ErrorNegativeGasConsumed struct {
Descriptor string
}
/ ErrorOutOfGas defines an error thrown when an action results in out of gas.
type ErrorOutOfGas struct {
Descriptor string
}
/ ErrorGasOverflow defines an error thrown when an action results gas consumption
/ unsigned integer overflow.
type ErrorGasOverflow struct {
Descriptor string
}
/ GasMeter interface to track gas consumption
type GasMeter interface {
GasConsumed()
Gas
GasConsumedToLimit()
Gas
GasRemaining()
Gas
Limit()
Gas
ConsumeGas(amount Gas, descriptor string)
RefundGas(amount Gas, descriptor string)
IsPastLimit()
bool
IsOutOfGas()
bool
String()
string
}
type basicGasMeter struct {
limit Gas
consumed Gas
}
/ NewGasMeter returns a reference to a new basicGasMeter.
func NewGasMeter(limit Gas)
GasMeter {
return &basicGasMeter{
limit: limit,
consumed: 0,
}
}
/ GasConsumed returns the gas consumed from the GasMeter.
func (g *basicGasMeter)
GasConsumed()
Gas {
return g.consumed
}
/ GasRemaining returns the gas left in the GasMeter.
func (g *basicGasMeter)
GasRemaining()
Gas {
if g.IsPastLimit() {
return 0
}
return g.limit - g.consumed
}
/ Limit returns the gas limit of the GasMeter.
func (g *basicGasMeter)
Limit()
Gas {
return g.limit
}
/ GasConsumedToLimit returns the gas limit if gas consumed is past the limit,
/ otherwise it returns the consumed gas.
/
/ NOTE: This behavior is only called when recovering from panic when
/ BlockGasMeter consumes gas past the limit.
func (g *basicGasMeter)
GasConsumedToLimit()
Gas {
if g.IsPastLimit() {
return g.limit
}
return g.consumed
}
/ addUint64Overflow performs the addition operation on two uint64 integers and
/ returns a boolean on whether or not the result overflows.
func addUint64Overflow(a, b uint64) (uint64, bool) {
if math.MaxUint64-a < b {
return 0, true
}
return a + b, false
}
/ ConsumeGas adds the given amount of gas to the gas consumed and panics if it overflows the limit or out of gas.
func (g *basicGasMeter)
ConsumeGas(amount Gas, descriptor string) {
var overflow bool
g.consumed, overflow = addUint64Overflow(g.consumed, amount)
if overflow {
g.consumed = math.MaxUint64
panic(ErrorGasOverflow{
descriptor
})
}
if g.consumed > g.limit {
panic(ErrorOutOfGas{
descriptor
})
}
}
/ RefundGas will deduct the given amount from the gas consumed. If the amount is greater than the
/ gas consumed, the function will panic.
/
/ Use case: This functionality enables refunding gas to the transaction or block gas pools so that
/ EVM-compatible chains can fully support the go-ethereum StateDb interface.
/ See https://github.com/cosmos/cosmos-sdk/pull/9403 for reference.
func (g *basicGasMeter)
RefundGas(amount Gas, descriptor string) {
if g.consumed < amount {
panic(ErrorNegativeGasConsumed{
Descriptor: descriptor
})
}
g.consumed -= amount
}
/ IsPastLimit returns true if gas consumed is past limit, otherwise it returns false.
func (g *basicGasMeter)
IsPastLimit()
bool {
return g.consumed > g.limit
}
/ IsOutOfGas returns true if gas consumed is greater than or equal to gas limit, otherwise it returns false.
func (g *basicGasMeter)
IsOutOfGas()
bool {
return g.consumed >= g.limit
}
/ String returns the BasicGasMeter's gas limit and gas consumed.
func (g *basicGasMeter)
String()
string {
return fmt.Sprintf("BasicGasMeter:\n limit: %d\n consumed: %d", g.limit, g.consumed)
}
type infiniteGasMeter struct {
consumed Gas
}
/ NewInfiniteGasMeter returns a new gas meter without a limit.
func NewInfiniteGasMeter()
GasMeter {
return &infiniteGasMeter{
consumed: 0,
}
}
/ GasConsumed returns the gas consumed from the GasMeter.
func (g *infiniteGasMeter)
GasConsumed()
Gas {
return g.consumed
}
/ GasConsumedToLimit returns the gas consumed from the GasMeter since the gas is not confined to a limit.
/ NOTE: This behavior is only called when recovering from panic when BlockGasMeter consumes gas past the limit.
func (g *infiniteGasMeter)
GasConsumedToLimit()
Gas {
return g.consumed
}
/ GasRemaining returns MaxUint64 since limit is not confined in infiniteGasMeter.
func (g *infiniteGasMeter)
GasRemaining()
Gas {
return math.MaxUint64
}
/ Limit returns MaxUint64 since limit is not confined in infiniteGasMeter.
func (g *infiniteGasMeter)
Limit()
Gas {
return math.MaxUint64
}
/ ConsumeGas adds the given amount of gas to the gas consumed and panics if it overflows the limit.
func (g *infiniteGasMeter)
ConsumeGas(amount Gas, descriptor string) {
var overflow bool
/ TODO: Should we set the consumed field after overflow checking?
g.consumed, overflow = addUint64Overflow(g.consumed, amount)
if overflow {
panic(ErrorGasOverflow{
descriptor
})
}
}
/ RefundGas will deduct the given amount from the gas consumed. If the amount is greater than the
/ gas consumed, the function will panic.
/
/ Use case: This functionality enables refunding gas to the trasaction or block gas pools so that
/ EVM-compatible chains can fully support the go-ethereum StateDb interface.
/ See https://github.com/cosmos/cosmos-sdk/pull/9403 for reference.
func (g *infiniteGasMeter)
RefundGas(amount Gas, descriptor string) {
if g.consumed < amount {
panic(ErrorNegativeGasConsumed{
Descriptor: descriptor
})
}
g.consumed -= amount
}
/ IsPastLimit returns false since the gas limit is not confined.
func (g *infiniteGasMeter)
IsPastLimit()
bool {
return false
}
/ IsOutOfGas returns false since the gas limit is not confined.
func (g *infiniteGasMeter)
IsOutOfGas()
bool {
return false
}
/ String returns the InfiniteGasMeter's gas consumed.
func (g *infiniteGasMeter)
String()
string {
return fmt.Sprintf("InfiniteGasMeter:\n consumed: %d", g.consumed)
}
/ GasConfig defines gas cost for each operation on KVStores
type GasConfig struct {
HasCost Gas
DeleteCost Gas
ReadCostFlat Gas
ReadCostPerByte Gas
WriteCostFlat Gas
WriteCostPerByte Gas
IterNextCostFlat Gas
}
/ KVGasConfig returns a default gas config for KVStores.
func KVGasConfig()
GasConfig {
return GasConfig{
HasCost: 1000,
DeleteCost: 1000,
ReadCostFlat: 1000,
ReadCostPerByte: 3,
WriteCostFlat: 2000,
WriteCostPerByte: 30,
IterNextCostFlat: 30,
}
}
/ TransientGasConfig returns a default gas config for TransientStores.
func TransientGasConfig()
GasConfig {
return GasConfig{
HasCost: 100,
DeleteCost: 100,
ReadCostFlat: 100,
ReadCostPerByte: 0,
WriteCostFlat: 200,
WriteCostPerByte: 3,
IterNextCostFlat: 3,
}
}
GasConsumed > GasWanted
, the function returns with Code != 0
and the execution fails.
Each transactions returns a response to the underlying consensus engine of type abci.ExecTxResult
. 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 transaction execution, 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). Seeevent
s for more.Codespace (string)
: Namespace for the Code.
EndBlock
EndBlock is run after transaction execution completes. It allows developers to have logic be executed at the end of each block. In the Cosmos SDK, the bulk EndBlock() method is to run the application’s EndBlocker(), which mainly runs the EndBlocker() method of each of the application’s modules.Copy
Ask AI
package baseapp
import (
"context"
"fmt"
"maps"
"math"
"slices"
"strconv"
"sync"
"github.com/cockroachdb/errors"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/crypto/tmhash"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/cosmos/gogoproto/proto"
protov2 "google.golang.org/protobuf/proto"
"cosmossdk.io/core/header"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/log"
"cosmossdk.io/store"
storemetrics "cosmossdk.io/store/metrics"
"cosmossdk.io/store/snapshots"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/baseapp/oe"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/mempool"
"github.com/cosmos/cosmos-sdk/types/msgservice"
)
type (
execMode 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 storetypes.CommitMultiStore)
error
)
const (
execModeCheck execMode = iota / Check a transaction
execModeReCheck / Recheck a (pending)
transaction after a commit
execModeSimulate / Simulate a transaction
execModePrepareProposal / Prepare a block proposal
execModeProcessProposal / Process a block proposal
execModeVoteExtension / Extend or verify a pre-commit vote
execModeVerifyVoteExtension / Verify a vote extension
execModeFinalize / Finalize a block proposal
)
var _ servertypes.ABCI = (*BaseApp)(nil)
/ BaseApp reflects the ABCI application implementation.
type BaseApp struct {
/ initialized on creation
mu sync.Mutex / mu protects the fields below.
logger log.Logger
name string / application name from abci.BlockInfo
db dbm.DB / common DB backend
cms storetypes.CommitMultiStore / Main (uncached)
state
qms storetypes.MultiStore / Optional alternative multistore for querying only.
storeLoader StoreLoader / function to handle store loading, may be overridden with SetStoreLoader()
grpcQueryRouter *GRPCQueryRouter / router for redirecting gRPC query calls
msgServiceRouter *MsgServiceRouter / router for redirecting Msg service messages
interfaceRegistry codectypes.InterfaceRegistry
txDecoder sdk.TxDecoder / unmarshal []byte into sdk.Tx
txEncoder sdk.TxEncoder / marshal sdk.Tx into []byte
mempool mempool.Mempool / application side mempool
anteHandler sdk.AnteHandler / ante handler for fee and auth
postHandler sdk.PostHandler / post handler, optional
checkTxHandler sdk.CheckTxHandler / ABCI CheckTx handler
initChainer sdk.InitChainer / ABCI InitChain handler
preBlocker sdk.PreBlocker / logic to run before BeginBlocker
beginBlocker sdk.BeginBlocker / (legacy ABCI)
BeginBlock handler
endBlocker sdk.EndBlocker / (legacy ABCI)
EndBlock handler
processProposal sdk.ProcessProposalHandler / ABCI ProcessProposal handler
prepareProposal sdk.PrepareProposalHandler / ABCI PrepareProposal
extendVote sdk.ExtendVoteHandler / ABCI ExtendVote handler
verifyVoteExt sdk.VerifyVoteExtensionHandler / ABCI VerifyVoteExtension handler
prepareCheckStater sdk.PrepareCheckStater / logic to run during commit using the checkState
precommiter sdk.Precommiter / logic to run during commit using the deliverState
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.
sigverifyTx bool / in the simulation test, since the account does not have a private key, we have to ignore the tx sigverify.
/ manages snapshots, i.e. dumps of app state at certain intervals
snapshotManager *snapshots.Manager
/ volatile states:
/
/ - checkState is set on InitChain and reset on Commit
/ - finalizeBlockState is set on InitChain and FinalizeBlock and set to nil
/ on Commit.
/
/ - checkState: Used for CheckTx, which is set based on the previous block's
/ state. This state is never committed.
/
/ - prepareProposalState: Used for PrepareProposal, which is set based on the
/ previous block's state. This state is never committed. In case of multiple
/ consensus rounds, the state is always reset to the previous block's state.
/
/ - processProposalState: Used for ProcessProposal, which is set based on the
/ the previous block's state. This state is never committed. In case of
/ multiple rounds, the state is always reset to the previous block's state.
/
/ - finalizeBlockState: Used for FinalizeBlock, which is set based on the
/ previous block's state. This state is committed.
checkState *state
prepareProposalState *state
processProposalState *state
finalizeBlockState *state
/ An inter-block write-through cache provided to the context during the ABCI
/ FinalizeBlock call.
interBlockCache storetypes.MultiStorePersistentCache
/ paramStore is used to query for ABCI consensus parameters from an
/ application parameter store.
paramStore ParamStore
/ queryGasLimit defines the maximum gas for queries; unbounded if 0.
queryGasLimit uint64
/ 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
/ initialHeight is the initial height at which we start the BaseApp
initialHeight int64
/ 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
/ minRetainBlocks defines the minimum block height offset from the current
/ block being committed, such that all blocks past this offset are pruned
/ from CometBFT. It is used as part of the process of determining the
/ ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates
/ that no blocks should be pruned.
/
/ Note: CometBFT block pruning is dependant on this parameter in conjunction
/ with the unbonding (safety threshold)
period, state pruning and state sync
/ snapshot parameters to determine the correct minimum value of
/ ResponseCommit.RetainHeight.
minRetainBlocks uint64
/ application's version string
version string
/ application's protocol version that increments on every upgrade
/ if BaseApp is passed to the upgrade keeper's NewKeeper method.
appVersion uint64
/ recovery handler for app.runTx method
runTxRecoveryMiddleware recoveryMiddleware
/ trace set will return full stack traces for errors in ABCI Log field
trace bool
/ indexEvents defines the set of events in the form {
eventType
}.{
attributeKey
},
/ which informs CometBFT what to index. If empty, all events will be indexed.
indexEvents map[string]struct{
}
/ streamingManager for managing instances and configuration of ABCIListener services
streamingManager storetypes.StreamingManager
chainID string
cdc codec.Codec
/ optimisticExec contains the context required for Optimistic Execution,
/ including the goroutine handling.This is experimental and must be enabled
/ by developers.
optimisticExec *oe.OptimisticExecution
/ disableBlockGasMeter will disable the block gas meter if true, block gas meter is tricky to support
/ when executing transactions in parallel.
/ when disabled, the block gas meter in context is a noop one.
/
/ SAFETY: it's safe to do if validators validate the total gas wanted in the `ProcessProposal`, which is the case in the default handler.
disableBlockGasMeter bool
}
/ 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.
func NewBaseApp(
name string, logger log.Logger, db dbm.DB, txDecoder sdk.TxDecoder, options ...func(*BaseApp),
) *BaseApp {
app := &BaseApp{
logger: logger.With(log.ModuleKey, "baseapp"),
name: name,
db: db,
cms: store.NewCommitMultiStore(db, logger, storemetrics.NewNoOpMetrics()), / by default we use a no-op metric gather in store
storeLoader: DefaultStoreLoader,
grpcQueryRouter: NewGRPCQueryRouter(),
msgServiceRouter: NewMsgServiceRouter(),
txDecoder: txDecoder,
fauxMerkleMode: false,
sigverifyTx: true,
queryGasLimit: math.MaxUint64,
}
for _, option := range options {
option(app)
}
if app.mempool == nil {
app.SetMempool(mempool.NoOpMempool{
})
}
abciProposalHandler := NewDefaultProposalHandler(app.mempool, app)
if app.prepareProposal == nil {
app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())
}
if app.processProposal == nil {
app.SetProcessProposal(abciProposalHandler.ProcessProposalHandler())
}
if app.extendVote == nil {
app.SetExtendVoteHandler(NoOpExtendVote())
}
if app.verifyVoteExt == nil {
app.SetVerifyVoteExtensionHandler(NoOpVerifyVoteExtensionHandler())
}
if app.interBlockCache != nil {
app.cms.SetInterBlockCache(app.interBlockCache)
}
app.runTxRecoveryMiddleware = newDefaultRecoveryMiddleware()
/ Initialize with an empty interface registry to avoid nil pointer dereference.
/ Unless SetInterfaceRegistry is called with an interface registry with proper address codecs baseapp will panic.
app.cdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry())
protoFiles, err := proto.MergedRegistry()
if err != nil {
logger.Warn("error creating merged proto registry", "error", err)
}
else {
err = msgservice.ValidateProtoAnnotations(protoFiles)
if err != nil {
/ Once we switch to using protoreflect-based antehandlers, we might
/ want to panic here instead of logging a warning.
logger.Warn("error validating merged proto registry annotations", "error", err)
}
}
return app
}
/ Name returns the name of the BaseApp.
func (app *BaseApp)
Name()
string {
return app.name
}
/ AppVersion returns the application's protocol version.
func (app *BaseApp)
AppVersion()
uint64 {
return app.appVersion
}
/ Version returns the application's version string.
func (app *BaseApp)
Version()
string {
return app.version
}
/ Logger returns the logger of the BaseApp.
func (app *BaseApp)
Logger()
log.Logger {
return app.logger
}
/ Trace returns the boolean value for logging error stack traces.
func (app *BaseApp)
Trace()
bool {
return app.trace
}
/ MsgServiceRouter returns the MsgServiceRouter of a BaseApp.
func (app *BaseApp)
MsgServiceRouter() *MsgServiceRouter {
return app.msgServiceRouter
}
/ GRPCQueryRouter returns the GRPCQueryRouter of a BaseApp.
func (app *BaseApp)
GRPCQueryRouter() *GRPCQueryRouter {
return app.grpcQueryRouter
}
/ MountStores mounts all IAVL or DB stores to the provided keys in the BaseApp
/ multistore.
func (app *BaseApp)
MountStores(keys ...storetypes.StoreKey) {
for _, key := range keys {
switch key.(type) {
case *storetypes.KVStoreKey:
if !app.fauxMerkleMode {
app.MountStore(key, storetypes.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, storetypes.StoreTypeDB)
}
case *storetypes.TransientStoreKey:
app.MountStore(key, storetypes.StoreTypeTransient)
case *storetypes.MemoryStoreKey:
app.MountStore(key, storetypes.StoreTypeMemory)
default:
panic(fmt.Sprintf("Unrecognized store key type :%T", key))
}
}
}
/ MountKVStores mounts all IAVL or DB stores to the provided keys in the
/ BaseApp multistore.
func (app *BaseApp)
MountKVStores(keys map[string]*storetypes.KVStoreKey) {
for _, key := range keys {
if !app.fauxMerkleMode {
app.MountStore(key, storetypes.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, storetypes.StoreTypeDB)
}
}
}
/ MountTransientStores mounts all transient stores to the provided keys in
/ the BaseApp multistore.
func (app *BaseApp)
MountTransientStores(keys map[string]*storetypes.TransientStoreKey) {
for _, key := range keys {
app.MountStore(key, storetypes.StoreTypeTransient)
}
}
/ MountMemoryStores mounts all in-memory KVStores with the BaseApp's internal
/ commit multi-store.
func (app *BaseApp)
MountMemoryStores(keys map[string]*storetypes.MemoryStoreKey) {
skeys := slices.Sorted(maps.Keys(keys))
for _, key := range skeys {
memKey := keys[key]
app.MountStore(memKey, storetypes.StoreTypeMemory)
}
}
/ MountStore mounts a store to the provided key in the BaseApp multistore,
/ using the default DB.
func (app *BaseApp)
MountStore(key storetypes.StoreKey, typ storetypes.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()
error {
err := app.storeLoader(app.cms)
if err != nil {
return fmt.Errorf("failed to load latest version: %w", err)
}
return app.Init()
}
/ DefaultStoreLoader will be used by default and loads the latest version
func DefaultStoreLoader(ms storetypes.CommitMultiStore)
error {
return ms.LoadLatestVersion()
}
/ CommitMultiStore returns the root multi-store.
/ App constructor can use this to access the `cms`.
/ UNSAFE: must not be used during the abci life cycle.
func (app *BaseApp)
CommitMultiStore()
storetypes.CommitMultiStore {
return app.cms
}
/ SnapshotManager returns the snapshot manager.
/ application use this to register extra extension snapshotters.
func (app *BaseApp)
SnapshotManager() *snapshots.Manager {
return app.snapshotManager
}
/ LoadVersion loads the BaseApp application version. It will panic if called
/ more than once on a running baseapp.
func (app *BaseApp)
LoadVersion(version int64)
error {
app.logger.Info("NOTICE: this could take a long time to migrate IAVL store to fastnode if you enable Fast Node.\n")
err := app.cms.LoadVersion(version)
if err != nil {
return fmt.Errorf("failed to load version %d: %w", version, err)
}
return app.Init()
}
/ LastCommitID returns the last CommitID of the multistore.
func (app *BaseApp)
LastCommitID()
storetypes.CommitID {
return app.cms.LastCommitID()
}
/ LastBlockHeight returns the last committed block height.
func (app *BaseApp)
LastBlockHeight()
int64 {
return app.cms.LastCommitID().Version
}
/ ChainID returns the chainID of the app.
func (app *BaseApp)
ChainID()
string {
return app.chainID
}
/ AnteHandler returns the AnteHandler of the app.
func (app *BaseApp)
AnteHandler()
sdk.AnteHandler {
return app.anteHandler
}
/ Mempool returns the Mempool of the app.
func (app *BaseApp)
Mempool()
mempool.Mempool {
return app.mempool
}
/ Init initializes the app. It seals the app, preventing any
/ further modifications. In addition, it validates the app against
/ the earlier provided settings. Returns an error if validation fails.
/ nil otherwise. Panics if the app is already sealed.
func (app *BaseApp)
Init()
error {
if app.sealed {
panic("cannot call initFromMainStore: baseapp already sealed")
}
if app.cms == nil {
return errors.New("commit multi-store must not be nil")
}
emptyHeader := cmtproto.Header{
ChainID: app.chainID
}
/ needed for the export command which inits from store but never calls initchain
app.setState(execModeCheck, emptyHeader)
app.Seal()
return app.cms.GetPruning().Validate()
}
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)
setMinRetainBlocks(minRetainBlocks uint64) {
app.minRetainBlocks = minRetainBlocks
}
func (app *BaseApp)
setInterBlockCache(cache storetypes.MultiStorePersistentCache) {
app.interBlockCache = cache
}
func (app *BaseApp)
setTrace(trace bool) {
app.trace = trace
}
func (app *BaseApp)
setIndexEvents(ie []string) {
app.indexEvents = make(map[string]struct{
})
for _, e := range ie {
app.indexEvents[e] = struct{
}{
}
}
}
/ 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
}
/ setState sets the BaseApp's state for the corresponding mode with a branched
/ multi-store (i.e. a CacheMultiStore)
and a new Context with the same
/ multi-store branch, and provided header.
func (app *BaseApp)
setState(mode execMode, h cmtproto.Header) {
ms := app.cms.CacheMultiStore()
headerInfo := header.Info{
Height: h.Height,
Time: h.Time,
ChainID: h.ChainID,
AppHash: h.AppHash,
}
baseState := &state{
ms: ms,
ctx: sdk.NewContext(ms, h, false, app.logger).
WithStreamingManager(app.streamingManager).
WithHeaderInfo(headerInfo),
}
switch mode {
case execModeCheck:
baseState.SetContext(baseState.Context().WithIsCheckTx(true).WithMinGasPrices(app.minGasPrices))
app.checkState = baseState
case execModePrepareProposal:
app.prepareProposalState = baseState
case execModeProcessProposal:
app.processProposalState = baseState
case execModeFinalize:
app.finalizeBlockState = baseState
default:
panic(fmt.Sprintf("invalid runTxMode for setState: %d", mode))
}
}
/ SetCircuitBreaker sets the circuit breaker for the BaseApp.
/ The circuit breaker is checked on every message execution to verify if a transaction should be executed or not.
func (app *BaseApp)
SetCircuitBreaker(cb CircuitBreaker) {
if app.msgServiceRouter == nil {
panic("cannot set circuit breaker with no msg service router set")
}
app.msgServiceRouter.SetCircuit(cb)
}
/ GetConsensusParams returns the current consensus parameters from the BaseApp's
/ ParamStore. If the BaseApp has no ParamStore defined, nil is returned.
func (app *BaseApp)
GetConsensusParams(ctx sdk.Context)
cmtproto.ConsensusParams {
if app.paramStore == nil {
return cmtproto.ConsensusParams{
}
}
cp, err := app.paramStore.Get(ctx)
if err != nil {
/ This could happen while migrating from v0.45/v0.46 to v0.50, we should
/ allow it to happen so during preblock the upgrade plan can be executed
/ and the consensus params set for the first time in the new format.
app.logger.Error("failed to get consensus params", "err", err)
return cmtproto.ConsensusParams{
}
}
return cp
}
/ StoreConsensusParams sets the consensus parameters to the BaseApp's param
/ store.
/
/ NOTE: We're explicitly not storing the CometBFT app_version in the param store.
/ It's stored instead in the x/upgrade store, with its own bump logic.
func (app *BaseApp)
StoreConsensusParams(ctx sdk.Context, cp cmtproto.ConsensusParams)
error {
if app.paramStore == nil {
return errors.New("cannot store consensus params with no params store set")
}
return app.paramStore.Set(ctx, cp)
}
/ AddRunTxRecoveryHandler adds custom app.runTx method panic handlers.
func (app *BaseApp)
AddRunTxRecoveryHandler(handlers ...RecoveryHandler) {
for _, h := range handlers {
app.runTxRecoveryMiddleware = newRecoveryMiddleware(h, app.runTxRecoveryMiddleware)
}
}
/ 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(ctx sdk.Context)
uint64 {
cp := app.GetConsensusParams(ctx)
if cp.Block == nil {
return 0
}
maxGas := cp.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)
validateFinalizeBlockHeight(req *abci.RequestFinalizeBlock)
error {
if req.Height < 1 {
return fmt.Errorf("invalid height: %d", req.Height)
}
lastBlockHeight := app.LastBlockHeight()
/ expectedHeight holds the expected height to validate
var expectedHeight int64
if lastBlockHeight == 0 && app.initialHeight > 1 {
/ In this case, we're validating the first block of the chain, i.e no
/ previous commit. The height we're expecting is the initial height.
expectedHeight = app.initialHeight
}
else {
/ This case can mean two things:
/
/ - Either there was already a previous commit in the store, in which
/ case we increment the version from there.
/ - Or there was no previous commit, in which case we start at version 1.
expectedHeight = lastBlockHeight + 1
}
if req.Height != expectedHeight {
return fmt.Errorf("invalid height: %d; expected: %d", req.Height, expectedHeight)
}
return nil
}
/ validateBasicTxMsgs executes basic validator calls for messages.
func validateBasicTxMsgs(msgs []sdk.Msg)
error {
if len(msgs) == 0 {
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "must contain at least one message")
}
for _, msg := range msgs {
m, ok := msg.(sdk.HasValidateBasic)
if !ok {
continue
}
if err := m.ValidateBasic(); err != nil {
return err
}
}
return nil
}
func (app *BaseApp)
getState(mode execMode) *state {
switch mode {
case execModeFinalize:
return app.finalizeBlockState
case execModePrepareProposal:
return app.prepareProposalState
case execModeProcessProposal:
return app.processProposalState
default:
return app.checkState
}
}
func (app *BaseApp)
getBlockGasMeter(ctx sdk.Context)
storetypes.GasMeter {
if app.disableBlockGasMeter {
return noopGasMeter{
}
}
if maxGas := app.GetMaximumBlockGas(ctx); maxGas > 0 {
return storetypes.NewGasMeter(maxGas)
}
return storetypes.NewInfiniteGasMeter()
}
/ retrieve the context for the tx w/ txBytes and other memoized values.
func (app *BaseApp)
getContextForTx(mode execMode, txBytes []byte)
sdk.Context {
app.mu.Lock()
defer app.mu.Unlock()
modeState := app.getState(mode)
if modeState == nil {
panic(fmt.Sprintf("state is nil for mode %v", mode))
}
ctx := modeState.Context().
WithTxBytes(txBytes).
WithGasMeter(storetypes.NewInfiniteGasMeter())
/ WithVoteInfos(app.voteInfos) / TODO: identify if this is needed
ctx = ctx.WithIsSigverifyTx(app.sigverifyTx)
ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx))
if mode == execModeReCheck {
ctx = ctx.WithIsReCheckTx(true)
}
if mode == execModeSimulate {
ctx, _ = ctx.CacheContext()
ctx = ctx.WithExecMode(sdk.ExecMode(execModeSimulate))
}
return ctx
}
/ cacheTxContext returns a new context based off of the provided context with
/ a branched multi-store.
func (app *BaseApp)
cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context, storetypes.CacheMultiStore) {
ms := ctx.MultiStore()
msCache := ms.CacheMultiStore()
if msCache.TracingEnabled() {
msCache = msCache.SetTracingContext(
storetypes.TraceContext(
map[string]any{
"txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)),
},
),
).(storetypes.CacheMultiStore)
}
return ctx.WithMultiStore(msCache), msCache
}
func (app *BaseApp)
preBlock(req *abci.RequestFinalizeBlock) ([]abci.Event, error) {
var events []abci.Event
if app.preBlocker != nil {
ctx := app.finalizeBlockState.Context().WithEventManager(sdk.NewEventManager())
rsp, err := app.preBlocker(ctx, req)
if err != nil {
return nil, err
}
/ rsp.ConsensusParamsChanged is true from preBlocker means ConsensusParams in store get changed
/ write the consensus parameters in store to context
if rsp.ConsensusParamsChanged {
ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx))
/ GasMeter must be set after we get a context with updated consensus params.
gasMeter := app.getBlockGasMeter(ctx)
ctx = ctx.WithBlockGasMeter(gasMeter)
app.finalizeBlockState.SetContext(ctx)
}
events = ctx.EventManager().ABCIEvents()
}
return events, nil
}
func (app *BaseApp)
beginBlock(_ *abci.RequestFinalizeBlock) (sdk.BeginBlock, error) {
var (
resp sdk.BeginBlock
err error
)
if app.beginBlocker != nil {
resp, err = app.beginBlocker(app.finalizeBlockState.Context())
if err != nil {
return resp, err
}
/ append BeginBlock attributes to all events in the EndBlock response
for i, event := range resp.Events {
resp.Events[i].Attributes = append(
event.Attributes,
abci.EventAttribute{
Key: "mode",
Value: "BeginBlock"
},
)
}
resp.Events = sdk.MarkEventsToIndex(resp.Events, app.indexEvents)
}
return resp, nil
}
func (app *BaseApp)
deliverTx(tx []byte) *abci.ExecTxResult {
gInfo := sdk.GasInfo{
}
resultStr := "successful"
var resp *abci.ExecTxResult
defer func() {
telemetry.IncrCounter(1, "tx", "count")
telemetry.IncrCounter(1, "tx", resultStr)
telemetry.SetGauge(float32(gInfo.GasUsed), "tx", "gas", "used")
telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted")
}()
gInfo, result, anteEvents, err := app.runTx(execModeFinalize, tx, nil)
if err != nil {
resultStr = "failed"
resp = sdkerrors.ResponseExecTxResultWithEvents(
err,
gInfo.GasWanted,
gInfo.GasUsed,
sdk.MarkEventsToIndex(anteEvents, app.indexEvents),
app.trace,
)
return resp
}
resp = &abci.ExecTxResult{
GasWanted: int64(gInfo.GasWanted),
GasUsed: int64(gInfo.GasUsed),
Log: result.Log,
Data: result.Data,
Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents),
}
return resp
}
/ endBlock is an application-defined function that is called after transactions
/ have been processed in FinalizeBlock.
func (app *BaseApp)
endBlock(_ context.Context) (sdk.EndBlock, error) {
var endblock sdk.EndBlock
if app.endBlocker != nil {
eb, err := app.endBlocker(app.finalizeBlockState.Context())
if err != nil {
return endblock, err
}
/ append EndBlock attributes to all events in the EndBlock response
for i, event := range eb.Events {
eb.Events[i].Attributes = append(
event.Attributes,
abci.EventAttribute{
Key: "mode",
Value: "EndBlock"
},
)
}
eb.Events = sdk.MarkEventsToIndex(eb.Events, app.indexEvents)
endblock = eb
}
return endblock, nil
}
/ runTx processes a transaction within a given execution mode, encoded transaction
/ bytes, and the decoded transaction itself. All state transitions occur through
/ a cached Context depending on the mode provided. State only gets persisted
/ if all messages get executed successfully and the execution mode is DeliverTx.
/ Note, gas execution info is always returned. A reference to a Result is
/ returned if the tx does not run out of gas and if all the messages are valid
/ and execute successfully. An error is returned otherwise.
/ both txbytes and the decoded tx are passed to runTx to avoid the state machine encoding the tx and decoding the transaction twice
/ passing the decoded tx to runTX is optional, it will be decoded if the tx is nil
func (app *BaseApp)
runTx(mode execMode, txBytes []byte, tx sdk.Tx) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) {
/ 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 == execModeFinalize && ctx.BlockGasMeter().IsOutOfGas() {
return gInfo, nil, nil, errorsmod.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx")
}
defer func() {
if r := recover(); r != nil {
recoveryMW := newOutOfGasRecoveryMiddleware(gasWanted, ctx, app.runTxRecoveryMiddleware)
err, result = processRecovery(r, recoveryMW), nil
ctx.Logger().Error("panic recovered in runTx", "err", err)
}
gInfo = sdk.GasInfo{
GasWanted: gasWanted,
GasUsed: ctx.GasMeter().GasConsumed()
}
}()
blockGasConsumed := false
/ consumeBlockGas makes sure block gas is consumed at most once. It must
/ happen after tx processing, and must be executed even if tx processing
/ fails. Hence, it's execution is deferred.
consumeBlockGas := func() {
if !blockGasConsumed {
blockGasConsumed = true
ctx.BlockGasMeter().ConsumeGas(
ctx.GasMeter().GasConsumedToLimit(), "block gas meter",
)
}
}
/ 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: consumeBlockGas must exist in a separate defer function from the
/ general deferred recovery function to recover from consumeBlockGas as it'll
/ be executed first (deferred statements are executed as stack).
if mode == execModeFinalize {
defer consumeBlockGas()
}
/ if the transaction is not decoded, decode it here
if tx == nil {
tx, err = app.txDecoder(txBytes)
if err != nil {
return sdk.GasInfo{
GasUsed: 0,
GasWanted: 0
}, nil, nil, sdkerrors.ErrTxDecode.Wrap(err.Error())
}
}
msgs := tx.GetMsgs()
if err := validateBasicTxMsgs(msgs); err != nil {
return sdk.GasInfo{
}, nil, nil, err
}
for _, msg := range msgs {
handler := app.msgServiceRouter.Handler(msg)
if handler == nil {
return sdk.GasInfo{
}, nil, nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg)
}
}
if app.anteHandler != nil {
var (
anteCtx sdk.Context
msCache storetypes.CacheMultiStore
)
/ Branch 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)
anteCtx = anteCtx.WithEventManager(sdk.NewEventManager())
newCtx, err := app.anteHandler(anteCtx, tx, mode == execModeSimulate)
if !newCtx.IsZero() {
/ At this point, newCtx.MultiStore()
is a store branch, or something else
/ replaced by the AnteHandler. We want the original multistore.
/
/ Also, in the case of the tx aborting, we need to track gas consumed via
/ the instantiated gas meter in the AnteHandler, so we update the context
/ prior to returning.
ctx = newCtx.WithMultiStore(ms)
}
events := ctx.EventManager().Events()
/ GasMeter expected to be set in AnteHandler
gasWanted = ctx.GasMeter().Limit()
if err != nil {
if mode == execModeReCheck {
/ if the ante handler fails on recheck, we want to remove the tx from the mempool
if mempoolErr := app.mempool.Remove(tx); mempoolErr != nil {
return gInfo, nil, anteEvents, errors.Join(err, mempoolErr)
}
}
return gInfo, nil, nil, err
}
msCache.Write()
anteEvents = events.ToABCIEvents()
}
switch mode {
case execModeCheck:
err = app.mempool.Insert(ctx, tx)
if err != nil {
return gInfo, nil, anteEvents, err
}
case execModeFinalize:
err = app.mempool.Remove(tx)
if err != nil && !errors.Is(err, mempool.ErrTxNotFound) {
return gInfo, nil, anteEvents,
fmt.Errorf("failed to remove tx from mempool: %w", err)
}
}
/ Create a new Context based off of the existing Context with a MultiStore branch
/ in case message processing fails. At this point, the MultiStore
/ is a branch of a branch.
runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes)
/ Attempt to execute all messages and only update state if all messages pass
/ and we're in DeliverTx. Note, runMsgs will never return a reference to a
/ Result if any single message fails or does not have a registered Handler.
msgsV2, err := tx.GetMsgsV2()
if err == nil {
result, err = app.runMsgs(runMsgCtx, msgs, msgsV2, mode)
}
/ Run optional postHandlers (should run regardless of the execution result).
/
/ Note: If the postHandler fails, we also revert the runMsgs state.
if app.postHandler != nil {
/ The runMsgCtx context currently contains events emitted by the ante handler.
/ We clear this to correctly order events without duplicates.
/ Note that the state is still preserved.
postCtx := runMsgCtx.WithEventManager(sdk.NewEventManager())
newCtx, errPostHandler := app.postHandler(postCtx, tx, mode == execModeSimulate, err == nil)
if errPostHandler != nil {
if err == nil {
/ when the msg was handled successfully, return the post handler error only
return gInfo, nil, anteEvents, errPostHandler
}
/ otherwise append to the msg error so that we keep the original error code for better user experience
return gInfo, nil, anteEvents, errorsmod.Wrapf(err, "postHandler: %s", errPostHandler)
}
/ we don't want runTx to panic if runMsgs has failed earlier
if result == nil {
result = &sdk.Result{
}
}
result.Events = append(result.Events, newCtx.EventManager().ABCIEvents()...)
}
if err == nil {
if mode == execModeFinalize {
/ When block gas exceeds, it'll panic and won't commit the cached store.
consumeBlockGas()
msCache.Write()
}
if len(anteEvents) > 0 && (mode == execModeFinalize || mode == execModeSimulate) {
/ append the events in the order of occurrence
result.Events = append(anteEvents, result.Events...)
}
}
return gInfo, result, anteEvents, err
}
/ runMsgs iterates through a list of messages and executes them with the provided
/ Context and execution mode. Messages will only be executed during simulation
/ and DeliverTx. An error is returned if any single message fails or if a
/ Handler does not exist for a given message route. Otherwise, a reference to a
/ Result is returned. The caller must not commit state if an error is returned.
func (app *BaseApp)
runMsgs(ctx sdk.Context, msgs []sdk.Msg, msgsV2 []protov2.Message, mode execMode) (*sdk.Result, error) {
events := sdk.EmptyEvents()
var msgResponses []*codectypes.Any
/ NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter.
for i, msg := range msgs {
if mode != execModeFinalize && mode != execModeSimulate {
break
}
handler := app.msgServiceRouter.Handler(msg)
if handler == nil {
return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "no message handler found for %T", msg)
}
/ ADR 031 request type routing
msgResult, err := handler(ctx, msg)
if err != nil {
return nil, errorsmod.Wrapf(err, "failed to execute message; message index: %d", i)
}
/ create message events
msgEvents, err := createEvents(app.cdc, msgResult.GetEvents(), msg, msgsV2[i])
if err != nil {
return nil, errorsmod.Wrapf(err, "failed to create message events; message index: %d", i)
}
/ append message events and data
/
/ Note: Each message result's data must be length-prefixed in order to
/ separate each result.
for j, event := range msgEvents {
/ append message index to all events
msgEvents[j] = event.AppendAttributes(sdk.NewAttribute("msg_index", strconv.Itoa(i)))
}
events = events.AppendEvents(msgEvents)
/ Each individual sdk.Result that went through the MsgServiceRouter
/ (which should represent 99% of the Msgs now, since everyone should
/ be using protobuf Msgs)
has exactly one Msg response, set inside
/ `WrapServiceResult`. We take that Msg response, and aggregate it
/ into an array.
if len(msgResult.MsgResponses) > 0 {
msgResponse := msgResult.MsgResponses[0]
if msgResponse == nil {
return nil, sdkerrors.ErrLogic.Wrapf("got nil Msg response at index %d for msg %s", i, sdk.MsgTypeURL(msg))
}
msgResponses = append(msgResponses, msgResponse)
}
}
data, err := makeABCIData(msgResponses)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to marshal tx data")
}
return &sdk.Result{
Data: data,
Events: events.ToABCIEvents(),
MsgResponses: msgResponses,
}, nil
}
/ makeABCIData generates the Data field to be sent to ABCI Check/DeliverTx.
func makeABCIData(msgResponses []*codectypes.Any) ([]byte, error) {
return proto.Marshal(&sdk.TxMsgData{
MsgResponses: msgResponses
})
}
func createEvents(cdc codec.Codec, events sdk.Events, msg sdk.Msg, msgV2 protov2.Message) (sdk.Events, error) {
eventMsgName := sdk.MsgTypeURL(msg)
msgEvent := sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, eventMsgName))
/ we set the signer attribute as the sender
signers, err := cdc.GetMsgV2Signers(msgV2)
if err != nil {
return nil, err
}
if len(signers) > 0 && signers[0] != nil {
addrStr, err := cdc.InterfaceRegistry().SigningContext().AddressCodec().BytesToString(signers[0])
if err != nil {
return nil, err
}
msgEvent = msgEvent.AppendAttributes(sdk.NewAttribute(sdk.AttributeKeySender, addrStr))
}
/ verify that events have no module attribute set
if _, found := events.GetAttributes(sdk.AttributeKeyModule); !found {
if moduleName := sdk.GetModuleNameFromTypeURL(eventMsgName); moduleName != "" {
msgEvent = msgEvent.AppendAttributes(sdk.NewAttribute(sdk.AttributeKeyModule, moduleName))
}
}
return sdk.Events{
msgEvent
}.AppendEvents(events), nil
}
/ PrepareProposalVerifyTx performs transaction verification when a proposer is
/ creating a block proposal during PrepareProposal. Any state committed to the
/ PrepareProposal state internally will be discarded. <nil, err> will be
/ returned if the transaction cannot be encoded. <bz, nil> will be returned if
/ the transaction is valid, otherwise <bz, err> will be returned.
func (app *BaseApp)
PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error) {
bz, err := app.txEncoder(tx)
if err != nil {
return nil, err
}
_, _, _, err = app.runTx(execModePrepareProposal, bz, tx)
if err != nil {
return nil, err
}
return bz, nil
}
/ ProcessProposalVerifyTx performs transaction verification when receiving a
/ block proposal during ProcessProposal. Any state committed to the
/ ProcessProposal state internally will be discarded. <nil, err> will be
/ returned if the transaction cannot be decoded. <Tx, nil> will be returned if
/ the transaction is valid, otherwise <Tx, err> will be returned.
func (app *BaseApp)
ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error) {
tx, err := app.txDecoder(txBz)
if err != nil {
return nil, err
}
_, _, _, err = app.runTx(execModeProcessProposal, txBz, tx)
if err != nil {
return nil, err
}
return tx, nil
}
func (app *BaseApp)
TxDecode(txBytes []byte) (sdk.Tx, error) {
return app.txDecoder(txBytes)
}
func (app *BaseApp)
TxEncode(tx sdk.Tx) ([]byte, error) {
return app.txEncoder(tx)
}
func (app *BaseApp)
StreamingManager()
storetypes.StreamingManager {
return app.streamingManager
}
/ Close is called in start cmd to gracefully cleanup resources.
func (app *BaseApp)
Close()
error {
var errs []error
/ Close app.db (opened by cosmos-sdk/server/start.go call to openDB)
if app.db != nil {
app.logger.Info("Closing application.db")
if err := app.db.Close(); err != nil {
errs = append(errs, err)
}
}
/ Close app.snapshotManager
/ - opened when app chains use cosmos-sdk/server/util.go/DefaultBaseappOptions (boilerplate)
/ - which calls cosmos-sdk/server/util.go/GetSnapshotStore
/ - which is passed to baseapp/options.go/SetSnapshot
/ - to set app.snapshotManager = snapshots.NewManager
if app.snapshotManager != nil {
app.logger.Info("Closing snapshots/metadata.db")
if err := app.snapshotManager.Close(); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
/ GetBaseApp returns the pointer to itself.
func (app *BaseApp)
GetBaseApp() *BaseApp {
return app
}
Commit
TheCommit
ABCI message is sent from the underlying CometBFT 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.CommitResponse)
function is implemented to commit all the valid state transitions that occurred during FinalizeBlock
and to reset state for the next block.
To commit state-transitions, the Commit
function calls the Write()
function on finalizeBlockState.ms
, where finalizeBlockState.ms
is a branched multistore of the main store app.cms
. Then, the Commit
function sets checkState
to the latest header (obtained from finalizeBlockState.ctx.BlockHeader
) and finalizeBlockState
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
TheInfo
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.InfoResponse)
function from BaseApp
will return the application’s name, version and the hash of the last commit of app.cms
.
Query
TheQuery
ABCI message is used to serve queries received from the underlying consensus engine, including queries received via RPC like CometBFT RPC. It used to be the main entrypoint to build interfaces with the application, but with the introduction of gRPC queries in Cosmos SDK v0.40, its usage is more limited. The application must respect a few rules when implementing the Query
method, which are outlined here.
Each CometBFT query
comes with a path
, which is a string
which denotes what to query. If the path
matches a gRPC fully-qualified service method, then BaseApp
will defer the query to the grpcQueryRouter
and let it handle it like explained above. Otherwise, the path
represents a query that is not (yet) handled by the gRPC router. BaseApp
splits the path
string with the /
delimiter. By convention, the first element of the split string (split[0]
) contains the category of query
(app
, p2p
, store
or custom
). The BaseApp
implementation of the Query(req abci.QueryRequest)
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 queries are different from custom queries which go throughapp.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 eitherapp.addrPeerFilter
orapp.ipPeerFilter
that contain the list of peers filtered by address or IP respectively. These lists are first initialized viaoptions
inBaseApp
’s constructor.
ExtendVote
ExtendVote
allows an application to extend a pre-commit vote with arbitrary data. This process does NOT have to be deterministic and the data returned can be unique to the validator process.
In the Cosmos-SDK this is implemented as a NoOp:
Copy
Ask AI
package baseapp
import (
"bytes"
"context"
"fmt"
"slices"
"github.com/cockroachdb/errors"
abci "github.com/cometbft/cometbft/abci/types"
cryptoenc "github.com/cometbft/cometbft/crypto/encoding"
cmtprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
cmttypes "github.com/cometbft/cometbft/types"
protoio "github.com/cosmos/gogoproto/io"
"github.com/cosmos/gogoproto/proto"
"cosmossdk.io/core/comet"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/mempool"
)
type (
/ ValidatorStore defines the interface contract required for verifying vote
/ extension signatures. Typically, this will be implemented by the x/staking
/ module, which has knowledge of the CometBFT public key.
ValidatorStore interface {
GetPubKeyByConsAddr(context.Context, sdk.ConsAddress) (cmtprotocrypto.PublicKey, error)
}
/ GasTx defines the contract that a transaction with a gas limit must implement.
GasTx interface {
GetGas()
uint64
}
)
/ ValidateVoteExtensions defines a helper function for verifying vote extension
/ signatures that may be passed or manually injected into a block proposal from
/ a proposer in PrepareProposal. It returns an error if any signature is invalid
/ or if unexpected vote extensions and/or signatures are found or less than 2/3
/ power is received.
/ NOTE: From v0.50.5 `currentHeight` and `chainID` arguments are ignored for fixing an issue.
/ They will be removed from the function in v0.51+.
func ValidateVoteExtensions(
ctx sdk.Context,
valStore ValidatorStore,
_ int64,
_ string,
extCommit abci.ExtendedCommitInfo,
)
error {
/ Get values from context
cp := ctx.ConsensusParams()
currentHeight := ctx.HeaderInfo().Height
chainID := ctx.HeaderInfo().ChainID
commitInfo := ctx.CometInfo().GetLastCommit()
/ Check that both extCommit + commit are ordered in accordance with vp/address.
if err := validateExtendedCommitAgainstLastCommit(extCommit, commitInfo); err != nil {
return err
}
/ Start checking vote extensions only **after** the vote extensions enable
/ height, because when `currentHeight == VoteExtensionsEnableHeight`
/ PrepareProposal doesn't get any vote extensions in its request.
extsEnabled := cp.Abci != nil && currentHeight > cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0
marshalDelimitedFn := func(msg proto.Message) ([]byte, error) {
var buf bytes.Buffer
if err := protoio.NewDelimitedWriter(&buf).WriteMsg(msg); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var (
/ Total voting power of all vote extensions.
totalVP int64
/ Total voting power of all validators that submitted valid vote extensions.
sumVP int64
)
for _, vote := range extCommit.Votes {
totalVP += vote.Validator.Power
/ Only check + include power if the vote is a commit vote. There must be super-majority, otherwise the
/ previous block (the block the vote is for)
could not have been committed.
if vote.BlockIdFlag != cmtproto.BlockIDFlagCommit {
continue
}
if !extsEnabled {
if len(vote.VoteExtension) > 0 {
return fmt.Errorf("vote extensions disabled; received non-empty vote extension at height %d", currentHeight)
}
if len(vote.ExtensionSignature) > 0 {
return fmt.Errorf("vote extensions disabled; received non-empty vote extension signature at height %d", currentHeight)
}
continue
}
if len(vote.ExtensionSignature) == 0 {
return fmt.Errorf("vote extensions enabled; received empty vote extension signature at height %d", currentHeight)
}
valConsAddr := sdk.ConsAddress(vote.Validator.Address)
pubKeyProto, err := valStore.GetPubKeyByConsAddr(ctx, valConsAddr)
if err != nil {
return fmt.Errorf("failed to get validator %X public key: %w", valConsAddr, err)
}
cmtPubKey, err := cryptoenc.PubKeyFromProto(pubKeyProto)
if err != nil {
return fmt.Errorf("failed to convert validator %X public key: %w", valConsAddr, err)
}
cve := cmtproto.CanonicalVoteExtension{
Extension: vote.VoteExtension,
Height: currentHeight - 1, / the vote extension was signed in the previous height
Round: int64(extCommit.Round),
ChainId: chainID,
}
extSignBytes, err := marshalDelimitedFn(&cve)
if err != nil {
return fmt.Errorf("failed to encode CanonicalVoteExtension: %w", err)
}
if !cmtPubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) {
return fmt.Errorf("failed to verify validator %X vote extension signature", valConsAddr)
}
sumVP += vote.Validator.Power
}
/ This check is probably unnecessary, but better safe than sorry.
if totalVP <= 0 {
return fmt.Errorf("total voting power must be positive, got: %d", totalVP)
}
/ If the sum of the voting power has not reached (2/3 + 1)
we need to error.
if requiredVP := ((totalVP * 2) / 3) + 1; sumVP < requiredVP {
return fmt.Errorf(
"insufficient cumulative voting power received to verify vote extensions; got: %d, expected: >=%d",
sumVP, requiredVP,
)
}
return nil
}
/ validateExtendedCommitAgainstLastCommit validates an ExtendedCommitInfo against a LastCommit. Specifically,
/ it checks that the ExtendedCommit + LastCommit (for the same height), are consistent with each other + that
/ they are ordered correctly (by voting power)
in accordance with
/ [comet](https://github.com/cometbft/cometbft/blob/4ce0277b35f31985bbf2c25d3806a184a4510010/types/validator_set.go#L784).
func validateExtendedCommitAgainstLastCommit(ec abci.ExtendedCommitInfo, lc comet.CommitInfo)
error {
/ check that the rounds are the same
if ec.Round != lc.Round() {
return fmt.Errorf("extended commit round %d does not match last commit round %d", ec.Round, lc.Round())
}
/ check that the # of votes are the same
if len(ec.Votes) != lc.Votes().Len() {
return fmt.Errorf("extended commit votes length %d does not match last commit votes length %d", len(ec.Votes), lc.Votes().Len())
}
/ check sort order of extended commit votes
if !slices.IsSortedFunc(ec.Votes, func(vote1, vote2 abci.ExtendedVoteInfo)
int {
if vote1.Validator.Power == vote2.Validator.Power {
return bytes.Compare(vote1.Validator.Address, vote2.Validator.Address) / addresses sorted in ascending order (used to break vp conflicts)
}
return -int(vote1.Validator.Power - vote2.Validator.Power) / vp sorted in descending order
}) {
return fmt.Errorf("extended commit votes are not sorted by voting power")
}
addressCache := make(map[string]struct{
}, len(ec.Votes))
/ check that consistency between LastCommit and ExtendedCommit
for i, vote := range ec.Votes {
/ cache addresses to check for duplicates
if _, ok := addressCache[string(vote.Validator.Address)]; ok {
return fmt.Errorf("extended commit vote address %X is duplicated", vote.Validator.Address)
}
addressCache[string(vote.Validator.Address)] = struct{
}{
}
if !bytes.Equal(vote.Validator.Address, lc.Votes().Get(i).Validator().Address()) {
return fmt.Errorf("extended commit vote address %X does not match last commit vote address %X", vote.Validator.Address, lc.Votes().Get(i).Validator().Address())
}
if vote.Validator.Power != lc.Votes().Get(i).Validator().Power() {
return fmt.Errorf("extended commit vote power %d does not match last commit vote power %d", vote.Validator.Power, lc.Votes().Get(i).Validator().Power())
}
}
return nil
}
type (
/ ProposalTxVerifier defines the interface that is implemented by BaseApp,
/ that any custom ABCI PrepareProposal and ProcessProposal handler can use
/ to verify a transaction.
ProposalTxVerifier interface {
PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error)
ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error)
TxDecode(txBz []byte) (sdk.Tx, error)
TxEncode(tx sdk.Tx) ([]byte, error)
}
/ DefaultProposalHandler defines the default ABCI PrepareProposal and
/ ProcessProposal handlers.
DefaultProposalHandler struct {
mempool mempool.Mempool
txVerifier ProposalTxVerifier
txSelector TxSelector
signerExtAdapter mempool.SignerExtractionAdapter
}
)
func NewDefaultProposalHandler(mp mempool.Mempool, txVerifier ProposalTxVerifier) *DefaultProposalHandler {
return &DefaultProposalHandler{
mempool: mp,
txVerifier: txVerifier,
txSelector: NewDefaultTxSelector(),
signerExtAdapter: mempool.NewDefaultSignerExtractionAdapter(),
}
}
/ SetTxSelector sets the TxSelector function on the DefaultProposalHandler.
func (h *DefaultProposalHandler)
SetTxSelector(ts TxSelector) {
h.txSelector = ts
}
/ PrepareProposalHandler returns the default implementation for processing an
/ ABCI proposal. The application's mempool is enumerated and all valid
/ transactions are added to the proposal. Transactions are valid if they:
/
/ 1)
Successfully encode to bytes.
/ 2)
Are valid (i.e. pass runTx, AnteHandler only).
/
/ Enumeration is halted once RequestPrepareProposal.MaxBytes of transactions is
/ reached or the mempool is exhausted.
/
/ Note:
/
/ - Step (2)
is identical to the validation step performed in
/ DefaultProcessProposal. It is very important that the same validation logic
/ is used in both steps, and applications must ensure that this is the case in
/ non-default handlers.
/
/ - If no mempool is set or if the mempool is a no-op mempool, the transactions
/ requested from CometBFT will simply be returned, which, by default, are in
/ FIFO order.
func (h *DefaultProposalHandler)
PrepareProposalHandler()
sdk.PrepareProposalHandler {
return func(ctx sdk.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) {
var maxBlockGas uint64
if b := ctx.ConsensusParams().Block; b != nil {
maxBlockGas = uint64(b.MaxGas)
}
defer h.txSelector.Clear()
/ If the mempool is nil or NoOp we simply return the transactions
/ requested from CometBFT, which, by default, should be in FIFO order.
/
/ Note, we still need to ensure the transactions returned respect req.MaxTxBytes.
_, isNoOp := h.mempool.(mempool.NoOpMempool)
if h.mempool == nil || isNoOp {
for _, txBz := range req.Txs {
tx, err := h.txVerifier.TxDecode(txBz)
if err != nil {
return nil, err
}
stop := h.txSelector.SelectTxForProposal(ctx, uint64(req.MaxTxBytes), maxBlockGas, tx, txBz)
if stop {
break
}
}
return &abci.ResponsePrepareProposal{
Txs: h.txSelector.SelectedTxs(ctx)
}, nil
}
selectedTxsSignersSeqs := make(map[string]uint64)
var (
resError error
selectedTxsNums int
invalidTxs []sdk.Tx / invalid txs to be removed out of the loop to avoid dead lock
)
mempool.SelectBy(ctx, h.mempool, req.Txs, func(memTx sdk.Tx)
bool {
unorderedTx, ok := memTx.(sdk.TxWithUnordered)
isUnordered := ok && unorderedTx.GetUnordered()
txSignersSeqs := make(map[string]uint64)
/ if the tx is unordered, we don't need to check the sequence, we just add it
if !isUnordered {
signerData, err := h.signerExtAdapter.GetSigners(memTx)
if err != nil {
/ propagate the error to the caller
resError = err
return false
}
/ If the signers aren't in selectedTxsSignersSeqs then we haven't seen them before
/ so we add them and continue given that we don't need to check the sequence.
shouldAdd := true
for _, signer := range signerData {
seq, ok := selectedTxsSignersSeqs[signer.Signer.String()]
if !ok {
txSignersSeqs[signer.Signer.String()] = signer.Sequence
continue
}
/ If we have seen this signer before in this block, we must make
/ sure that the current sequence is seq+1; otherwise is invalid
/ and we skip it.
if seq+1 != signer.Sequence {
shouldAdd = false
break
}
txSignersSeqs[signer.Signer.String()] = signer.Sequence
}
if !shouldAdd {
return true
}
}
/ NOTE: Since transaction verification was already executed in CheckTx,
/ which calls mempool.Insert, in theory everything in the pool should be
/ valid. But some mempool implementations may insert invalid txs, so we
/ check again.
txBz, err := h.txVerifier.PrepareProposalVerifyTx(memTx)
if err != nil {
invalidTxs = append(invalidTxs, memTx)
}
else {
stop := h.txSelector.SelectTxForProposal(ctx, uint64(req.MaxTxBytes), maxBlockGas, memTx, txBz)
if stop {
return false
}
txsLen := len(h.txSelector.SelectedTxs(ctx))
/ If the tx is unordered, we don't need to update the sender sequence.
if !isUnordered {
for sender, seq := range txSignersSeqs {
/ If txsLen != selectedTxsNums is true, it means that we've
/ added a new tx to the selected txs, so we need to update
/ the sequence of the sender.
if txsLen != selectedTxsNums {
selectedTxsSignersSeqs[sender] = seq
}
else if _, ok := selectedTxsSignersSeqs[sender]; !ok {
/ The transaction hasn't been added but it passed the
/ verification, so we know that the sequence is correct.
/ So we set this sender's sequence to seq-1, in order
/ to avoid unnecessary calls to PrepareProposalVerifyTx.
selectedTxsSignersSeqs[sender] = seq - 1
}
}
}
selectedTxsNums = txsLen
}
return true
})
if resError != nil {
return nil, resError
}
for _, tx := range invalidTxs {
err := h.mempool.Remove(tx)
if err != nil && !errors.Is(err, mempool.ErrTxNotFound) {
return nil, err
}
}
return &abci.ResponsePrepareProposal{
Txs: h.txSelector.SelectedTxs(ctx)
}, nil
}
}
/ ProcessProposalHandler returns the default implementation for processing an
/ ABCI proposal. Every transaction in the proposal must pass 2 conditions:
/
/ 1. The transaction bytes must decode to a valid transaction.
/ 2. The transaction must be valid (i.e. pass runTx, AnteHandler only)
/
/ If any transaction fails to pass either condition, the proposal is rejected.
/ Note that step (2)
is identical to the validation step performed in
/ DefaultPrepareProposal. It is very important that the same validation logic
/ is used in both steps, and applications must ensure that this is the case in
/ non-default handlers.
func (h *DefaultProposalHandler)
ProcessProposalHandler()
sdk.ProcessProposalHandler {
/ If the mempool is nil or NoOp we simply return ACCEPT,
/ because PrepareProposal may have included txs that could fail verification.
_, isNoOp := h.mempool.(mempool.NoOpMempool)
if h.mempool == nil || isNoOp {
return NoOpProcessProposal()
}
return func(ctx sdk.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) {
var totalTxGas uint64
var maxBlockGas int64
if b := ctx.ConsensusParams().Block; b != nil {
maxBlockGas = b.MaxGas
}
for _, txBytes := range req.Txs {
tx, err := h.txVerifier.ProcessProposalVerifyTx(txBytes)
if err != nil {
return &abci.ResponseProcessProposal{
Status: abci.ResponseProcessProposal_REJECT
}, nil
}
if maxBlockGas > 0 {
gasTx, ok := tx.(GasTx)
if ok {
totalTxGas += gasTx.GetGas()
}
if totalTxGas > uint64(maxBlockGas) {
return &abci.ResponseProcessProposal{
Status: abci.ResponseProcessProposal_REJECT
}, nil
}
}
}
return &abci.ResponseProcessProposal{
Status: abci.ResponseProcessProposal_ACCEPT
}, nil
}
}
/ NoOpPrepareProposal defines a no-op PrepareProposal handler. It will always
/ return the transactions sent by the client's request.
func NoOpPrepareProposal()
sdk.PrepareProposalHandler {
return func(_ sdk.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) {
return &abci.ResponsePrepareProposal{
Txs: req.Txs
}, nil
}
}
/ NoOpProcessProposal defines a no-op ProcessProposal Handler. It will always
/ return ACCEPT.
func NoOpProcessProposal()
sdk.ProcessProposalHandler {
return func(_ sdk.Context, _ *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) {
return &abci.ResponseProcessProposal{
Status: abci.ResponseProcessProposal_ACCEPT
}, nil
}
}
/ NoOpExtendVote defines a no-op ExtendVote handler. It will always return an
/ empty byte slice as the vote extension.
func NoOpExtendVote()
sdk.ExtendVoteHandler {
return func(_ sdk.Context, _ *abci.RequestExtendVote) (*abci.ResponseExtendVote, error) {
return &abci.ResponseExtendVote{
VoteExtension: []byte{
}}, nil
}
}
/ NoOpVerifyVoteExtensionHandler defines a no-op VerifyVoteExtension handler. It
/ will always return an ACCEPT status with no error.
func NoOpVerifyVoteExtensionHandler()
sdk.VerifyVoteExtensionHandler {
return func(_ sdk.Context, _ *abci.RequestVerifyVoteExtension) (*abci.ResponseVerifyVoteExtension, error) {
return &abci.ResponseVerifyVoteExtension{
Status: abci.ResponseVerifyVoteExtension_ACCEPT
}, nil
}
}
/ TxSelector defines a helper type that assists in selecting transactions during
/ mempool transaction selection in PrepareProposal. It keeps track of the total
/ number of bytes and total gas of the selected transactions. It also keeps
/ track of the selected transactions themselves.
type TxSelector interface {
/ SelectedTxs should return a copy of the selected transactions.
SelectedTxs(ctx context.Context) [][]byte
/ Clear should clear the TxSelector, nulling out all relevant fields.
Clear()
/ SelectTxForProposal should attempt to select a transaction for inclusion in
/ a proposal based on inclusion criteria defined by the TxSelector. It must
/ return <true> if the caller should halt the transaction selection loop
/ (typically over a mempool)
or <false> otherwise.
SelectTxForProposal(ctx context.Context, maxTxBytes, maxBlockGas uint64, memTx sdk.Tx, txBz []byte)
bool
}
type defaultTxSelector struct {
totalTxBytes uint64
totalTxGas uint64
selectedTxs [][]byte
}
func NewDefaultTxSelector()
TxSelector {
return &defaultTxSelector{
}
}
func (ts *defaultTxSelector)
SelectedTxs(_ context.Context) [][]byte {
txs := make([][]byte, len(ts.selectedTxs))
copy(txs, ts.selectedTxs)
return txs
}
func (ts *defaultTxSelector)
Clear() {
ts.totalTxBytes = 0
ts.totalTxGas = 0
ts.selectedTxs = nil
}
func (ts *defaultTxSelector)
SelectTxForProposal(_ context.Context, maxTxBytes, maxBlockGas uint64, memTx sdk.Tx, txBz []byte)
bool {
txSize := uint64(cmttypes.ComputeProtoSizeForTxs([]cmttypes.Tx{
txBz
}))
var txGasLimit uint64
if memTx != nil {
if gasTx, ok := memTx.(GasTx); ok {
txGasLimit = gasTx.GetGas()
}
}
/ only add the transaction to the proposal if we have enough capacity
if (txSize + ts.totalTxBytes) <= maxTxBytes {
/ If there is a max block gas limit, add the tx only if the limit has
/ not been met.
if maxBlockGas > 0 {
if (txGasLimit + ts.totalTxGas) <= maxBlockGas {
ts.totalTxGas += txGasLimit
ts.totalTxBytes += txSize
ts.selectedTxs = append(ts.selectedTxs, txBz)
}
}
else {
ts.totalTxBytes += txSize
ts.selectedTxs = append(ts.selectedTxs, txBz)
}
}
/ check if we've reached capacity; if so, we cannot select any more transactions
return ts.totalTxBytes >= maxTxBytes || (maxBlockGas > 0 && (ts.totalTxGas >= maxBlockGas))
}
VerifyVoteExtension
VerifyVoteExtension
allows an application to verify that the data returned by ExtendVote
is valid. This process MUST be deterministic. Moreover, the value of ResponseVerifyVoteExtension.status MUST exclusively depend on the parameters passed in the call to RequestVerifyVoteExtension, and the last committed Application state.
In the Cosmos-SDK this is implemented as a NoOp:
Copy
Ask AI
package baseapp
import (
"bytes"
"context"
"fmt"
"slices"
"github.com/cockroachdb/errors"
abci "github.com/cometbft/cometbft/abci/types"
cryptoenc "github.com/cometbft/cometbft/crypto/encoding"
cmtprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
cmttypes "github.com/cometbft/cometbft/types"
protoio "github.com/cosmos/gogoproto/io"
"github.com/cosmos/gogoproto/proto"
"cosmossdk.io/core/comet"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/mempool"
)
type (
/ ValidatorStore defines the interface contract required for verifying vote
/ extension signatures. Typically, this will be implemented by the x/staking
/ module, which has knowledge of the CometBFT public key.
ValidatorStore interface {
GetPubKeyByConsAddr(context.Context, sdk.ConsAddress) (cmtprotocrypto.PublicKey, error)
}
/ GasTx defines the contract that a transaction with a gas limit must implement.
GasTx interface {
GetGas()
uint64
}
)
/ ValidateVoteExtensions defines a helper function for verifying vote extension
/ signatures that may be passed or manually injected into a block proposal from
/ a proposer in PrepareProposal. It returns an error if any signature is invalid
/ or if unexpected vote extensions and/or signatures are found or less than 2/3
/ power is received.
/ NOTE: From v0.50.5 `currentHeight` and `chainID` arguments are ignored for fixing an issue.
/ They will be removed from the function in v0.51+.
func ValidateVoteExtensions(
ctx sdk.Context,
valStore ValidatorStore,
_ int64,
_ string,
extCommit abci.ExtendedCommitInfo,
)
error {
/ Get values from context
cp := ctx.ConsensusParams()
currentHeight := ctx.HeaderInfo().Height
chainID := ctx.HeaderInfo().ChainID
commitInfo := ctx.CometInfo().GetLastCommit()
/ Check that both extCommit + commit are ordered in accordance with vp/address.
if err := validateExtendedCommitAgainstLastCommit(extCommit, commitInfo); err != nil {
return err
}
/ Start checking vote extensions only **after** the vote extensions enable
/ height, because when `currentHeight == VoteExtensionsEnableHeight`
/ PrepareProposal doesn't get any vote extensions in its request.
extsEnabled := cp.Abci != nil && currentHeight > cp.Abci.VoteExtensionsEnableHeight && cp.Abci.VoteExtensionsEnableHeight != 0
marshalDelimitedFn := func(msg proto.Message) ([]byte, error) {
var buf bytes.Buffer
if err := protoio.NewDelimitedWriter(&buf).WriteMsg(msg); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var (
/ Total voting power of all vote extensions.
totalVP int64
/ Total voting power of all validators that submitted valid vote extensions.
sumVP int64
)
for _, vote := range extCommit.Votes {
totalVP += vote.Validator.Power
/ Only check + include power if the vote is a commit vote. There must be super-majority, otherwise the
/ previous block (the block the vote is for)
could not have been committed.
if vote.BlockIdFlag != cmtproto.BlockIDFlagCommit {
continue
}
if !extsEnabled {
if len(vote.VoteExtension) > 0 {
return fmt.Errorf("vote extensions disabled; received non-empty vote extension at height %d", currentHeight)
}
if len(vote.ExtensionSignature) > 0 {
return fmt.Errorf("vote extensions disabled; received non-empty vote extension signature at height %d", currentHeight)
}
continue
}
if len(vote.ExtensionSignature) == 0 {
return fmt.Errorf("vote extensions enabled; received empty vote extension signature at height %d", currentHeight)
}
valConsAddr := sdk.ConsAddress(vote.Validator.Address)
pubKeyProto, err := valStore.GetPubKeyByConsAddr(ctx, valConsAddr)
if err != nil {
return fmt.Errorf("failed to get validator %X public key: %w", valConsAddr, err)
}
cmtPubKey, err := cryptoenc.PubKeyFromProto(pubKeyProto)
if err != nil {
return fmt.Errorf("failed to convert validator %X public key: %w", valConsAddr, err)
}
cve := cmtproto.CanonicalVoteExtension{
Extension: vote.VoteExtension,
Height: currentHeight - 1, / the vote extension was signed in the previous height
Round: int64(extCommit.Round),
ChainId: chainID,
}
extSignBytes, err := marshalDelimitedFn(&cve)
if err != nil {
return fmt.Errorf("failed to encode CanonicalVoteExtension: %w", err)
}
if !cmtPubKey.VerifySignature(extSignBytes, vote.ExtensionSignature) {
return fmt.Errorf("failed to verify validator %X vote extension signature", valConsAddr)
}
sumVP += vote.Validator.Power
}
/ This check is probably unnecessary, but better safe than sorry.
if totalVP <= 0 {
return fmt.Errorf("total voting power must be positive, got: %d", totalVP)
}
/ If the sum of the voting power has not reached (2/3 + 1)
we need to error.
if requiredVP := ((totalVP * 2) / 3) + 1; sumVP < requiredVP {
return fmt.Errorf(
"insufficient cumulative voting power received to verify vote extensions; got: %d, expected: >=%d",
sumVP, requiredVP,
)
}
return nil
}
/ validateExtendedCommitAgainstLastCommit validates an ExtendedCommitInfo against a LastCommit. Specifically,
/ it checks that the ExtendedCommit + LastCommit (for the same height), are consistent with each other + that
/ they are ordered correctly (by voting power)
in accordance with
/ [comet](https://github.com/cometbft/cometbft/blob/4ce0277b35f31985bbf2c25d3806a184a4510010/types/validator_set.go#L784).
func validateExtendedCommitAgainstLastCommit(ec abci.ExtendedCommitInfo, lc comet.CommitInfo)
error {
/ check that the rounds are the same
if ec.Round != lc.Round() {
return fmt.Errorf("extended commit round %d does not match last commit round %d", ec.Round, lc.Round())
}
/ check that the # of votes are the same
if len(ec.Votes) != lc.Votes().Len() {
return fmt.Errorf("extended commit votes length %d does not match last commit votes length %d", len(ec.Votes), lc.Votes().Len())
}
/ check sort order of extended commit votes
if !slices.IsSortedFunc(ec.Votes, func(vote1, vote2 abci.ExtendedVoteInfo)
int {
if vote1.Validator.Power == vote2.Validator.Power {
return bytes.Compare(vote1.Validator.Address, vote2.Validator.Address) / addresses sorted in ascending order (used to break vp conflicts)
}
return -int(vote1.Validator.Power - vote2.Validator.Power) / vp sorted in descending order
}) {
return fmt.Errorf("extended commit votes are not sorted by voting power")
}
addressCache := make(map[string]struct{
}, len(ec.Votes))
/ check that consistency between LastCommit and ExtendedCommit
for i, vote := range ec.Votes {
/ cache addresses to check for duplicates
if _, ok := addressCache[string(vote.Validator.Address)]; ok {
return fmt.Errorf("extended commit vote address %X is duplicated", vote.Validator.Address)
}
addressCache[string(vote.Validator.Address)] = struct{
}{
}
if !bytes.Equal(vote.Validator.Address, lc.Votes().Get(i).Validator().Address()) {
return fmt.Errorf("extended commit vote address %X does not match last commit vote address %X", vote.Validator.Address, lc.Votes().Get(i).Validator().Address())
}
if vote.Validator.Power != lc.Votes().Get(i).Validator().Power() {
return fmt.Errorf("extended commit vote power %d does not match last commit vote power %d", vote.Validator.Power, lc.Votes().Get(i).Validator().Power())
}
}
return nil
}
type (
/ ProposalTxVerifier defines the interface that is implemented by BaseApp,
/ that any custom ABCI PrepareProposal and ProcessProposal handler can use
/ to verify a transaction.
ProposalTxVerifier interface {
PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error)
ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error)
TxDecode(txBz []byte) (sdk.Tx, error)
TxEncode(tx sdk.Tx) ([]byte, error)
}
/ DefaultProposalHandler defines the default ABCI PrepareProposal and
/ ProcessProposal handlers.
DefaultProposalHandler struct {
mempool mempool.Mempool
txVerifier ProposalTxVerifier
txSelector TxSelector
signerExtAdapter mempool.SignerExtractionAdapter
}
)
func NewDefaultProposalHandler(mp mempool.Mempool, txVerifier ProposalTxVerifier) *DefaultProposalHandler {
return &DefaultProposalHandler{
mempool: mp,
txVerifier: txVerifier,
txSelector: NewDefaultTxSelector(),
signerExtAdapter: mempool.NewDefaultSignerExtractionAdapter(),
}
}
/ SetTxSelector sets the TxSelector function on the DefaultProposalHandler.
func (h *DefaultProposalHandler)
SetTxSelector(ts TxSelector) {
h.txSelector = ts
}
/ PrepareProposalHandler returns the default implementation for processing an
/ ABCI proposal. The application's mempool is enumerated and all valid
/ transactions are added to the proposal. Transactions are valid if they:
/
/ 1)
Successfully encode to bytes.
/ 2)
Are valid (i.e. pass runTx, AnteHandler only).
/
/ Enumeration is halted once RequestPrepareProposal.MaxBytes of transactions is
/ reached or the mempool is exhausted.
/
/ Note:
/
/ - Step (2)
is identical to the validation step performed in
/ DefaultProcessProposal. It is very important that the same validation logic
/ is used in both steps, and applications must ensure that this is the case in
/ non-default handlers.
/
/ - If no mempool is set or if the mempool is a no-op mempool, the transactions
/ requested from CometBFT will simply be returned, which, by default, are in
/ FIFO order.
func (h *DefaultProposalHandler)
PrepareProposalHandler()
sdk.PrepareProposalHandler {
return func(ctx sdk.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) {
var maxBlockGas uint64
if b := ctx.ConsensusParams().Block; b != nil {
maxBlockGas = uint64(b.MaxGas)
}
defer h.txSelector.Clear()
/ If the mempool is nil or NoOp we simply return the transactions
/ requested from CometBFT, which, by default, should be in FIFO order.
/
/ Note, we still need to ensure the transactions returned respect req.MaxTxBytes.
_, isNoOp := h.mempool.(mempool.NoOpMempool)
if h.mempool == nil || isNoOp {
for _, txBz := range req.Txs {
tx, err := h.txVerifier.TxDecode(txBz)
if err != nil {
return nil, err
}
stop := h.txSelector.SelectTxForProposal(ctx, uint64(req.MaxTxBytes), maxBlockGas, tx, txBz)
if stop {
break
}
}
return &abci.ResponsePrepareProposal{
Txs: h.txSelector.SelectedTxs(ctx)
}, nil
}
selectedTxsSignersSeqs := make(map[string]uint64)
var (
resError error
selectedTxsNums int
invalidTxs []sdk.Tx / invalid txs to be removed out of the loop to avoid dead lock
)
mempool.SelectBy(ctx, h.mempool, req.Txs, func(memTx sdk.Tx)
bool {
unorderedTx, ok := memTx.(sdk.TxWithUnordered)
isUnordered := ok && unorderedTx.GetUnordered()
txSignersSeqs := make(map[string]uint64)
/ if the tx is unordered, we don't need to check the sequence, we just add it
if !isUnordered {
signerData, err := h.signerExtAdapter.GetSigners(memTx)
if err != nil {
/ propagate the error to the caller
resError = err
return false
}
/ If the signers aren't in selectedTxsSignersSeqs then we haven't seen them before
/ so we add them and continue given that we don't need to check the sequence.
shouldAdd := true
for _, signer := range signerData {
seq, ok := selectedTxsSignersSeqs[signer.Signer.String()]
if !ok {
txSignersSeqs[signer.Signer.String()] = signer.Sequence
continue
}
/ If we have seen this signer before in this block, we must make
/ sure that the current sequence is seq+1; otherwise is invalid
/ and we skip it.
if seq+1 != signer.Sequence {
shouldAdd = false
break
}
txSignersSeqs[signer.Signer.String()] = signer.Sequence
}
if !shouldAdd {
return true
}
}
/ NOTE: Since transaction verification was already executed in CheckTx,
/ which calls mempool.Insert, in theory everything in the pool should be
/ valid. But some mempool implementations may insert invalid txs, so we
/ check again.
txBz, err := h.txVerifier.PrepareProposalVerifyTx(memTx)
if err != nil {
invalidTxs = append(invalidTxs, memTx)
}
else {
stop := h.txSelector.SelectTxForProposal(ctx, uint64(req.MaxTxBytes), maxBlockGas, memTx, txBz)
if stop {
return false
}
txsLen := len(h.txSelector.SelectedTxs(ctx))
/ If the tx is unordered, we don't need to update the sender sequence.
if !isUnordered {
for sender, seq := range txSignersSeqs {
/ If txsLen != selectedTxsNums is true, it means that we've
/ added a new tx to the selected txs, so we need to update
/ the sequence of the sender.
if txsLen != selectedTxsNums {
selectedTxsSignersSeqs[sender] = seq
}
else if _, ok := selectedTxsSignersSeqs[sender]; !ok {
/ The transaction hasn't been added but it passed the
/ verification, so we know that the sequence is correct.
/ So we set this sender's sequence to seq-1, in order
/ to avoid unnecessary calls to PrepareProposalVerifyTx.
selectedTxsSignersSeqs[sender] = seq - 1
}
}
}
selectedTxsNums = txsLen
}
return true
})
if resError != nil {
return nil, resError
}
for _, tx := range invalidTxs {
err := h.mempool.Remove(tx)
if err != nil && !errors.Is(err, mempool.ErrTxNotFound) {
return nil, err
}
}
return &abci.ResponsePrepareProposal{
Txs: h.txSelector.SelectedTxs(ctx)
}, nil
}
}
/ ProcessProposalHandler returns the default implementation for processing an
/ ABCI proposal. Every transaction in the proposal must pass 2 conditions:
/
/ 1. The transaction bytes must decode to a valid transaction.
/ 2. The transaction must be valid (i.e. pass runTx, AnteHandler only)
/
/ If any transaction fails to pass either condition, the proposal is rejected.
/ Note that step (2)
is identical to the validation step performed in
/ DefaultPrepareProposal. It is very important that the same validation logic
/ is used in both steps, and applications must ensure that this is the case in
/ non-default handlers.
func (h *DefaultProposalHandler)
ProcessProposalHandler()
sdk.ProcessProposalHandler {
/ If the mempool is nil or NoOp we simply return ACCEPT,
/ because PrepareProposal may have included txs that could fail verification.
_, isNoOp := h.mempool.(mempool.NoOpMempool)
if h.mempool == nil || isNoOp {
return NoOpProcessProposal()
}
return func(ctx sdk.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) {
var totalTxGas uint64
var maxBlockGas int64
if b := ctx.ConsensusParams().Block; b != nil {
maxBlockGas = b.MaxGas
}
for _, txBytes := range req.Txs {
tx, err := h.txVerifier.ProcessProposalVerifyTx(txBytes)
if err != nil {
return &abci.ResponseProcessProposal{
Status: abci.ResponseProcessProposal_REJECT
}, nil
}
if maxBlockGas > 0 {
gasTx, ok := tx.(GasTx)
if ok {
totalTxGas += gasTx.GetGas()
}
if totalTxGas > uint64(maxBlockGas) {
return &abci.ResponseProcessProposal{
Status: abci.ResponseProcessProposal_REJECT
}, nil
}
}
}
return &abci.ResponseProcessProposal{
Status: abci.ResponseProcessProposal_ACCEPT
}, nil
}
}
/ NoOpPrepareProposal defines a no-op PrepareProposal handler. It will always
/ return the transactions sent by the client's request.
func NoOpPrepareProposal()
sdk.PrepareProposalHandler {
return func(_ sdk.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) {
return &abci.ResponsePrepareProposal{
Txs: req.Txs
}, nil
}
}
/ NoOpProcessProposal defines a no-op ProcessProposal Handler. It will always
/ return ACCEPT.
func NoOpProcessProposal()
sdk.ProcessProposalHandler {
return func(_ sdk.Context, _ *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) {
return &abci.ResponseProcessProposal{
Status: abci.ResponseProcessProposal_ACCEPT
}, nil
}
}
/ NoOpExtendVote defines a no-op ExtendVote handler. It will always return an
/ empty byte slice as the vote extension.
func NoOpExtendVote()
sdk.ExtendVoteHandler {
return func(_ sdk.Context, _ *abci.RequestExtendVote) (*abci.ResponseExtendVote, error) {
return &abci.ResponseExtendVote{
VoteExtension: []byte{
}}, nil
}
}
/ NoOpVerifyVoteExtensionHandler defines a no-op VerifyVoteExtension handler. It
/ will always return an ACCEPT status with no error.
func NoOpVerifyVoteExtensionHandler()
sdk.VerifyVoteExtensionHandler {
return func(_ sdk.Context, _ *abci.RequestVerifyVoteExtension) (*abci.ResponseVerifyVoteExtension, error) {
return &abci.ResponseVerifyVoteExtension{
Status: abci.ResponseVerifyVoteExtension_ACCEPT
}, nil
}
}
/ TxSelector defines a helper type that assists in selecting transactions during
/ mempool transaction selection in PrepareProposal. It keeps track of the total
/ number of bytes and total gas of the selected transactions. It also keeps
/ track of the selected transactions themselves.
type TxSelector interface {
/ SelectedTxs should return a copy of the selected transactions.
SelectedTxs(ctx context.Context) [][]byte
/ Clear should clear the TxSelector, nulling out all relevant fields.
Clear()
/ SelectTxForProposal should attempt to select a transaction for inclusion in
/ a proposal based on inclusion criteria defined by the TxSelector. It must
/ return <true> if the caller should halt the transaction selection loop
/ (typically over a mempool)
or <false> otherwise.
SelectTxForProposal(ctx context.Context, maxTxBytes, maxBlockGas uint64, memTx sdk.Tx, txBz []byte)
bool
}
type defaultTxSelector struct {
totalTxBytes uint64
totalTxGas uint64
selectedTxs [][]byte
}
func NewDefaultTxSelector()
TxSelector {
return &defaultTxSelector{
}
}
func (ts *defaultTxSelector)
SelectedTxs(_ context.Context) [][]byte {
txs := make([][]byte, len(ts.selectedTxs))
copy(txs, ts.selectedTxs)
return txs
}
func (ts *defaultTxSelector)
Clear() {
ts.totalTxBytes = 0
ts.totalTxGas = 0
ts.selectedTxs = nil
}
func (ts *defaultTxSelector)
SelectTxForProposal(_ context.Context, maxTxBytes, maxBlockGas uint64, memTx sdk.Tx, txBz []byte)
bool {
txSize := uint64(cmttypes.ComputeProtoSizeForTxs([]cmttypes.Tx{
txBz
}))
var txGasLimit uint64
if memTx != nil {
if gasTx, ok := memTx.(GasTx); ok {
txGasLimit = gasTx.GetGas()
}
}
/ only add the transaction to the proposal if we have enough capacity
if (txSize + ts.totalTxBytes) <= maxTxBytes {
/ If there is a max block gas limit, add the tx only if the limit has
/ not been met.
if maxBlockGas > 0 {
if (txGasLimit + ts.totalTxGas) <= maxBlockGas {
ts.totalTxGas += txGasLimit
ts.totalTxBytes += txSize
ts.selectedTxs = append(ts.selectedTxs, txBz)
}
}
else {
ts.totalTxBytes += txSize
ts.selectedTxs = append(ts.selectedTxs, txBz)
}
}
/ check if we've reached capacity; if so, we cannot select any more transactions
return ts.totalTxBytes >= maxTxBytes || (maxBlockGas > 0 && (ts.totalTxGas >= maxBlockGas))
}