From f65215ad922f55862e6baf33ef2dfe714655962c Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 16 Oct 2017 19:21:43 +0200 Subject: [PATCH] Big cleanup of app dir StoreApp just the queries, BaseApp with handler/ticker Ticker now defined top level, as an interface, with context Name for info taken as parameter, start cmd uses commandline name Cleaner compisition of apps. --- app/app_test.go | 19 +-- app/base.go | 113 ++++++++++++++++ app/bc.go | 127 ------------------ app/genesis_test.go | 18 +-- app/{app.go => store.go} | 59 ++++---- app/{app_val_test.go => val_test.go} | 3 +- benchmarks/app_test.go | 12 +- client/query_test.go | 5 +- examples/basecoin/tests/cli/rpc.sh | 2 +- .../counter/plugins/counter/counter_test.go | 8 +- handler.go | 12 ++ server/commands/start.go | 28 ++-- 12 files changed, 202 insertions(+), 204 deletions(-) create mode 100644 app/base.go delete mode 100644 app/bc.go rename app/{app.go => store.go} (77%) rename app/{app_val_test.go => val_test.go} (96%) diff --git a/app/app_test.go b/app/app_test.go index 5be1320daf..0e90f65413 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -55,7 +55,7 @@ func DefaultHandler(feeDenom string) sdk.Handler { type appTest struct { t *testing.T chainID string - app *Basecoin + app *BaseApp acctIn *coin.AccountWithKey acctOut *coin.AccountWithKey } @@ -113,14 +113,9 @@ func (at *appTest) reset() { logger := log.TestingLogger() // logger := log.NewTracingLogger(log.NewTMLogger(os.Stdout)) - var err error - at.app, err = NewBasecoin( - DefaultHandler("mycoin"), - "", - 0, - logger.With("module", "app"), - ) + store, err := NewStoreApp("app-test", "", 0, logger) require.Nil(at.t, err, "%+v", err) + at.app = NewBaseApp(store, DefaultHandler("mycoin"), nil) _, err = at.app.InitState("base", "chain_id", at.chainID) require.Nil(at.t, err, "%+v", err) @@ -173,13 +168,9 @@ func TestInitState(t *testing.T) { require := require.New(t) logger := log.TestingLogger() - app, err := NewBasecoin( - DefaultHandler("atom"), - "", - 0, - logger.With("module", "app"), - ) + store, err := NewStoreApp("app-test", "", 0, logger) require.Nil(err, "%+v", err) + app := NewBaseApp(store, DefaultHandler("atom"), nil) //testing ChainID chainID := "testChain" diff --git a/app/base.go b/app/base.go new file mode 100644 index 0000000000..a0ddbb7e21 --- /dev/null +++ b/app/base.go @@ -0,0 +1,113 @@ +package app + +import ( + "fmt" + + abci "github.com/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/errors" + "github.com/cosmos/cosmos-sdk/stack" +) + +// BaseApp - The ABCI application +type BaseApp struct { + *StoreApp + handler sdk.Handler + clock sdk.Ticker +} + +var _ abci.Application = &BaseApp{} + +// NewBaseApp extends a StoreApp with a handler and a ticker, +// which it binds to the proper abci calls +func NewBaseApp(store *StoreApp, handler sdk.Handler, clock sdk.Ticker) *BaseApp { + return &BaseApp{ + StoreApp: store, + handler: handler, + clock: clock, + } +} + +// DeliverTx - ABCI - dispatches to the handler +func (app *BaseApp) DeliverTx(txBytes []byte) abci.Result { + tx, err := sdk.LoadTx(txBytes) + if err != nil { + return errors.Result(err) + } + + ctx := stack.NewContext( + app.GetChainID(), + app.WorkingHeight(), + app.Logger().With("call", "delivertx"), + ) + res, err := app.handler.DeliverTx(ctx, app.Append(), tx) + + if err != nil { + return errors.Result(err) + } + app.AddValChange(res.Diff) + return sdk.ToABCI(res) +} + +// CheckTx - ABCI - dispatches to the handler +func (app *BaseApp) CheckTx(txBytes []byte) abci.Result { + tx, err := sdk.LoadTx(txBytes) + if err != nil { + return errors.Result(err) + } + + ctx := stack.NewContext( + app.GetChainID(), + app.WorkingHeight(), + app.Logger().With("call", "checktx"), + ) + res, err := app.handler.CheckTx(ctx, app.Check(), tx) + + if err != nil { + return errors.Result(err) + } + return sdk.ToABCI(res) +} + +// BeginBlock - ABCI - triggers Tick actions +func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) { + // execute tick if present + if app.clock != nil { + ctx := stack.NewContext( + app.GetChainID(), + app.WorkingHeight(), + app.Logger().With("call", "tick"), + ) + + diff, err := app.clock.Tick(ctx, app.Append()) + if err != nil { + panic(err) + } + app.AddValChange(diff) + } +} + +// InitState - used to setup state (was SetOption) +// to be used by InitChain later +// +// TODO: rethink this a bit more.... +func (app *BaseApp) InitState(module, key, value string) (string, error) { + state := app.Append() + + if module == ModuleNameBase { + if key == ChainKey { + app.info.SetChainID(state, value) + return "Success", nil + } + return "", fmt.Errorf("unknown base option: %s", key) + } + + log, err := app.handler.InitState(app.Logger(), state, module, key, value) + if err != nil { + app.Logger().Error("Genesis App Options", "err", err) + } else { + app.Logger().Info(log) + } + return log, err +} diff --git a/app/bc.go b/app/bc.go deleted file mode 100644 index 46d1a1ca0f..0000000000 --- a/app/bc.go +++ /dev/null @@ -1,127 +0,0 @@ -package app - -import ( - "fmt" - - abci "github.com/tendermint/abci/types" - "github.com/tendermint/tmlibs/log" - - sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/errors" - "github.com/cosmos/cosmos-sdk/stack" - sm "github.com/cosmos/cosmos-sdk/state" - "github.com/cosmos/cosmos-sdk/version" -) - -// Basecoin - The ABCI application -type Basecoin struct { - *BaseApp - handler sdk.Handler - tick Ticker -} - -// Ticker - tick function -type Ticker func(sm.SimpleDB) ([]*abci.Validator, error) - -var _ abci.Application = &Basecoin{} - -// NewBasecoin - create a new instance of the basecoin application -func NewBasecoin(handler sdk.Handler, dbName string, cacheSize int, logger log.Logger) (*Basecoin, error) { - appName := fmt.Sprintf("Basecoin v%v", version.Version) - base, err := NewBaseApp(appName, dbName, cacheSize, logger) - app := &Basecoin{ - BaseApp: base, - handler: handler, - } - return app, err -} - -// NewBasecoinTick - create a new instance of the basecoin application with tick functionality -func NewBasecoinTick(handler sdk.Handler, tick Ticker, dbName string, cacheSize int, logger log.Logger) (*Basecoin, error) { - appName := fmt.Sprintf("Basecoin v%v", version.Version) - base, err := NewBaseApp(appName, dbName, cacheSize, logger) - app := &Basecoin{ - BaseApp: base, - handler: handler, - tick: tick, - } - return app, err -} - -// InitState - used to setup state (was SetOption) -// to be used by InitChain later -func (app *Basecoin) InitState(module, key, value string) (string, error) { - state := app.Append() - - if module == ModuleNameBase { - if key == ChainKey { - app.info.SetChainID(state, value) - return "Success", nil - } - return "", fmt.Errorf("unknown base option: %s", key) - } - - log, err := app.handler.InitState(app.Logger(), state, module, key, value) - if err != nil { - app.Logger().Error("Genesis App Options", "err", err) - } else { - app.Logger().Info(log) - } - return log, err -} - -// DeliverTx - ABCI -func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result { - tx, err := sdk.LoadTx(txBytes) - if err != nil { - return errors.Result(err) - } - - ctx := stack.NewContext( - app.GetChainID(), - app.height+1, - app.Logger().With("call", "delivertx"), - ) - res, err := app.handler.DeliverTx(ctx, app.Append(), tx) - - if err != nil { - return errors.Result(err) - } - app.AddValChange(res.Diff) - return sdk.ToABCI(res) -} - -// CheckTx - ABCI -func (app *Basecoin) CheckTx(txBytes []byte) abci.Result { - tx, err := sdk.LoadTx(txBytes) - if err != nil { - return errors.Result(err) - } - - ctx := stack.NewContext( - app.GetChainID(), - app.height+1, - app.Logger().With("call", "checktx"), - ) - res, err := app.handler.CheckTx(ctx, app.Check(), tx) - - if err != nil { - return errors.Result(err) - } - return sdk.ToABCI(res) -} - -// BeginBlock - ABCI -func (app *Basecoin) BeginBlock(req abci.RequestBeginBlock) { - // call the embeded Begin - app.BaseApp.BeginBlock(req) - - // now execute tick - if app.tick != nil { - diff, err := app.tick(app.Append()) - if err != nil { - panic(err) - } - app.AddValChange(diff) - } -} diff --git a/app/genesis_test.go b/app/genesis_test.go index ee73ceb0f4..14b841480b 100644 --- a/app/genesis_test.go +++ b/app/genesis_test.go @@ -19,11 +19,9 @@ const genesisAcctFilepath = "./testdata/genesis2.json" func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) { logger := log.TestingLogger() - app, err := NewBasecoin(DefaultHandler("mycoin"), - "", - 0, - logger) + store, err := MockStoreApp("genesis", logger) require.Nil(t, err, "%+v", err) + app := NewBaseApp(store, DefaultHandler("mycoin"), nil) err = LoadGenesis(app, "./testdata/genesis3.json") require.Nil(t, err, "%+v", err) @@ -33,11 +31,9 @@ func TestLoadGenesis(t *testing.T) { assert, require := assert.New(t), require.New(t) logger := log.TestingLogger() - app, err := NewBasecoin(DefaultHandler("mycoin"), - "", - 0, - logger) + store, err := MockStoreApp("genesis", logger) require.Nil(err, "%+v", err) + app := NewBaseApp(store, DefaultHandler("mycoin"), nil) err = LoadGenesis(app, genesisFilepath) require.Nil(err, "%+v", err) @@ -67,11 +63,9 @@ func TestLoadGenesisAccountAddress(t *testing.T) { assert, require := assert.New(t), require.New(t) logger := log.TestingLogger() - app, err := NewBasecoin(DefaultHandler("mycoin"), - "", - 0, - logger) + store, err := MockStoreApp("genesis", logger) require.Nil(err, "%+v", err) + app := NewBaseApp(store, DefaultHandler("mycoin"), nil) err = LoadGenesis(app, genesisAcctFilepath) require.Nil(err, "%+v", err) diff --git a/app/app.go b/app/store.go similarity index 77% rename from app/app.go rename to app/store.go index c8a03c6fe2..8385515916 100644 --- a/app/app.go +++ b/app/store.go @@ -22,12 +22,12 @@ const ( ChainKey = "chain_id" ) -// BaseApp contains a data store and all info needed +// StoreApp contains a data store and all info needed // to perform queries and handshakes. // // It should be embeded in another struct for CheckTx, // DeliverTx and initializing state from the genesis. -type BaseApp struct { +type StoreApp struct { // Name is what is returned from info Name string @@ -44,63 +44,78 @@ type BaseApp struct { logger log.Logger } -// NewBaseApp creates a data store to handle queries -func NewBaseApp(appName, dbName string, cacheSize int, logger log.Logger) (*BaseApp, error) { +// NewStoreApp creates a data store to handle queries +func NewStoreApp(appName, dbName string, cacheSize int, logger log.Logger) (*StoreApp, error) { state, err := loadState(dbName, cacheSize) if err != nil { return nil, err } - app := &BaseApp{ + app := &StoreApp{ Name: appName, State: state, height: state.LatestHeight(), info: sm.NewChainState(), - logger: logger, + logger: logger.With("module", "app"), } return app, nil } +// MockStoreApp returns a Store app with no persistence +func MockStoreApp(appName string, logger log.Logger) (*StoreApp, error) { + return NewStoreApp(appName, "", 0, logger) +} + // GetChainID returns the currently stored chain -func (app *BaseApp) GetChainID() string { +func (app *StoreApp) GetChainID() string { return app.info.GetChainID(app.Committed()) } // Logger returns the application base logger -func (app *BaseApp) Logger() log.Logger { +func (app *StoreApp) Logger() log.Logger { return app.logger } // Hash gets the last hash stored in the database -func (app *BaseApp) Hash() []byte { +func (app *StoreApp) Hash() []byte { return app.State.LatestHash() } +// CommittedHeight gets the last block height committed +// to the db +func (app *StoreApp) CommittedHeight() uint64 { + return app.height +} + +// WorkingHeight gets the current block we are writing +func (app *StoreApp) WorkingHeight() uint64 { + return app.height + 1 +} + // Info implements abci.Application. It returns the height and hash, // as well as the abci name and version. // // The height is the block that holds the transactions, not the apphash itself. -func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo { +func (app *StoreApp) Info(req abci.RequestInfo) abci.ResponseInfo { hash := app.Hash() app.logger.Info("Info synced", - "height", app.height, + "height", app.CommittedHeight(), "hash", fmt.Sprintf("%X", hash)) return abci.ResponseInfo{ - // TODO Data: app.Name, - LastBlockHeight: app.height, + LastBlockHeight: app.CommittedHeight(), LastBlockAppHash: hash, } } // SetOption - ABCI -func (app *BaseApp) SetOption(key string, value string) string { +func (app *StoreApp) SetOption(key string, value string) string { return "Not Implemented" } // Query - ABCI -func (app *BaseApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { +func (app *StoreApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { if len(reqQuery.Data) == 0 { resQuery.Log = "Query cannot be zero length" resQuery.Code = abci.CodeType_EncodingError @@ -120,7 +135,7 @@ func (app *BaseApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQue // if tree.Tree.VersionExists(app.height - 1) { // height = app.height - 1 // } else { - height = app.height + height = app.CommittedHeight() // } } resQuery.Height = height @@ -150,7 +165,7 @@ func (app *BaseApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQue } // Commit implements abci.Application -func (app *BaseApp) Commit() (res abci.Result) { +func (app *StoreApp) Commit() (res abci.Result) { app.height++ hash, err := app.State.Commit(app.height) @@ -170,16 +185,14 @@ func (app *BaseApp) Commit() (res abci.Result) { } // InitChain - ABCI -func (app *BaseApp) InitChain(req abci.RequestInitChain) { -} +func (app *StoreApp) InitChain(req abci.RequestInitChain) {} // BeginBlock - ABCI -func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) { -} +func (app *StoreApp) BeginBlock(req abci.RequestBeginBlock) {} // EndBlock - ABCI // Returns a list of all validator changes made in this block -func (app *BaseApp) EndBlock(height uint64) (res abci.ResponseEndBlock) { +func (app *StoreApp) EndBlock(height uint64) (res abci.ResponseEndBlock) { // TODO: cleanup in case a validator exists multiple times in the list res.Diffs = app.pending app.pending = nil @@ -189,7 +202,7 @@ func (app *BaseApp) EndBlock(height uint64) (res abci.ResponseEndBlock) { // AddValChange is meant to be called by apps on DeliverTx // results, this is added to the cache for the endblock // changeset -func (app *BaseApp) AddValChange(diffs []*abci.Validator) { +func (app *StoreApp) AddValChange(diffs []*abci.Validator) { for _, d := range diffs { idx := pubKeyIndex(d, app.pending) if idx >= 0 { diff --git a/app/app_val_test.go b/app/val_test.go similarity index 96% rename from app/app_val_test.go rename to app/val_test.go index e85868b8ab..c333273f25 100644 --- a/app/app_val_test.go +++ b/app/val_test.go @@ -40,8 +40,9 @@ func TestEndBlock(t *testing.T) { logger := log.NewNopLogger() handler := base.ValSetHandler{} - app, err := NewBasecoin(handler, "", 0, logger) + store, err := MockStoreApp("vals", logger) require.Nil(err, "%+v", err) + app := NewBaseApp(store, handler, nil) val1 := makeVal() val2 := makeVal() diff --git a/benchmarks/app_test.go b/benchmarks/app_test.go index 690727e74a..5d72736aa3 100644 --- a/benchmarks/app_test.go +++ b/benchmarks/app_test.go @@ -10,7 +10,7 @@ import ( "github.com/tendermint/tmlibs/log" sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/app" + sdkapp "github.com/cosmos/cosmos-sdk/app" "github.com/cosmos/cosmos-sdk/modules/auth" "github.com/cosmos/cosmos-sdk/modules/base" "github.com/cosmos/cosmos-sdk/modules/coin" @@ -21,7 +21,7 @@ import ( ) type BenchApp struct { - App *app.Basecoin + App *sdkapp.BaseApp Accounts []*coin.AccountWithKey ChainID string } @@ -59,15 +59,11 @@ func NewBenchApp(h sdk.Handler, chainID string, n int, cache = 500 } - app, err := app.NewBasecoin( - h, - dbDir, - cache, - logger.With("module", "app"), - ) + store, err := sdkapp.NewStoreApp("bench", dbDir, cache, logger) if err != nil { panic(err) } + app := sdkapp.NewBaseApp(store, h, nil) _, err = app.InitState("base", "chain_id", chainID) if err != nil { diff --git a/client/query_test.go b/client/query_test.go index fe8ee41801..f8031ee838 100644 --- a/client/query_test.go +++ b/client/query_test.go @@ -18,7 +18,7 @@ import ( "github.com/tendermint/tendermint/types" "github.com/tendermint/tmlibs/log" - "github.com/cosmos/cosmos-sdk/app" + sdkapp "github.com/cosmos/cosmos-sdk/app" "github.com/cosmos/cosmos-sdk/modules/eyes" ) @@ -26,10 +26,11 @@ var node *nm.Node func TestMain(m *testing.M) { logger := log.TestingLogger() - app, err := app.NewBasecoin(eyes.NewHandler(), "", 0, logger) + store, err := sdkapp.MockStoreApp("query", logger) if err != nil { panic(err) } + app := sdkapp.NewBaseApp(store, eyes.NewHandler(), nil) node = rpctest.StartTendermint(app) diff --git a/examples/basecoin/tests/cli/rpc.sh b/examples/basecoin/tests/cli/rpc.sh index 3f5a557134..5ab6022d9b 100755 --- a/examples/basecoin/tests/cli/rpc.sh +++ b/examples/basecoin/tests/cli/rpc.sh @@ -66,7 +66,7 @@ test01GetInsecure() { INFO=$(${CLIENT_EXE} rpc info) assertTrue "line=${LINENO}, get info" "$?" DATA=$(echo $INFO | jq .response.data) - assertEquals "line=${LINENO}, basecoin info" '"Basecoin v0.7.1"' "$DATA" + assertEquals "line=${LINENO}, basecoin info" '"basecoin v0.7.1"' "$DATA" } test02GetSecure() { diff --git a/examples/counter/plugins/counter/counter_test.go b/examples/counter/plugins/counter/counter_test.go index a10e0afbbb..07861dc29b 100644 --- a/examples/counter/plugins/counter/counter_test.go +++ b/examples/counter/plugins/counter/counter_test.go @@ -28,13 +28,9 @@ func TestCounterPlugin(t *testing.T) { // logger := log.NewTracingLogger(log.NewTMLogger(os.Stdout)) h := NewHandler("gold") - bcApp, err := app.NewBasecoin( - h, - "", - 0, - logger.With("module", "app"), - ) + store, err := app.MockStoreApp("counter", logger) require.Nil(err, "%+v", err) + bcApp := app.NewBaseApp(store, h, nil) _, err = bcApp.InitState("base", "chain_id", chainID) require.Nil(err, "%+v", err) diff --git a/handler.go b/handler.go index 8047fc40af..51d8e4a127 100644 --- a/handler.go +++ b/handler.go @@ -25,6 +25,18 @@ type Handler interface { // BeginBlock(store state.SimpleDB, hash []byte, header *abci.Header) } +// Ticker can be executed every block +type Ticker interface { + Tick(Context, state.SimpleDB) ([]*abci.Validator, error) +} + +// TickerFunc allows a function to implement the interface +type TickerFunc func(Context, state.SimpleDB) ([]*abci.Validator, error) + +func (t TickerFunc) Tick(ctx Context, store state.SimpleDB) ([]*abci.Validator, error) { + return t(ctx, store) +} + // Named ensures there is a name for the item type Named interface { Name() string diff --git a/server/commands/start.go b/server/commands/start.go index 117ec6b2bf..22e79ec1c7 100644 --- a/server/commands/start.go +++ b/server/commands/start.go @@ -21,6 +21,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk" "github.com/cosmos/cosmos-sdk/app" + "github.com/cosmos/cosmos-sdk/version" ) // StartCmd - command to start running the abci app (and tendermint)! @@ -31,7 +32,7 @@ var StartCmd = &cobra.Command{ } // GetTickStartCmd - initialize a command as the start command with tick -func GetTickStartCmd(tick app.Ticker) *cobra.Command { +func GetTickStartCmd(tick sdk.Ticker) *cobra.Command { startCmd := &cobra.Command{ Use: "start", Short: "Start this full node", @@ -70,20 +71,23 @@ func addStartFlag(startCmd *cobra.Command) { } //returns the start command which uses the tick -func tickStartCmd(tick app.Ticker) func(cmd *cobra.Command, args []string) error { +func tickStartCmd(clock sdk.Ticker) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { rootDir := viper.GetString(cli.HomeFlag) - // Create Basecoin app - basecoinApp, err := app.NewBasecoinTick( - Handler, - tick, + cmdName := cmd.Root().Name() + appName := fmt.Sprintf("%s v%v", cmdName, version.Version) + storeApp, err := app.NewStoreApp( + appName, path.Join(rootDir, "data", "merkleeyes.db"), EyesCacheSize, logger.With("module", "app")) if err != nil { return err } + + // Create Basecoin app + basecoinApp := app.NewBaseApp(storeApp, Handler, clock) return start(rootDir, basecoinApp) } } @@ -91,19 +95,23 @@ func tickStartCmd(tick app.Ticker) func(cmd *cobra.Command, args []string) error func startCmd(cmd *cobra.Command, args []string) error { rootDir := viper.GetString(cli.HomeFlag) - // Create Basecoin app - basecoinApp, err := app.NewBasecoin( - Handler, + cmdName := cmd.Root().Name() + appName := fmt.Sprintf("%s v%v", cmdName, version.Version) + storeApp, err := app.NewStoreApp( + appName, path.Join(rootDir, "data", "merkleeyes.db"), EyesCacheSize, logger.With("module", "app")) if err != nil { return err } + + // Create Basecoin app + basecoinApp := app.NewBaseApp(storeApp, Handler, nil) return start(rootDir, basecoinApp) } -func start(rootDir string, basecoinApp *app.Basecoin) error { +func start(rootDir string, basecoinApp *app.BaseApp) error { // if chain_id has not been set yet, load the genesis. // else, assume it's been loaded