diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index f455b31843..c969ed0ea8 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -20,25 +20,31 @@ var mainHeaderKey = []byte("header") // The ABCI application type BaseApp struct { - logger log.Logger - name string // application name from abci.Info - db dbm.DB // common DB backend - cms sdk.CommitMultiStore // Main (uncached) state - txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx - initChainer sdk.InitChainer // - anteHandler sdk.AnteHandler // ante handler for fee and auth - router Router // handle any kind of message + // 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 + + // may be nil + txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx + 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 + anteHandler sdk.AnteHandler // ante handler for fee and auth //-------------------- // Volatile - // .msCheck and .header are set on initialization. - // .msDeliver is only set (and reset) in BeginBlock. - // .header and .valUpdates are also reset in BeginBlock. - // .msCheck is only reset in Commit. + // .msCheck and .ctxCheck are set on initialization and reset on Commit. + // .msDeliver and .ctxDeliver are (re-)set on BeginBlock. + // .valUpdates accumulate in DeliverTx and reset in BeginBlock. + // QUESTION: should we put valUpdates in the ctxDeliver? - header abci.Header // current block header msCheck sdk.CacheMultiStore // CheckTx state, a cache-wrap of `.cms` msDeliver sdk.CacheMultiStore // DeliverTx state, a cache-wrap of `.cms` + ctxCheck sdk.Context // CheckTx context + ctxDeliver sdk.Context // DeliverTx context valUpdates []abci.Validator // cached validator changes from DeliverTx } @@ -143,23 +149,19 @@ func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error { } } - // set BaseApp state - app.header = header + // initialize Check state app.msCheck = app.cms.CacheMultiStore() - app.msDeliver = nil - app.valUpdates = nil + app.ctxCheck = app.NewContext(true, abci.Header{}) return nil } -// NewContext returns a new Context suitable for AnteHandler and Handler processing. -// NOTE: header is empty for checkTx -// NOTE: txBytes may be nil, for instance in tests (using app.Check or app.Deliver directly). -func (app *BaseApp) NewContext(isCheckTx bool, txBytes []byte) sdk.Context { - store := app.getMultiStore(isCheckTx) - // XXX CheckTx can't safely get the header - header := abci.Header{} - return sdk.NewContext(store, header, isCheckTx, txBytes) +// NewContext returns a new Context with the correct store, the given header, and nil txBytes. +func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context { + if isCheckTx { + return sdk.NewContext(app.msCheck, header, true, nil) + } + return sdk.NewContext(app.msDeliver, header, false, nil) } //---------------------------------------- @@ -195,11 +197,8 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC // NOTE: we're writing to the cms directly, without a CacheWrap ctx := sdk.NewContext(app.cms, abci.Header{}, false, nil) - err := app.initChainer(ctx, req) - if err != nil { - // TODO: something better https://github.com/cosmos/cosmos-sdk/issues/468 - cmn.Exit(fmt.Sprintf("error initializing application genesis state: %v", err)) - } + res = app.initChainer(ctx, req) + // TODO: handle error https://github.com/cosmos/cosmos-sdk/issues/468 // XXX this commits everything and bumps the version. // https://github.com/cosmos/cosmos-sdk/issues/442#issuecomment-366470148 @@ -221,10 +220,12 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) { // Implements ABCI func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) { - // NOTE: For consistency we should unset these upon EndBlock. - app.header = req.Header app.msDeliver = app.cms.CacheMultiStore() + app.ctxDeliver = app.NewContext(false, req.Header) app.valUpdates = nil + if app.beginBlocker != nil { + res = app.beginBlocker(app.ctxDeliver, req) + } return } @@ -317,8 +318,13 @@ func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk return err.Result() } - // Construct a Context. - var ctx = app.NewContext(isCheckTx, txBytes) + // Get the context + var ctx sdk.Context + if isCheckTx { + ctx = app.ctxCheck.WithTxBytes(txBytes) + } else { + ctx = app.ctxDeliver.WithTxBytes(txBytes) + } // TODO: override default ante handler w/ custom ante handler. @@ -332,7 +338,7 @@ func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk } // CacheWrap app.msDeliver in case it fails. - msCache := app.getMultiStore(false).CacheMultiStore() + msCache := app.msDeliver.CacheMultiStore() ctx = ctx.WithMultiStore(msCache) // Match and run route. @@ -350,19 +356,30 @@ func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk // Implements ABCI func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) { - res.ValidatorUpdates = app.valUpdates - app.valUpdates = nil - app.msDeliver = nil + if app.endBlocker != nil { + res = app.endBlocker(app.ctxDeliver, req) + } else { + res.ValidatorUpdates = app.valUpdates + } return } // Implements ABCI func (app *BaseApp) Commit() (res abci.ResponseCommit) { + // Write the Deliver state and commit the MultiStore app.msDeliver.Write() commitID := app.cms.Commit() app.logger.Debug("Commit synced", "commit", commitID, ) + + // Reset the Check state + // NOTE: safe because Tendermint holds a lock on the mempool for Commit. + // Use the header from this latest block. + header := app.ctxDeliver.BlockHeader() + app.msCheck = app.cms.CacheMultiStore() + app.ctxCheck = app.NewContext(true, header) + return abci.ResponseCommit{ Data: commitID.Hash, } diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index fb136904cc..5bb67834e5 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -65,10 +65,10 @@ func TestInitChainer(t *testing.T) { key, value := []byte("hello"), []byte("goodbye") // initChainer sets a value in the store - var initChainer sdk.InitChainer = func(ctx sdk.Context, req abci.RequestInitChain) sdk.Error { + var initChainer sdk.InitChainer = func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { store := ctx.KVStore(capKey) store.Set(key, value) - return nil + return abci.ResponseInitChain{} } query := abci.RequestQuery{ diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index b1f66009e4..b2c26addbe 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -57,8 +57,8 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp { app.Router().AddRoute("sketchy", sketchy.NewHandler()) // initialize BaseApp - app.SetTxDecoder() - app.SetInitChainer() + app.SetTxDecoder(app.txDecoder) + app.SetInitChainer(app.initChainer) app.MountStoresIAVL(app.capKeyMainStore, app.capKeyIBCStore) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper)) err := app.LoadLatestVersion(app.capKeyMainStore) @@ -78,37 +78,35 @@ func MakeTxCodec() *wire.Codec { } // custom logic for transaction decoding -func (app *BasecoinApp) SetTxDecoder() { - app.BaseApp.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) { - var tx = sdk.StdTx{} - // StdTx.Msg is an interface whose concrete - // types are registered in app/msgs.go. - err := app.cdc.UnmarshalBinary(txBytes, &tx) - if err != nil { - return nil, sdk.ErrTxParse("").TraceCause(err, "") - } - return tx, nil - }) +func (app *BasecoinApp) txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) { + var tx = sdk.StdTx{} + // StdTx.Msg is an interface whose concrete + // types are registered in app/msgs.go. + err := app.cdc.UnmarshalBinary(txBytes, &tx) + if err != nil { + return nil, sdk.ErrTxParse("").TraceCause(err, "") + } + return tx, nil } // custom logic for basecoin initialization -func (app *BasecoinApp) SetInitChainer() { - app.BaseApp.SetInitChainer(func(ctx sdk.Context, req abci.RequestInitChain) sdk.Error { - stateJSON := req.AppStateBytes +func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + stateJSON := req.AppStateBytes - genesisState := new(types.GenesisState) - err := json.Unmarshal(stateJSON, genesisState) + genesisState := new(types.GenesisState) + err := json.Unmarshal(stateJSON, genesisState) + if err != nil { + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 + // return sdk.ErrGenesisParse("").TraceCause(err, "") + } + + for _, gacc := range genesisState.Accounts { + acc, err := gacc.ToAppAccount() if err != nil { - return sdk.ErrGenesisParse("").TraceCause(err, "") + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 + // return sdk.ErrGenesisParse("").TraceCause(err, "") } - - for _, gacc := range genesisState.Accounts { - acc, err := gacc.ToAppAccount() - if err != nil { - return sdk.ErrGenesisParse("").TraceCause(err, "") - } - app.accountMapper.SetAccount(ctx, acc) - } - return nil - }) + app.accountMapper.SetAccount(ctx, acc) + } + return abci.ResponseInitChain{} } diff --git a/examples/basecoin/app/app_test.go b/examples/basecoin/app/app_test.go index 5ef1195c8f..9bcd1e7748 100644 --- a/examples/basecoin/app/app_test.go +++ b/examples/basecoin/app/app_test.go @@ -87,7 +87,7 @@ func TestGenesis(t *testing.T) { bapp.InitChain(abci.RequestInitChain{vals, stateBytes}) // a checkTx context - ctx := bapp.BaseApp.NewContext(true, nil) + ctx := bapp.BaseApp.NewContext(true, abci.Header{}) res1 := bapp.accountMapper.GetAccount(ctx, baseAcc.Address) assert.Equal(t, acc, res1) diff --git a/types/abci.go b/types/abci.go index 58f40dd673..8cee24e4e4 100644 --- a/types/abci.go +++ b/types/abci.go @@ -3,4 +3,10 @@ package types import abci "github.com/tendermint/abci/types" // initialize application state at genesis -type InitChainer func(ctx Context, req abci.RequestInitChain) Error +type InitChainer func(ctx Context, req abci.RequestInitChain) abci.ResponseInitChain + +// +type BeginBlocker func(ctx Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock + +// +type EndBlocker func(ctx Context, req abci.RequestEndBlock) abci.ResponseEndBlock