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