baseapp: ctxCheck and ctxDeliver, begin/endBlocker

This commit is contained in:
Ethan Buchman 2018-02-17 18:10:55 -05:00
parent 8fc12a5265
commit 958a632eed
5 changed files with 92 additions and 71 deletions

View File

@ -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,
}

View File

@ -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{

View File

@ -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{}
}

View File

@ -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)

View File

@ -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