package baseapp import ( "fmt" "io" "os" "reflect" "runtime/debug" "strings" "errors" "github.com/gogo/protobuf/proto" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/tmhash" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" ) // Key to store the consensus params in the main store. var mainConsensusParamsKey = []byte("consensus_params") // Enum mode for app.runTx type runTxMode uint8 const ( // Check a transaction runTxModeCheck runTxMode = iota // Simulate a transaction runTxModeSimulate runTxMode = iota // Deliver a transaction runTxModeDeliver runTxMode = iota // MainStoreKey is the string representation of the main store MainStoreKey = "main" ) // BaseApp reflects the ABCI application implementation. type BaseApp struct { // 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 router Router // handle any kind of message queryRouter 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 state // checkState is set on initialization and reset on Commit. // deliverState is set in InitChain and BeginBlock and cleared on Commit. // See methods setCheckState and setDeliverState. checkState *state // for CheckTx deliverState *state // for DeliverTx voteInfos []abci.VoteInfo // absent validators from begin block // 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 // height at which to halt the chain and gracefully shutdown haltHeight uint64 // application's version string appVersion string } var _ abci.Application = (*BaseApp)(nil) // 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), router: NewRouter(), queryRouter: NewQueryRouter(), txDecoder: txDecoder, fauxMerkleMode: false, } for _, option := range options { option(app) } 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 } // SetCommitMultiStoreTracer sets the store tracer on the BaseApp's underlying // CommitMultiStore. func (app *BaseApp) SetCommitMultiStoreTracer(w io.Writer) { app.cms.SetTracer(w) } // 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()) } } } // 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.cms.LoadLatestVersion() if err != nil { return err } return app.initFromMainStore(baseKey) } // 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 `gaiad export`, 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(height uint64) { app.haltHeight = height } // Router returns the router of the BaseApp. func (app *BaseApp) Router() 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() 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 checkState with the cached multistore and // the context wrapping it. // It is called by InitChain() and 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), } } // setCheckState sets checkState with the cached multistore and // the context wrapping it. // It is called by InitChain() and BeginBlock(), // and deliverState is set 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) } } // ---------------------------------------------------------------------------- // ABCI // Info implements the ABCI interface. func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo { lastCommitID := app.cms.LastCommitID() return abci.ResponseInfo{ Data: app.name, LastBlockHeight: lastCommitID.Version, LastBlockAppHash: lastCommitID.Hash, } } // SetOption implements the ABCI interface. func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOption) { // TODO: Implement! return } // InitChain implements the ABCI interface. It runs the initialization logic // directly on the CommitMultiStore. func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) { // stash the consensus params in the cms main store and memoize if req.ConsensusParams != nil { app.setConsensusParams(req.ConsensusParams) app.storeConsensusParams(req.ConsensusParams) } initHeader := abci.Header{ChainID: req.ChainId, Time: req.Time} // initialize the deliver state and check state with a correct header app.setDeliverState(initHeader) app.setCheckState(initHeader) if app.initChainer == nil { return } // add block gas meter for any genesis transactions (allow infinite gas) app.deliverState.ctx = app.deliverState.ctx. WithBlockGasMeter(sdk.NewInfiniteGasMeter()) res = app.initChainer(app.deliverState.ctx, req) // NOTE: We don't commit, but BeginBlock for block 1 starts from this // deliverState. return } // 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{} } // FilterPeerByIDfilters peers by node ID. func (app *BaseApp) FilterPeerByID(info string) abci.ResponseQuery { if app.idPeerFilter != nil { return app.idPeerFilter(info) } return abci.ResponseQuery{} } // Splits a string path using the delimiter '/'. // e.g. "this/is/funny" becomes []string{"this", "is", "funny"} func splitPath(requestPath string) (path []string) { path = strings.Split(requestPath, "/") // first element is empty string if len(path) > 0 && path[0] == "" { path = path[1:] } return path } // Query implements the ABCI interface. It delegates to CommitMultiStore if it // implements Queryable. func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) { path := splitPath(req.Path) if len(path) == 0 { msg := "no query path provided" return sdk.ErrUnknownRequest(msg).QueryResult() } switch path[0] { // "/app" prefix for special application queries case "app": return handleQueryApp(app, path, req) case "store": return handleQueryStore(app, path, req) case "p2p": return handleQueryP2P(app, path, req) case "custom": return handleQueryCustom(app, path, req) } msg := "unknown query path" return sdk.ErrUnknownRequest(msg).QueryResult() } func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) { if len(path) >= 2 { var result sdk.Result switch path[1] { case "simulate": txBytes := req.Data tx, err := app.txDecoder(txBytes) if err != nil { result = err.Result() } else { result = app.Simulate(txBytes, tx) } case "version": return abci.ResponseQuery{ Code: uint32(sdk.CodeOK), Codespace: string(sdk.CodespaceRoot), Value: []byte(app.appVersion), } default: result = sdk.ErrUnknownRequest(fmt.Sprintf("Unknown query: %s", path)).Result() } value := codec.Cdc.MustMarshalBinaryLengthPrefixed(result) return abci.ResponseQuery{ Code: uint32(sdk.CodeOK), Codespace: string(sdk.CodespaceRoot), Value: value, } } msg := "Expected second parameter to be either simulate or version, neither was present" return sdk.ErrUnknownRequest(msg).QueryResult() } func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) { // "/store" prefix for store queries queryable, ok := app.cms.(sdk.Queryable) if !ok { msg := "multistore doesn't support queries" return sdk.ErrUnknownRequest(msg).QueryResult() } req.Path = "/" + strings.Join(path[1:], "/") return queryable.Query(req) } func handleQueryP2P(app *BaseApp, path []string, _ abci.RequestQuery) (res abci.ResponseQuery) { // "/p2p" prefix for p2p queries if len(path) >= 4 { cmd, typ, arg := path[1], path[2], path[3] switch cmd { case "filter": switch typ { case "addr": return app.FilterPeerByAddrPort(arg) case "id": return app.FilterPeerByID(arg) } default: msg := "Expected second parameter to be filter" return sdk.ErrUnknownRequest(msg).QueryResult() } } msg := "Expected path is p2p filter " return sdk.ErrUnknownRequest(msg).QueryResult() } func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) { // path[0] should be "custom" because "/custom" prefix is required for keeper // queries. // // The queryRouter routes using path[1]. For example, in the path // "custom/gov/proposal", queryRouter routes using "gov". if len(path) < 2 || path[1] == "" { return sdk.ErrUnknownRequest("No route for custom query specified").QueryResult() } querier := app.queryRouter.Route(path[1]) if querier == nil { return sdk.ErrUnknownRequest(fmt.Sprintf("no custom querier found for route %s", path[1])).QueryResult() } // cache wrap the commit-multistore for safety ctx := sdk.NewContext( app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.logger, ).WithMinGasPrices(app.minGasPrices) // Passes the rest of the path as an argument to the querier. // // For example, in the path "custom/gov/proposal/test", the gov querier gets // []string{"proposal", "test"} as the path. resBytes, err := querier(ctx, path[2:], req) if err != nil { return abci.ResponseQuery{ Code: uint32(err.Code()), Codespace: string(err.Codespace()), Log: err.ABCILog(), } } return abci.ResponseQuery{ Code: uint32(sdk.CodeOK), Value: resBytes, } } 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 } // BeginBlock implements the ABCI application interface. func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) { if app.cms.TracingEnabled() { app.cms.SetTracingContext(sdk.TraceContext( map[string]interface{}{"blockHeight": req.Header.Height}, )) } if err := app.validateHeight(req); err != nil { panic(err) } // Initialize the DeliverTx state. If this is the first block, it should // already be initialized in InitChain. Otherwise app.deliverState will be // nil, since it is reset on Commit. if app.deliverState == nil { app.setDeliverState(req.Header) } else { // In the first block, app.deliverState.ctx will already be initialized // by InitChain. Context is now updated with Header information. app.deliverState.ctx = app.deliverState.ctx. WithBlockHeader(req.Header). WithBlockHeight(req.Header.Height) } // add block gas meter var gasMeter sdk.GasMeter if maxGas := app.getMaximumBlockGas(); maxGas > 0 { gasMeter = sdk.NewGasMeter(maxGas) } else { gasMeter = sdk.NewInfiniteGasMeter() } app.deliverState.ctx = app.deliverState.ctx.WithBlockGasMeter(gasMeter) if app.beginBlocker != nil { res = app.beginBlocker(app.deliverState.ctx, req) } // set the signed validators for addition to context in deliverTx app.voteInfos = req.LastCommitInfo.GetVotes() return } // CheckTx implements the ABCI interface. It runs the "basic checks" to see // whether or not a transaction can possibly be executed, first decoding, then // the ante handler (which checks signatures/fees/ValidateBasic), then finally // the route match to see whether a handler exists. // // NOTE:CheckTx does not run the actual Msg handler function(s). func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) { var result sdk.Result tx, err := app.txDecoder(txBytes) if err != nil { result = err.Result() } else { result = app.runTx(runTxModeCheck, txBytes, tx) } return abci.ResponseCheckTx{ Code: uint32(result.Code), Data: result.Data, Log: result.Log, GasWanted: int64(result.GasWanted), // TODO: Should type accept unsigned ints? GasUsed: int64(result.GasUsed), // TODO: Should type accept unsigned ints? Tags: result.Tags, } } // DeliverTx implements the ABCI interface. func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) { var result sdk.Result tx, err := app.txDecoder(txBytes) if err != nil { result = err.Result() } else { result = app.runTx(runTxModeDeliver, txBytes, tx) } return abci.ResponseDeliverTx{ Code: uint32(result.Code), Codespace: string(result.Codespace), Data: result.Data, Log: result.Log, GasWanted: int64(result.GasWanted), // TODO: Should type accept unsigned ints? GasUsed: int64(result.GasUsed), // TODO: Should type accept unsigned ints? Tags: result.Tags, } } // validateBasicTxMsgs executes basic validator calls for messages. func validateBasicTxMsgs(msgs []sdk.Msg) sdk.Error { if msgs == nil || len(msgs) == 0 { return sdk.ErrUnknownRequest("Tx.GetMsgs() must return at least one message in list") } for _, msg := range msgs { // Validate the Msg. err := msg.ValidateBasic() if err != nil { return err } } return nil } // retrieve the context for the tx w/ txBytes and other memoized values. func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) (ctx sdk.Context) { ctx = app.getState(mode).ctx. WithTxBytes(txBytes). WithVoteInfos(app.voteInfos). WithConsensusParams(app.consensusParams) if mode == runTxModeSimulate { ctx, _ = ctx.CacheContext() } return } // runMsgs iterates through all the messages and executes them. func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (result sdk.Result) { idxLogs := make([]sdk.ABCIMessageLog, 0, len(msgs)) // a list of JSON-encoded logs with msg index var data []byte // NOTE: we just append them all (?!) var tags sdk.Tags // also just append them all var code sdk.CodeType var codespace sdk.CodespaceType for msgIdx, msg := range msgs { // match message route msgRoute := msg.Route() handler := app.router.Route(msgRoute) if handler == nil { return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgRoute).Result() } var msgResult sdk.Result // skip actual execution for CheckTx mode if mode != runTxModeCheck { msgResult = handler(ctx, msg) } // NOTE: GasWanted is determined by ante handler and GasUsed by the GasMeter. // Result.Data must be length prefixed in order to separate each result data = append(data, msgResult.Data...) tags = append(tags, sdk.MakeTag(sdk.TagAction, msg.Type())) tags = append(tags, msgResult.Tags...) idxLog := sdk.ABCIMessageLog{MsgIndex: uint16(msgIdx), Log: msgResult.Log} // stop execution and return on first failed message if !msgResult.IsOK() { idxLog.Success = false idxLogs = append(idxLogs, idxLog) code = msgResult.Code codespace = msgResult.Codespace break } idxLog.Success = true idxLogs = append(idxLogs, idxLog) } logJSON := codec.Cdc.MustMarshalJSON(idxLogs) result = sdk.Result{ Code: code, Codespace: codespace, Data: data, Log: strings.TrimSpace(string(logJSON)), GasUsed: ctx.GasMeter().GasConsumed(), Tags: tags, } return result } // 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 == runTxModeCheck || mode == runTxModeSimulate { return app.checkState } return app.deliverState } // cacheTxContext returns a new context based off of the provided context with // a cache wrapped multi-store. func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) ( sdk.Context, sdk.CacheMultiStore) { ms := ctx.MultiStore() // TODO: https://github.com/cosmos/cosmos-sdk/issues/2824 msCache := ms.CacheMultiStore() if msCache.TracingEnabled() { msCache = msCache.SetTracingContext( sdk.TraceContext( map[string]interface{}{ "txHash": fmt.Sprintf("%X", tmhash.Sum(txBytes)), }, ), ).(sdk.CacheMultiStore) } return ctx.WithMultiStore(msCache), msCache } // runTx processes a transaction. The transactions is proccessed via an // anteHandler. The provided txBytes may be nil in some cases, eg. in tests. For // further details on transaction execution, reference the BaseApp SDK // documentation. func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk.Result) { // NOTE: GasWanted should be returned by the AnteHandler. GasUsed is // determined by the GasMeter. We need access to the context to get the gas // meter so we initialize upfront. var gasWanted uint64 ctx := app.getContextForTx(mode, txBytes) ms := ctx.MultiStore() // only run the tx if there is block gas remaining if mode == runTxModeDeliver && ctx.BlockGasMeter().IsOutOfGas() { result = sdk.ErrOutOfGas("no block gas left to run tx").Result() return } var startingGas uint64 if mode == runTxModeDeliver { startingGas = ctx.BlockGasMeter().GasConsumed() } defer func() { if r := recover(); r != nil { switch rType := r.(type) { case sdk.ErrorOutOfGas: log := fmt.Sprintf( "out of gas in location: %v; gasWanted: %d, gasUsed: %d", rType.Descriptor, gasWanted, ctx.GasMeter().GasConsumed(), ) result = sdk.ErrOutOfGas(log).Result() default: log := fmt.Sprintf("recovered: %v\nstack:\n%v", r, string(debug.Stack())) result = sdk.ErrInternal(log).Result() } } result.GasWanted = gasWanted result.GasUsed = ctx.GasMeter().GasConsumed() }() // If BlockGasMeter() panics it will be caught by the above recover and will // return an error - in any case BlockGasMeter will consume gas past the limit. // // NOTE: This must exist in a separate defer function for the above recovery // to recover from this one. defer func() { if mode == runTxModeDeliver { ctx.BlockGasMeter().ConsumeGas( ctx.GasMeter().GasConsumedToLimit(), "block gas meter", ) if ctx.BlockGasMeter().GasConsumed() < startingGas { panic(sdk.ErrorGasOverflow{Descriptor: "tx gas summation"}) } } }() var msgs = tx.GetMsgs() if err := validateBasicTxMsgs(msgs); err != nil { return err.Result() } if app.anteHandler != nil { var anteCtx sdk.Context var msCache sdk.CacheMultiStore // Cache wrap context before anteHandler call in case it aborts. // This is required for both CheckTx and DeliverTx. // Ref: https://github.com/cosmos/cosmos-sdk/issues/2772 // // NOTE: Alternatively, we could require that anteHandler ensures that // writes do not happen if aborted/failed. This may have some // performance benefits, but it'll be more difficult to get right. anteCtx, msCache = app.cacheTxContext(ctx, txBytes) newCtx, result, abort := app.anteHandler(anteCtx, tx, mode == runTxModeSimulate) if !newCtx.IsZero() { // At this point, newCtx.MultiStore() is cache-wrapped, or something else // replaced by the ante handler. We want the original multistore, not one // which was cache-wrapped for the ante handler. // // Also, in the case of the tx aborting, we need to track gas consumed via // the instantiated gas meter in the ante handler, so we update the context // prior to returning. ctx = newCtx.WithMultiStore(ms) } gasWanted = result.GasWanted if abort { return result } msCache.Write() } if mode == runTxModeCheck { return } // Create a new context based off of the existing context with a cache wrapped // multi-store in case message processing fails. runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes) result = app.runMsgs(runMsgCtx, msgs, mode) result.GasWanted = gasWanted if mode == runTxModeSimulate { return } // only update state if all messages pass if result.IsOK() { msCache.Write() } return } // EndBlock implements the ABCI interface. func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) { if app.deliverState.ms.TracingEnabled() { app.deliverState.ms = app.deliverState.ms.SetTracingContext(nil).(sdk.CacheMultiStore) } if app.endBlocker != nil { res = app.endBlocker(app.deliverState.ctx, req) } return } // 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() (res abci.ResponseCommit) { header := app.deliverState.ctx.BlockHeader() // write the Deliver state and commit the MultiStore app.deliverState.ms.Write() commitID := app.cms.Commit() app.logger.Debug("Commit synced", "commit", fmt.Sprintf("%X", commitID)) // Reset the Check state to the latest committed. // // NOTE: This is safe because Tendermint holds a lock on the mempool for // Commit. Use the header from this latest block. app.setCheckState(header) // empty/reset the deliver state app.deliverState = nil defer func() { if app.haltHeight > 0 && uint64(header.Height) == app.haltHeight { app.logger.Info("halting node per configuration", "height", app.haltHeight) os.Exit(0) } }() return abci.ResponseCommit{ Data: commitID.Hash, } } // ---------------------------------------------------------------------------- // State type state struct { ms sdk.CacheMultiStore ctx sdk.Context } func (st *state) CacheMultiStore() sdk.CacheMultiStore { return st.ms.CacheMultiStore() } func (st *state) Context() sdk.Context { return st.ctx }