From e863e1be70a47fae1be48f7450d5a1c6b006567f Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 16 Oct 2017 15:51:37 +0200 Subject: [PATCH 01/15] Started separating BaseApp from Basecoin --- app/app.go | 151 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 87 insertions(+), 64 deletions(-) diff --git a/app/app.go b/app/app.go index e20f97f2c7..b89042d8e8 100644 --- a/app/app.go +++ b/app/app.go @@ -24,15 +24,9 @@ const ( // Basecoin - The ABCI application type Basecoin struct { - info *sm.ChainState - state *Store - + *BaseApp handler sdk.Handler tick Ticker - - pending []*abci.Validator - height uint64 - logger log.Logger } // Ticker - tick function @@ -43,48 +37,20 @@ var _ abci.Application = &Basecoin{} // NewBasecoin - create a new instance of the basecoin application func NewBasecoin(handler sdk.Handler, store *Store, logger log.Logger) *Basecoin { return &Basecoin{ + BaseApp: NewBaseApp(store, logger), handler: handler, - info: sm.NewChainState(), - state: store, - logger: logger, } } // NewBasecoinTick - create a new instance of the basecoin application with tick functionality func NewBasecoinTick(handler sdk.Handler, store *Store, logger log.Logger, tick Ticker) *Basecoin { return &Basecoin{ + BaseApp: NewBaseApp(store, logger), handler: handler, - info: sm.NewChainState(), - state: store, - logger: logger, tick: tick, } } -// GetChainID returns the currently stored chain -func (app *Basecoin) GetChainID() string { - return app.info.GetChainID(app.state.Committed()) -} - -// GetState is back... please kill me -func (app *Basecoin) GetState() sm.SimpleDB { - return app.state.Append() -} - -// Info - ABCI -func (app *Basecoin) Info(req abci.RequestInfo) abci.ResponseInfo { - resp := app.state.Info() - app.logger.Debug("Info", - "height", resp.LastBlockHeight, - "hash", fmt.Sprintf("%X", resp.LastBlockAppHash)) - app.height = resp.LastBlockHeight - return abci.ResponseInfo{ - Data: fmt.Sprintf("Basecoin v%v", version.Version), - LastBlockHeight: resp.LastBlockHeight, - LastBlockAppHash: resp.LastBlockAppHash, - } -} - // InitState - used to setup state (was SetOption) // to be used by InitChain later func (app *Basecoin) InitState(key string, value string) string { @@ -99,18 +65,13 @@ func (app *Basecoin) InitState(key string, value string) string { return fmt.Sprintf("Error: unknown base option: %s", key) } - log, err := app.handler.InitState(app.logger, state, module, key, value) + log, err := app.handler.InitState(app.Logger(), state, module, key, value) if err == nil { return log } return "Error: " + err.Error() } -// SetOption - ABCI -func (app *Basecoin) SetOption(key string, value string) string { - return "Not Implemented" -} - // DeliverTx - ABCI func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result { tx, err := sdk.LoadTx(txBytes) @@ -121,14 +82,14 @@ func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result { ctx := stack.NewContext( app.GetChainID(), app.height, - app.logger.With("call", "delivertx"), + app.Logger().With("call", "delivertx"), ) res, err := app.handler.DeliverTx(ctx, app.state.Append(), tx) if err != nil { return errors.Result(err) } - app.addValChange(res.Diff) + app.AddValChange(res.Diff) return sdk.ToABCI(res) } @@ -142,7 +103,7 @@ func (app *Basecoin) CheckTx(txBytes []byte) abci.Result { ctx := stack.NewContext( app.GetChainID(), app.height, - app.logger.With("call", "checktx"), + app.Logger().With("call", "checktx"), ) res, err := app.handler.CheckTx(ctx, app.state.Check(), tx) @@ -152,8 +113,82 @@ func (app *Basecoin) CheckTx(txBytes []byte) abci.Result { 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.state.Append()) + if err != nil { + panic(err) + } + app.AddValChange(diff) + } +} + +/////////////////////////// Move to SDK /////// + +// BaseApp 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 { + info *sm.ChainState + state *Store + + pending []*abci.Validator + height uint64 + logger log.Logger +} + +// NewBaseApp creates a data store to handle queries +func NewBaseApp(store *Store, logger log.Logger) *BaseApp { + return &BaseApp{ + info: sm.NewChainState(), + state: store, + logger: logger, + } +} + +// GetChainID returns the currently stored chain +func (app *BaseApp) GetChainID() string { + return app.info.GetChainID(app.state.Committed()) +} + +// GetState returns the delivertx state, should be removed +func (app *BaseApp) GetState() sm.SimpleDB { + return app.state.Append() +} + +// Logger returns the application base logger +func (app *BaseApp) Logger() log.Logger { + return app.logger +} + +// Info - ABCI +func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo { + resp := app.state.Info() + app.logger.Debug("Info", + "height", resp.LastBlockHeight, + "hash", fmt.Sprintf("%X", resp.LastBlockAppHash)) + app.height = resp.LastBlockHeight + return abci.ResponseInfo{ + Data: fmt.Sprintf("Basecoin v%v", version.Version), + LastBlockHeight: resp.LastBlockHeight, + LastBlockAppHash: resp.LastBlockAppHash, + } +} + +// SetOption - ABCI +func (app *BaseApp) SetOption(key string, value string) string { + return "Not Implemented" +} + // Query - ABCI -func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { +func (app *BaseApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { if len(reqQuery.Data) == 0 { resQuery.Log = "Query cannot be zero length" resQuery.Code = abci.CodeType_EncodingError @@ -164,7 +199,7 @@ func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQu } // Commit - ABCI -func (app *Basecoin) Commit() (res abci.Result) { +func (app *BaseApp) Commit() (res abci.Result) { // Commit state res = app.state.Commit() if res.IsErr() { @@ -174,39 +209,27 @@ func (app *Basecoin) Commit() (res abci.Result) { } // InitChain - ABCI -func (app *Basecoin) InitChain(req abci.RequestInitChain) { +func (app *BaseApp) InitChain(req abci.RequestInitChain) { // for _, plugin := range app.plugins.GetList() { // plugin.InitChain(app.state, validators) // } } // BeginBlock - ABCI -func (app *Basecoin) BeginBlock(req abci.RequestBeginBlock) { +func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) { app.height++ - - // for _, plugin := range app.plugins.GetList() { - // plugin.BeginBlock(app.state, hash, header) - // } - - if app.tick != nil { - diff, err := app.tick(app.state.Append()) - if err != nil { - panic(err) - } - app.addValChange(diff) - } } // EndBlock - ABCI // Returns a list of all validator changes made in this block -func (app *Basecoin) EndBlock(height uint64) (res abci.ResponseEndBlock) { +func (app *BaseApp) 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 return } -func (app *Basecoin) addValChange(diffs []*abci.Validator) { +func (app *BaseApp) AddValChange(diffs []*abci.Validator) { for _, d := range diffs { idx := pubKeyIndex(d, app.pending) if idx >= 0 { From 987c0469da2b9e7d61e48c74b4bb6c02afb19083 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 16 Oct 2017 15:56:23 +0200 Subject: [PATCH 02/15] Pull out basecoin stuff --- app/app.go | 109 ------------------------------------------------ app/bc.go | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 109 deletions(-) create mode 100644 app/bc.go diff --git a/app/app.go b/app/app.go index b89042d8e8..df0e2d2079 100644 --- a/app/app.go +++ b/app/app.go @@ -9,9 +9,6 @@ import ( cmn "github.com/tendermint/tmlibs/common" "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" ) @@ -22,112 +19,6 @@ const ( ChainKey = "chain_id" ) -// 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, store *Store, logger log.Logger) *Basecoin { - return &Basecoin{ - BaseApp: NewBaseApp(store, logger), - handler: handler, - } -} - -// NewBasecoinTick - create a new instance of the basecoin application with tick functionality -func NewBasecoinTick(handler sdk.Handler, store *Store, logger log.Logger, tick Ticker) *Basecoin { - return &Basecoin{ - BaseApp: NewBaseApp(store, logger), - handler: handler, - tick: tick, - } -} - -// InitState - used to setup state (was SetOption) -// to be used by InitChain later -func (app *Basecoin) InitState(key string, value string) string { - module, key := splitKey(key) - state := app.state.Append() - - if module == ModuleNameBase { - if key == ChainKey { - app.info.SetChainID(state, value) - return "Success" - } - return fmt.Sprintf("Error: unknown base option: %s", key) - } - - log, err := app.handler.InitState(app.Logger(), state, module, key, value) - if err == nil { - return log - } - return "Error: " + err.Error() -} - -// 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, - app.Logger().With("call", "delivertx"), - ) - res, err := app.handler.DeliverTx(ctx, app.state.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, - app.Logger().With("call", "checktx"), - ) - res, err := app.handler.CheckTx(ctx, app.state.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.state.Append()) - if err != nil { - panic(err) - } - app.AddValChange(diff) - } -} - /////////////////////////// Move to SDK /////// // BaseApp contains a data store and all info needed diff --git a/app/bc.go b/app/bc.go new file mode 100644 index 0000000000..a7dbf80fa4 --- /dev/null +++ b/app/bc.go @@ -0,0 +1,119 @@ +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" +) + +// 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, store *Store, logger log.Logger) *Basecoin { + return &Basecoin{ + BaseApp: NewBaseApp(store, logger), + handler: handler, + } +} + +// NewBasecoinTick - create a new instance of the basecoin application with tick functionality +func NewBasecoinTick(handler sdk.Handler, store *Store, logger log.Logger, tick Ticker) *Basecoin { + return &Basecoin{ + BaseApp: NewBaseApp(store, logger), + handler: handler, + tick: tick, + } +} + +// InitState - used to setup state (was SetOption) +// to be used by InitChain later +func (app *Basecoin) InitState(key string, value string) string { + module, key := splitKey(key) + state := app.state.Append() + + if module == ModuleNameBase { + if key == ChainKey { + app.info.SetChainID(state, value) + return "Success" + } + return fmt.Sprintf("Error: unknown base option: %s", key) + } + + log, err := app.handler.InitState(app.Logger(), state, module, key, value) + if err == nil { + return log + } + return "Error: " + err.Error() +} + +// 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, + app.Logger().With("call", "delivertx"), + ) + res, err := app.handler.DeliverTx(ctx, app.state.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, + app.Logger().With("call", "checktx"), + ) + res, err := app.handler.CheckTx(ctx, app.state.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.state.Append()) + if err != nil { + panic(err) + } + app.AddValChange(diff) + } +} From 9d1205f8c7caa1f87c017039bcbf86ef13067f12 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 16 Oct 2017 16:26:21 +0200 Subject: [PATCH 03/15] Merged app.Store into app.BaseApp --- app/app.go | 154 +++++++++++++++++++++++++++++++------- app/app_test.go | 30 ++++---- app/app_val_test.go | 4 +- app/bc.go | 24 +++--- app/genesis_test.go | 23 ++++-- app/store.go | 175 +++----------------------------------------- state/merkle.go | 4 +- 7 files changed, 183 insertions(+), 231 deletions(-) diff --git a/app/app.go b/app/app.go index df0e2d2079..aa06cce8ea 100644 --- a/app/app.go +++ b/app/app.go @@ -3,12 +3,17 @@ package app import ( "bytes" "fmt" + "path" + "path/filepath" "strings" abci "github.com/tendermint/abci/types" + "github.com/tendermint/iavl" cmn "github.com/tendermint/tmlibs/common" + dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" + "github.com/cosmos/cosmos-sdk/errors" sm "github.com/cosmos/cosmos-sdk/state" "github.com/cosmos/cosmos-sdk/version" ) @@ -19,16 +24,14 @@ const ( ChainKey = "chain_id" ) -/////////////////////////// Move to SDK /////// - // BaseApp 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 { - info *sm.ChainState - state *Store + info *sm.ChainState + *sm.State pending []*abci.Validator height uint64 @@ -36,22 +39,22 @@ type BaseApp struct { } // NewBaseApp creates a data store to handle queries -func NewBaseApp(store *Store, logger log.Logger) *BaseApp { - return &BaseApp{ +func NewBaseApp(dbName string, cacheSize int, logger log.Logger) (*BaseApp, error) { + state, err := loadState(dbName, cacheSize) + if err != nil { + return nil, err + } + app := &BaseApp{ info: sm.NewChainState(), - state: store, + State: state, logger: logger, } + return app, nil } // GetChainID returns the currently stored chain func (app *BaseApp) GetChainID() string { - return app.info.GetChainID(app.state.Committed()) -} - -// GetState returns the delivertx state, should be removed -func (app *BaseApp) GetState() sm.SimpleDB { - return app.state.Append() + return app.info.GetChainID(app.Committed()) } // Logger returns the application base logger @@ -59,17 +62,27 @@ func (app *BaseApp) Logger() log.Logger { return app.logger } -// Info - ABCI +// Hash gets the last hash stored in the database +func (app *BaseApp) Hash() []byte { + return app.State.LatestHash() +} + +// 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 { - resp := app.state.Info() - app.logger.Debug("Info", - "height", resp.LastBlockHeight, - "hash", fmt.Sprintf("%X", resp.LastBlockAppHash)) - app.height = resp.LastBlockHeight + hash := app.Hash() + + app.logger.Info("Info synced", + "height", app.height, + "hash", fmt.Sprintf("%X", hash)) + return abci.ResponseInfo{ + // TODO Data: fmt.Sprintf("Basecoin v%v", version.Version), - LastBlockHeight: resp.LastBlockHeight, - LastBlockAppHash: resp.LastBlockAppHash, + LastBlockHeight: app.height, + LastBlockAppHash: hash, } } @@ -86,17 +99,66 @@ func (app *BaseApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQue return } - return app.state.Query(reqQuery) + // set the query response height to current + tree := app.State.Committed() + + height := reqQuery.Height + if height == 0 { + // TODO: once the rpc actually passes in non-zero + // heights we can use to query right after a tx + // we must retrun most recent, even if apphash + // is not yet in the blockchain + + // if tree.Tree.VersionExists(app.height - 1) { + // height = app.height - 1 + // } else { + height = app.height + // } + } + resQuery.Height = height + + switch reqQuery.Path { + case "/store", "/key": // Get by key + key := reqQuery.Data // Data holds the key bytes + resQuery.Key = key + if reqQuery.Prove { + value, proof, err := tree.GetVersionedWithProof(key, height) + if err != nil { + resQuery.Log = err.Error() + break + } + resQuery.Value = value + resQuery.Proof = proof.Bytes() + } else { + value := tree.Get(key) + resQuery.Value = value + } + + default: + resQuery.Code = abci.CodeType_UnknownRequest + resQuery.Log = cmn.Fmt("Unexpected Query path: %v", reqQuery.Path) + } + return } -// Commit - ABCI +// Commit implements abci.Application func (app *BaseApp) Commit() (res abci.Result) { - // Commit state - res = app.state.Commit() - if res.IsErr() { - cmn.PanicSanity("Error getting hash: " + res.Error()) + app.height++ + + hash, err := app.State.Commit(app.height) + if err != nil { + // die if we can't commit, not to recover + panic(err) } - return res + app.logger.Debug("Commit synced", + "height", app.height, + "hash", fmt.Sprintf("%X", hash), + ) + + if app.State.Size() == 0 { + return abci.NewResultOK(nil, "Empty hash for empty tree") + } + return abci.NewResultOK(hash, "") } // InitChain - ABCI @@ -152,3 +214,39 @@ func splitKey(key string) (string, string) { } return ModuleNameBase, key } + +func loadState(dbName string, cacheSize int) (*sm.State, error) { + // memory backed case, just for testing + if dbName == "" { + tree := iavl.NewVersionedTree(0, dbm.NewMemDB()) + return sm.NewState(tree), nil + } + + // Expand the path fully + dbPath, err := filepath.Abs(dbName) + if err != nil { + return nil, errors.ErrInternal("Invalid Database Name") + } + + // Some external calls accidently add a ".db", which is now removed + dbPath = strings.TrimSuffix(dbPath, path.Ext(dbPath)) + + // Split the database name into it's components (dir, name) + dir := path.Dir(dbPath) + name := path.Base(dbPath) + + // Make sure the path exists + empty, _ := cmn.IsDirEmpty(dbPath + ".db") + + // Open database called "dir/name.db", if it doesn't exist it will be created + db := dbm.NewDB(name, dbm.LevelDBBackendStr, dir) + tree := iavl.NewVersionedTree(cacheSize, db) + + if !empty { + if err = tree.Load(); err != nil { + return nil, errors.ErrInternal("Loading tree: " + err.Error()) + } + } + + return sm.NewState(tree), nil +} diff --git a/app/app_test.go b/app/app_test.go index 1cf9834595..bcf32dfbeb 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -112,14 +112,15 @@ func (at *appTest) reset() { // Note: switch logger if you want to get more info logger := log.TestingLogger() // logger := log.NewTracingLogger(log.NewTMLogger(os.Stdout)) - store, err := NewStore("", 0, logger.With("module", "store")) - require.Nil(at.t, err, "%+v", err) - at.app = NewBasecoin( + var err error + at.app, err = NewBasecoin( DefaultHandler("mycoin"), - store, + "", + 0, logger.With("module", "app"), ) + require.Nil(at.t, err, "%+v", err) res := at.app.InitState("base/chain_id", at.chainID) require.EqualValues(at.t, res, "Success") @@ -146,9 +147,9 @@ func getAddr(addr []byte, state state.SimpleDB) (coin.Coins, error) { func (at *appTest) exec(t *testing.T, tx sdk.Tx, checkTx bool) (res abci.Result, diffIn, diffOut coin.Coins) { require := require.New(t) - initBalIn, err := getBalance(at.acctIn.Actor(), at.app.GetState()) + initBalIn, err := getBalance(at.acctIn.Actor(), at.app.Append()) require.Nil(err, "%+v", err) - initBalOut, err := getBalance(at.acctOut.Actor(), at.app.GetState()) + initBalOut, err := getBalance(at.acctOut.Actor(), at.app.Append()) require.Nil(err, "%+v", err) txBytes := wire.BinaryBytes(tx) @@ -158,9 +159,9 @@ func (at *appTest) exec(t *testing.T, tx sdk.Tx, checkTx bool) (res abci.Result, res = at.app.DeliverTx(txBytes) } - endBalIn, err := getBalance(at.acctIn.Actor(), at.app.GetState()) + endBalIn, err := getBalance(at.acctIn.Actor(), at.app.Append()) require.Nil(err, "%+v", err) - endBalOut, err := getBalance(at.acctOut.Actor(), at.app.GetState()) + endBalOut, err := getBalance(at.acctOut.Actor(), at.app.Append()) require.Nil(err, "%+v", err) return res, endBalIn.Minus(initBalIn), endBalOut.Minus(initBalOut) } @@ -172,14 +173,13 @@ func TestInitState(t *testing.T) { require := require.New(t) logger := log.TestingLogger() - store, err := NewStore("", 0, logger.With("module", "store")) - require.Nil(err, "%+v", err) - - app := NewBasecoin( + app, err := NewBasecoin( DefaultHandler("atom"), - store, + "", + 0, logger.With("module", "app"), ) + require.Nil(err, "%+v", err) //testing ChainID chainID := "testChain" @@ -194,7 +194,7 @@ func TestInitState(t *testing.T) { require.EqualValues(res, "Success") // make sure it is set correctly, with some balance - coins, err := getBalance(acct.Actor(), app.GetState()) + coins, err := getBalance(acct.Actor(), app.Append()) require.Nil(err) assert.Equal(bal, coins) @@ -221,7 +221,7 @@ func TestInitState(t *testing.T) { res = app.InitState("coin/account", unsortAcc) require.EqualValues(res, "Success") - coins, err = getAddr(unsortAddr, app.GetState()) + coins, err = getAddr(unsortAddr, app.Append()) require.Nil(err) assert.True(coins.IsValid()) assert.Equal(unsortCoins, coins) diff --git a/app/app_val_test.go b/app/app_val_test.go index 95ea77674a..e85868b8ab 100644 --- a/app/app_val_test.go +++ b/app/app_val_test.go @@ -39,9 +39,9 @@ func TestEndBlock(t *testing.T) { assert, require := assert.New(t), require.New(t) logger := log.NewNopLogger() - store := MockStore() handler := base.ValSetHandler{} - app := NewBasecoin(handler, store, logger) + app, err := NewBasecoin(handler, "", 0, logger) + require.Nil(err, "%+v", err) val1 := makeVal() val2 := makeVal() diff --git a/app/bc.go b/app/bc.go index a7dbf80fa4..f681a7c52b 100644 --- a/app/bc.go +++ b/app/bc.go @@ -25,27 +25,31 @@ 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, store *Store, logger log.Logger) *Basecoin { - return &Basecoin{ - BaseApp: NewBaseApp(store, logger), +func NewBasecoin(handler sdk.Handler, dbName string, cacheSize int, logger log.Logger) (*Basecoin, error) { + base, err := NewBaseApp(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, store *Store, logger log.Logger, tick Ticker) *Basecoin { - return &Basecoin{ - BaseApp: NewBaseApp(store, logger), +func NewBasecoinTick(handler sdk.Handler, tick Ticker, dbName string, cacheSize int, logger log.Logger) (*Basecoin, error) { + base, err := NewBaseApp(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(key string, value string) string { module, key := splitKey(key) - state := app.state.Append() + state := app.Append() if module == ModuleNameBase { if key == ChainKey { @@ -74,7 +78,7 @@ func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result { app.height, app.Logger().With("call", "delivertx"), ) - res, err := app.handler.DeliverTx(ctx, app.state.Append(), tx) + res, err := app.handler.DeliverTx(ctx, app.Append(), tx) if err != nil { return errors.Result(err) @@ -95,7 +99,7 @@ func (app *Basecoin) CheckTx(txBytes []byte) abci.Result { app.height, app.Logger().With("call", "checktx"), ) - res, err := app.handler.CheckTx(ctx, app.state.Check(), tx) + res, err := app.handler.CheckTx(ctx, app.Check(), tx) if err != nil { return errors.Result(err) @@ -110,7 +114,7 @@ func (app *Basecoin) BeginBlock(req abci.RequestBeginBlock) { // now execute tick if app.tick != nil { - diff, err := app.tick(app.state.Append()) + diff, err := app.tick(app.Append()) if err != nil { panic(err) } diff --git a/app/genesis_test.go b/app/genesis_test.go index 2b76cd7f95..ccb1229dcc 100644 --- a/app/genesis_test.go +++ b/app/genesis_test.go @@ -19,9 +19,12 @@ const genesisAcctFilepath = "./testdata/genesis2.json" func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) { logger := log.TestingLogger() - store, err := NewStore("", 0, logger) + app, err := NewBasecoin(DefaultHandler("mycoin"), + "", + 0, + logger) require.Nil(t, err, "%+v", err) - app := NewBasecoin(DefaultHandler("mycoin"), store, logger) + err = app.LoadGenesis("./testdata/genesis3.json") require.Nil(t, err, "%+v", err) } @@ -30,10 +33,12 @@ func TestLoadGenesis(t *testing.T) { assert, require := assert.New(t), require.New(t) logger := log.TestingLogger() - store, err := NewStore("", 0, logger) + app, err := NewBasecoin(DefaultHandler("mycoin"), + "", + 0, + logger) require.Nil(err, "%+v", err) - app := NewBasecoin(DefaultHandler("mycoin"), store, logger) err = app.LoadGenesis(genesisFilepath) require.Nil(err, "%+v", err) @@ -43,7 +48,7 @@ func TestLoadGenesis(t *testing.T) { // and check the account info - previously calculated values addr, _ := hex.DecodeString("eb98e0688217cfdeb70eddf4b33cdcc37fc53197") - coins, err := getAddr(addr, app.GetState()) + coins, err := getAddr(addr, app.Append()) require.Nil(err) assert.True(coins.IsPositive()) @@ -62,10 +67,12 @@ func TestLoadGenesisAccountAddress(t *testing.T) { assert, require := assert.New(t), require.New(t) logger := log.TestingLogger() - store, err := NewStore("", 0, logger) + app, err := NewBasecoin(DefaultHandler("mycoin"), + "", + 0, + logger) require.Nil(err, "%+v", err) - app := NewBasecoin(DefaultHandler("mycoin"), store, logger) err = app.LoadGenesis(genesisAcctFilepath) require.Nil(err, "%+v", err) @@ -93,7 +100,7 @@ func TestLoadGenesisAccountAddress(t *testing.T) { for i, tc := range cases { addr, err := hex.DecodeString(tc.addr) require.Nil(err, tc.addr) - coins, err := getAddr(addr, app.GetState()) + coins, err := getAddr(addr, app.Append()) require.Nil(err, "%+v", err) if !tc.exists { assert.True(coins.IsZero(), "%d", i) diff --git a/app/store.go b/app/store.go index 05843f64ff..ff33d7dbcb 100644 --- a/app/store.go +++ b/app/store.go @@ -1,168 +1,11 @@ package app -import ( - "fmt" - "path" - "path/filepath" - "strings" - - "github.com/pkg/errors" - abci "github.com/tendermint/abci/types" - "github.com/tendermint/iavl" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" - - "github.com/cosmos/cosmos-sdk/state" -) - -// Store contains the merkle tree, and all info to handle abci requests -type Store struct { - state.State - height uint64 - logger log.Logger -} - -// MockStore returns an in-memory store only intended for testing -func MockStore() *Store { - res, err := NewStore("", 0, log.NewNopLogger()) - if err != nil { - // should never happen, abort test if it does - panic(err) - } - return res -} - -// NewStore initializes an in-memory iavl.VersionedTree, or attempts to load a -// persistant tree from disk -func NewStore(dbName string, cacheSize int, logger log.Logger) (*Store, error) { - // memory backed case, just for testing - if dbName == "" { - tree := iavl.NewVersionedTree( - 0, - dbm.NewMemDB(), - ) - store := &Store{ - State: state.NewState(tree), - logger: logger, - } - return store, nil - } - - // Expand the path fully - dbPath, err := filepath.Abs(dbName) - if err != nil { - return nil, errors.Wrap(err, "Invalid Database Name") - } - - // Some external calls accidently add a ".db", which is now removed - dbPath = strings.TrimSuffix(dbPath, path.Ext(dbPath)) - - // Split the database name into it's components (dir, name) - dir := path.Dir(dbPath) - name := path.Base(dbPath) - - // Make sure the path exists - empty, _ := cmn.IsDirEmpty(dbPath + ".db") - - // Open database called "dir/name.db", if it doesn't exist it will be created - db := dbm.NewDB(name, dbm.LevelDBBackendStr, dir) - tree := iavl.NewVersionedTree(cacheSize, db) - - if empty { - logger.Info("no existing db, creating new db") - } else { - logger.Info("loading existing db") - if err = tree.Load(); err != nil { - return nil, errors.Wrap(err, "Loading tree") - } - } - - res := &Store{ - State: state.NewState(tree), - logger: logger, - } - res.height = res.State.LatestHeight() - return res, nil -} - -// Hash gets the last hash stored in the database -func (s *Store) Hash() []byte { - return s.State.LatestHash() -} - -// Info implements abci.Application. It returns the height, hash and size (in the data). -// The height is the block that holds the transactions, not the apphash itself. -func (s *Store) Info() abci.ResponseInfo { - s.logger.Info("Info synced", - "height", s.height, - "hash", fmt.Sprintf("%X", s.Hash())) - return abci.ResponseInfo{ - Data: cmn.Fmt("size:%v", s.State.Size()), - LastBlockHeight: s.height, - LastBlockAppHash: s.Hash(), - } -} - -// Commit implements abci.Application -func (s *Store) Commit() abci.Result { - s.height++ - - hash, err := s.State.Commit(s.height) - if err != nil { - return abci.NewError(abci.CodeType_InternalError, err.Error()) - } - s.logger.Debug("Commit synced", - "height", s.height, - "hash", fmt.Sprintf("%X", hash), - ) - - if s.State.Size() == 0 { - return abci.NewResultOK(nil, "Empty hash for empty tree") - } - return abci.NewResultOK(hash, "") -} - -// Query implements abci.Application -func (s *Store) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { - // set the query response height to current - tree := s.State.Committed() - - height := reqQuery.Height - if height == 0 { - // TODO: once the rpc actually passes in non-zero - // heights we can use to query right after a tx - // we must retrun most recent, even if apphash - // is not yet in the blockchain - - // if tree.Tree.VersionExists(s.height - 1) { - // height = s.height - 1 - // } else { - height = s.height - // } - } - resQuery.Height = height - - switch reqQuery.Path { - case "/store", "/key": // Get by key - key := reqQuery.Data // Data holds the key bytes - resQuery.Key = key - if reqQuery.Prove { - value, proof, err := tree.GetVersionedWithProof(key, height) - if err != nil { - resQuery.Log = err.Error() - break - } - resQuery.Value = value - resQuery.Proof = proof.Bytes() - } else { - value := tree.Get(key) - resQuery.Value = value - } - - default: - resQuery.Code = abci.CodeType_UnknownRequest - resQuery.Log = cmn.Fmt("Unexpected Query path: %v", reqQuery.Path) - } - return -} +// // MockStore returns an in-memory store only intended for testing +// func MockStore() *Store { +// res, err := NewStore("", 0, log.NewNopLogger()) +// if err != nil { +// // should never happen, abort test if it does +// panic(err) +// } +// return res +// } diff --git a/state/merkle.go b/state/merkle.go index 14ad530024..ec81c6d002 100644 --- a/state/merkle.go +++ b/state/merkle.go @@ -11,9 +11,9 @@ type State struct { persistent bool } -func NewState(tree *iavl.VersionedTree) State { +func NewState(tree *iavl.VersionedTree) *State { base := NewBonsai(tree) - return State{ + return &State{ committed: base, deliverTx: base.Checkpoint(), checkTx: base.Checkpoint(), From c1d36eeb217276352d68afad158d6b34e273f7b9 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 16 Oct 2017 16:56:20 +0200 Subject: [PATCH 04/15] Fixed all tests --- app/app.go | 20 +++++++------- app/bc.go | 4 +-- benchmarks/app_test.go | 23 +++++++--------- client/query_test.go | 4 +-- .../counter/plugins/counter/counter_test.go | 9 +++---- server/commands/start.go | 26 +++++++++---------- 6 files changed, 41 insertions(+), 45 deletions(-) diff --git a/app/app.go b/app/app.go index aa06cce8ea..47fb34f311 100644 --- a/app/app.go +++ b/app/app.go @@ -33,9 +33,13 @@ type BaseApp struct { info *sm.ChainState *sm.State + // cached validator changes from DeliverTx pending []*abci.Validator - height uint64 - logger log.Logger + + // height is last committed block, DeliverTx is the next one + height uint64 + + logger log.Logger } // NewBaseApp creates a data store to handle queries @@ -45,8 +49,9 @@ func NewBaseApp(dbName string, cacheSize int, logger log.Logger) (*BaseApp, erro return nil, err } app := &BaseApp{ - info: sm.NewChainState(), State: state, + height: state.LatestHeight(), + info: sm.NewChainState(), logger: logger, } return app, nil @@ -163,14 +168,10 @@ func (app *BaseApp) Commit() (res abci.Result) { // InitChain - ABCI func (app *BaseApp) InitChain(req abci.RequestInitChain) { - // for _, plugin := range app.plugins.GetList() { - // plugin.InitChain(app.state, validators) - // } } // BeginBlock - ABCI func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) { - app.height++ } // EndBlock - ABCI @@ -182,6 +183,9 @@ func (app *BaseApp) EndBlock(height uint64) (res abci.ResponseEndBlock) { return } +// 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) { for _, d := range diffs { idx := pubKeyIndex(d, app.pending) @@ -203,8 +207,6 @@ func pubKeyIndex(val *abci.Validator, list []*abci.Validator) int { return -1 } -//TODO move split key to tmlibs? - // Splits the string at the first '/'. // if there are none, assign default module ("base"). func splitKey(key string) (string, string) { diff --git a/app/bc.go b/app/bc.go index f681a7c52b..183e48bf43 100644 --- a/app/bc.go +++ b/app/bc.go @@ -75,7 +75,7 @@ func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result { ctx := stack.NewContext( app.GetChainID(), - app.height, + app.height+1, app.Logger().With("call", "delivertx"), ) res, err := app.handler.DeliverTx(ctx, app.Append(), tx) @@ -96,7 +96,7 @@ func (app *Basecoin) CheckTx(txBytes []byte) abci.Result { ctx := stack.NewContext( app.GetChainID(), - app.height, + app.height+1, app.Logger().With("call", "checktx"), ) res, err := app.handler.CheckTx(ctx, app.Check(), tx) diff --git a/benchmarks/app_test.go b/benchmarks/app_test.go index acbf182f78..a849120156 100644 --- a/benchmarks/app_test.go +++ b/benchmarks/app_test.go @@ -53,25 +53,22 @@ func NewBenchApp(h sdk.Handler, chainID string, n int, // logger := log.NewFilter(log.NewTMLogger(os.Stdout), log.AllowError()) // logger = log.NewTracingLogger(logger) - // TODO: disk writing - var store *app.Store - var err error - + dbDir, cache := "", 0 if persist { - tmpDir, _ := ioutil.TempDir("", "bc-app-benchmark") - store, err = app.NewStore(tmpDir, 500, logger) - } else { - store, err = app.NewStore("", 0, logger) + dbDir, _ = ioutil.TempDir("", "bc-app-benchmark") + cache = 500 } + + app, err := app.NewBasecoin( + h, + dbDir, + cache, + logger.With("module", "app"), + ) if err != nil { panic(err) } - app := app.NewBasecoin( - h, - store, - logger.With("module", "app"), - ) res := app.InitState("base/chain_id", chainID) if res != "Success" { panic("cannot set chain") diff --git a/client/query_test.go b/client/query_test.go index 5d6a5bf5c4..fe8ee41801 100644 --- a/client/query_test.go +++ b/client/query_test.go @@ -26,11 +26,11 @@ var node *nm.Node func TestMain(m *testing.M) { logger := log.TestingLogger() - store, err := app.NewStore("", 0, logger) + app, err := app.NewBasecoin(eyes.NewHandler(), "", 0, logger) if err != nil { panic(err) } - app := app.NewBasecoin(eyes.NewHandler(), store, logger) + node = rpctest.StartTendermint(app) code := m.Run() diff --git a/examples/counter/plugins/counter/counter_test.go b/examples/counter/plugins/counter/counter_test.go index 50af3b3d9b..b9630706ef 100644 --- a/examples/counter/plugins/counter/counter_test.go +++ b/examples/counter/plugins/counter/counter_test.go @@ -27,15 +27,14 @@ func TestCounterPlugin(t *testing.T) { logger := log.TestingLogger() // logger := log.NewTracingLogger(log.NewTMLogger(os.Stdout)) - store, err := app.NewStore("", 0, logger.With("module", "store")) - require.Nil(err, "%+v", err) - h := NewHandler("gold") - bcApp := app.NewBasecoin( + bcApp, err := app.NewBasecoin( h, - store, + "", + 0, logger.With("module", "app"), ) + require.Nil(err, "%+v", err) bcApp.InitState("base/chain_id", chainID) // Account initialization diff --git a/server/commands/start.go b/server/commands/start.go index 494660300a..825e9bfd35 100644 --- a/server/commands/start.go +++ b/server/commands/start.go @@ -74,38 +74,36 @@ func tickStartCmd(tick app.Ticker) func(cmd *cobra.Command, args []string) error return func(cmd *cobra.Command, args []string) error { rootDir := viper.GetString(cli.HomeFlag) - store, err := app.NewStore( + // Create Basecoin app + basecoinApp, err := app.NewBasecoinTick( + Handler, + tick, path.Join(rootDir, "data", "merkleeyes.db"), EyesCacheSize, - logger.With("module", "store"), - ) + logger.With("module", "app")) if err != nil { return err } - - // Create Basecoin app - basecoinApp := app.NewBasecoinTick(Handler, store, logger.With("module", "app"), tick) - return start(rootDir, store, basecoinApp) + return start(rootDir, basecoinApp) } } func startCmd(cmd *cobra.Command, args []string) error { rootDir := viper.GetString(cli.HomeFlag) - store, err := app.NewStore( + // Create Basecoin app + basecoinApp, err := app.NewBasecoin( + Handler, path.Join(rootDir, "data", "merkleeyes.db"), EyesCacheSize, - logger.With("module", "store"), - ) + logger.With("module", "app")) if err != nil { return err } - // Create Basecoin app - basecoinApp := app.NewBasecoin(Handler, store, logger.With("module", "app")) - return start(rootDir, store, basecoinApp) + return start(rootDir, basecoinApp) } -func start(rootDir string, store *app.Store, basecoinApp *app.Basecoin) error { +func start(rootDir string, basecoinApp *app.Basecoin) error { // if chain_id has not been set yet, load the genesis. // else, assume it's been loaded From df0f3a22da2c865cd7aeefbfa237081490934a13 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 16 Oct 2017 17:34:29 +0200 Subject: [PATCH 05/15] Separate genesis parsing from basecoin --- app/app.go | 13 +---- app/app_test.go | 33 ++++++------ app/bc.go | 35 +++++++++---- app/genesis.go | 52 +++++++++++++++---- benchmarks/app_test.go | 8 +-- .../counter/plugins/counter/counter_test.go | 7 +-- 6 files changed, 93 insertions(+), 55 deletions(-) diff --git a/app/app.go b/app/app.go index 47fb34f311..fcac9cb9fe 100644 --- a/app/app.go +++ b/app/app.go @@ -20,8 +20,7 @@ import ( //nolint const ( - ModuleNameBase = "base" - ChainKey = "chain_id" + ChainKey = "chain_id" ) // BaseApp contains a data store and all info needed @@ -207,16 +206,6 @@ func pubKeyIndex(val *abci.Validator, list []*abci.Validator) int { return -1 } -// Splits the string at the first '/'. -// if there are none, assign default module ("base"). -func splitKey(key string) (string, string) { - if strings.Contains(key, "/") { - keyParts := strings.SplitN(key, "/", 2) - return keyParts[0], keyParts[1] - } - return ModuleNameBase, key -} - func loadState(dbName string, cacheSize int) (*sm.State, error) { // memory backed case, just for testing if dbName == "" { diff --git a/app/app_test.go b/app/app_test.go index bcf32dfbeb..5be1320daf 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -100,8 +100,8 @@ func (at *appTest) feeTx(coins coin.Coins, toll coin.Coin, sequence uint32) sdk. // set the account on the app through InitState func (at *appTest) initAccount(acct *coin.AccountWithKey) { - res := at.app.InitState("coin/account", acct.MakeOption()) - require.EqualValues(at.t, res, "Success") + _, err := at.app.InitState("coin", "account", acct.MakeOption()) + require.Nil(at.t, err, "%+v", err) } // reset the in and out accs to be one account each with 7mycoin @@ -122,8 +122,8 @@ func (at *appTest) reset() { ) require.Nil(at.t, err, "%+v", err) - res := at.app.InitState("base/chain_id", at.chainID) - require.EqualValues(at.t, res, "Success") + _, err = at.app.InitState("base", "chain_id", at.chainID) + require.Nil(at.t, err, "%+v", err) at.initAccount(at.acctIn) at.initAccount(at.acctOut) @@ -183,15 +183,15 @@ func TestInitState(t *testing.T) { //testing ChainID chainID := "testChain" - res := app.InitState("base/chain_id", chainID) + _, err = app.InitState("base", "chain_id", chainID) + require.Nil(err, "%+v", err) assert.EqualValues(app.GetChainID(), chainID) - assert.EqualValues(res, "Success") // make a nice account... bal := coin.Coins{{"atom", 77}, {"eth", 12}} acct := coin.NewAccountWithKey(bal) - res = app.InitState("coin/account", acct.MakeOption()) - require.EqualValues(res, "Success") + _, err = app.InitState("coin", "account", acct.MakeOption()) + require.Nil(err, "%+v", err) // make sure it is set correctly, with some balance coins, err := getBalance(acct.Actor(), app.Append()) @@ -218,23 +218,22 @@ func TestInitState(t *testing.T) { } ] }` - res = app.InitState("coin/account", unsortAcc) - require.EqualValues(res, "Success") + _, err = app.InitState("coin", "account", unsortAcc) + require.Nil(err, "%+v", err) coins, err = getAddr(unsortAddr, app.Append()) require.Nil(err) assert.True(coins.IsValid()) assert.Equal(unsortCoins, coins) - res = app.InitState("base/dslfkgjdas", "") - assert.NotEqual(res, "Success") + _, err = app.InitState("base", "dslfkgjdas", "") + require.NotNil(err) - res = app.InitState("dslfkgjdas", "") - assert.NotEqual(res, "Success") - - res = app.InitState("dslfkgjdas/szfdjzs", "") - assert.NotEqual(res, "Success") + _, err = app.InitState("", "dslfkgjdas", "") + require.NotNil(err) + _, err = app.InitState("dslfkgjdas", "szfdjzs", "") + require.NotNil(err) } // Test CheckTx and DeliverTx with insufficient and sufficient balance diff --git a/app/bc.go b/app/bc.go index 183e48bf43..b876156d89 100644 --- a/app/bc.go +++ b/app/bc.go @@ -47,23 +47,18 @@ func NewBasecoinTick(handler sdk.Handler, tick Ticker, dbName string, cacheSize // InitState - used to setup state (was SetOption) // to be used by InitChain later -func (app *Basecoin) InitState(key string, value string) string { - module, key := splitKey(key) +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" + return "Success", nil } - return fmt.Sprintf("Error: unknown base option: %s", key) + return "", fmt.Errorf("unknown base option: %s", key) } - log, err := app.handler.InitState(app.Logger(), state, module, key, value) - if err == nil { - return log - } - return "Error: " + err.Error() + return app.handler.InitState(app.Logger(), state, module, key, value) } // DeliverTx - ABCI @@ -121,3 +116,25 @@ func (app *Basecoin) BeginBlock(req abci.RequestBeginBlock) { app.AddValChange(diff) } } + +// LoadGenesis parses the genesis file and sets the initial +// state based on that +func (app *Basecoin) LoadGenesis(filePath string) error { + init, err := GetInitialState(filePath) + if err != nil { + return err + } + + // execute all the genesis init options + // abort on any error + fmt.Printf("%#v\n", init) + for _, mkv := range init { + log, _ := app.InitState(mkv.Module, mkv.Key, mkv.Value) + // TODO: error out on bad options?? + // if err != nil { + // return err + // } + app.Logger().Info(log) + } + return nil +} diff --git a/app/genesis.go b/app/genesis.go index 81d99fead9..ceb0e1156e 100644 --- a/app/genesis.go +++ b/app/genesis.go @@ -2,33 +2,55 @@ package app import ( "encoding/json" + "strings" "github.com/pkg/errors" cmn "github.com/tendermint/tmlibs/common" ) -// LoadGenesis - Load the genesis file into memory -func (app *Basecoin) LoadGenesis(path string) error { +//nolint +const ( + ModuleNameBase = "base" +) + +// InitState just holds module/key/value triples from +// parsing the genesis file +type InitState struct { + Module string + Key string + Value string +} + +// GetInitialState parses the genesis file in a format +// that can easily be handed into InitState modules +func GetInitialState(path string) ([]InitState, error) { genDoc, err := loadGenesis(path) if err != nil { - return err + return nil, err } - // set chain_id - app.InitState("base/chain_id", genDoc.ChainID) + opts := genDoc.AppOptions + cnt := 1 + len(opts.Accounts) + len(opts.pluginOptions) + res := make([]InitState, cnt) + + res[0] = InitState{ModuleNameBase, ChainKey, genDoc.ChainID} + i := 1 // set accounts - for _, acct := range genDoc.AppOptions.Accounts { - _ = app.InitState("coin/account", string(acct)) + for _, acct := range opts.Accounts { + res[i] = InitState{"coin", "account", string(acct)} + i++ } // set plugin options - for _, kv := range genDoc.AppOptions.pluginOptions { - _ = app.InitState(kv.Key, kv.Value) + for _, kv := range opts.pluginOptions { + module, key := splitKey(kv.Key) + res[i] = InitState{module, key, kv.Value} + i++ } - return nil + return res, nil } type keyValue struct { @@ -97,3 +119,13 @@ func parseGenesisList(kvzIn []json.RawMessage) (kvz []keyValue, err error) { } return kvz, nil } + +// Splits the string at the first '/'. +// if there are none, assign default module ("base"). +func splitKey(key string) (string, string) { + if strings.Contains(key, "/") { + keyParts := strings.SplitN(key, "/", 2) + return keyParts[0], keyParts[1] + } + return ModuleNameBase, key +} diff --git a/benchmarks/app_test.go b/benchmarks/app_test.go index a849120156..690727e74a 100644 --- a/benchmarks/app_test.go +++ b/benchmarks/app_test.go @@ -69,8 +69,8 @@ func NewBenchApp(h sdk.Handler, chainID string, n int, panic(err) } - res := app.InitState("base/chain_id", chainID) - if res != "Success" { + _, err = app.InitState("base", "chain_id", chainID) + if err != nil { panic("cannot set chain") } @@ -79,8 +79,8 @@ func NewBenchApp(h sdk.Handler, chainID string, n int, accts := make([]*coin.AccountWithKey, n) for i := 0; i < n; i++ { accts[i] = coin.NewAccountWithKey(money) - res := app.InitState("coin/account", accts[i].MakeOption()) - if res != "Success" { + _, err = app.InitState("coin", "account", accts[i].MakeOption()) + if err != nil { panic("can't set account") } } diff --git a/examples/counter/plugins/counter/counter_test.go b/examples/counter/plugins/counter/counter_test.go index b9630706ef..a10e0afbbb 100644 --- a/examples/counter/plugins/counter/counter_test.go +++ b/examples/counter/plugins/counter/counter_test.go @@ -35,13 +35,14 @@ func TestCounterPlugin(t *testing.T) { logger.With("module", "app"), ) require.Nil(err, "%+v", err) - bcApp.InitState("base/chain_id", chainID) + _, err = bcApp.InitState("base", "chain_id", chainID) + require.Nil(err, "%+v", err) // Account initialization bal := coin.Coins{{"", 1000}, {"gold", 1000}} acct := coin.NewAccountWithKey(bal) - log := bcApp.InitState("coin/account", acct.MakeOption()) - require.Equal("Success", log) + _, err = bcApp.InitState("coin", "account", acct.MakeOption()) + require.Nil(err, "%+v", err) // Deliver a CounterTx DeliverCounterTx := func(valid bool, counterFee coin.Coins, sequence uint32) abci.Result { From adab86c94747d3d65c341789616c45aa4e56db39 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 16 Oct 2017 18:14:55 +0200 Subject: [PATCH 06/15] Cleanup --- app/app.go | 10 +++++++--- app/bc.go | 7 +++++-- app/store.go | 11 ----------- 3 files changed, 12 insertions(+), 16 deletions(-) delete mode 100644 app/store.go diff --git a/app/app.go b/app/app.go index fcac9cb9fe..c8a03c6fe2 100644 --- a/app/app.go +++ b/app/app.go @@ -15,7 +15,6 @@ import ( "github.com/cosmos/cosmos-sdk/errors" sm "github.com/cosmos/cosmos-sdk/state" - "github.com/cosmos/cosmos-sdk/version" ) //nolint @@ -29,6 +28,10 @@ const ( // It should be embeded in another struct for CheckTx, // DeliverTx and initializing state from the genesis. type BaseApp struct { + // Name is what is returned from info + Name string + + // this is the database state info *sm.ChainState *sm.State @@ -42,12 +45,13 @@ type BaseApp struct { } // NewBaseApp creates a data store to handle queries -func NewBaseApp(dbName string, cacheSize int, logger log.Logger) (*BaseApp, error) { +func NewBaseApp(appName, dbName string, cacheSize int, logger log.Logger) (*BaseApp, error) { state, err := loadState(dbName, cacheSize) if err != nil { return nil, err } app := &BaseApp{ + Name: appName, State: state, height: state.LatestHeight(), info: sm.NewChainState(), @@ -84,7 +88,7 @@ func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo { return abci.ResponseInfo{ // TODO - Data: fmt.Sprintf("Basecoin v%v", version.Version), + Data: app.Name, LastBlockHeight: app.height, LastBlockAppHash: hash, } diff --git a/app/bc.go b/app/bc.go index b876156d89..81e559f4a1 100644 --- a/app/bc.go +++ b/app/bc.go @@ -4,6 +4,7 @@ import ( "fmt" abci "github.com/tendermint/abci/types" + "github.com/tendermint/abci/version" "github.com/tendermint/tmlibs/log" sdk "github.com/cosmos/cosmos-sdk" @@ -26,7 +27,8 @@ 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) { - base, err := NewBaseApp(dbName, cacheSize, logger) + appName := fmt.Sprintf("Basecoin v%v", version.Version) + base, err := NewBaseApp(appName, dbName, cacheSize, logger) app := &Basecoin{ BaseApp: base, handler: handler, @@ -36,7 +38,8 @@ func NewBasecoin(handler sdk.Handler, dbName string, cacheSize int, logger log.L // 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) { - base, err := NewBaseApp(dbName, cacheSize, logger) + appName := fmt.Sprintf("Basecoin v%v", version.Version) + base, err := NewBaseApp(appName, dbName, cacheSize, logger) app := &Basecoin{ BaseApp: base, handler: handler, diff --git a/app/store.go b/app/store.go deleted file mode 100644 index ff33d7dbcb..0000000000 --- a/app/store.go +++ /dev/null @@ -1,11 +0,0 @@ -package app - -// // MockStore returns an in-memory store only intended for testing -// func MockStore() *Store { -// res, err := NewStore("", 0, log.NewNopLogger()) -// if err != nil { -// // should never happen, abort test if it does -// panic(err) -// } -// return res -// } From 3a16fa9482dfc358287b54c5aada71ae8ba31211 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 16 Oct 2017 18:31:07 +0200 Subject: [PATCH 07/15] Clean up genesis handling --- app/bc.go | 32 ++++++++--------------------- app/genesis.go | 44 ++++++++++++++++++++++++++++++++-------- app/genesis_test.go | 6 +++--- server/commands/start.go | 2 +- 4 files changed, 47 insertions(+), 37 deletions(-) diff --git a/app/bc.go b/app/bc.go index 81e559f4a1..46d1a1ca0f 100644 --- a/app/bc.go +++ b/app/bc.go @@ -4,13 +4,13 @@ import ( "fmt" abci "github.com/tendermint/abci/types" - "github.com/tendermint/abci/version" "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 @@ -61,7 +61,13 @@ func (app *Basecoin) InitState(module, key, value string) (string, error) { return "", fmt.Errorf("unknown base option: %s", key) } - return app.handler.InitState(app.Logger(), state, module, key, value) + 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 @@ -119,25 +125,3 @@ func (app *Basecoin) BeginBlock(req abci.RequestBeginBlock) { app.AddValChange(diff) } } - -// LoadGenesis parses the genesis file and sets the initial -// state based on that -func (app *Basecoin) LoadGenesis(filePath string) error { - init, err := GetInitialState(filePath) - if err != nil { - return err - } - - // execute all the genesis init options - // abort on any error - fmt.Printf("%#v\n", init) - for _, mkv := range init { - log, _ := app.InitState(mkv.Module, mkv.Key, mkv.Value) - // TODO: error out on bad options?? - // if err != nil { - // return err - // } - app.Logger().Info(log) - } - return nil -} diff --git a/app/genesis.go b/app/genesis.go index ceb0e1156e..29031d269a 100644 --- a/app/genesis.go +++ b/app/genesis.go @@ -14,17 +14,43 @@ const ( ModuleNameBase = "base" ) -// InitState just holds module/key/value triples from +// Option just holds module/key/value triples from // parsing the genesis file -type InitState struct { +type Option struct { Module string Key string Value string } -// GetInitialState parses the genesis file in a format -// that can easily be handed into InitState modules -func GetInitialState(path string) ([]InitState, error) { +// InitStater is anything that can handle app options +// from genesis file. +type InitStater interface { + InitState(module, key, value string) (string, error) +} + +// LoadGenesis parses the genesis file and sets the initial +// state based on that +func LoadGenesis(app InitStater, filePath string) error { + opts, err := GetGenesisOptions(filePath) + if err != nil { + return err + } + + // execute all the genesis init options + // abort on any error + for _, opt := range opts { + _, _ = app.InitState(opt.Module, opt.Key, opt.Value) + // TODO: error out on bad options?? + // if err != nil { + // return err + // } + } + return nil +} + +// GetGenesisOptions parses the genesis file in a format +// that can easily be handed into InitStaters +func GetGenesisOptions(path string) ([]Option, error) { genDoc, err := loadGenesis(path) if err != nil { return nil, err @@ -32,21 +58,21 @@ func GetInitialState(path string) ([]InitState, error) { opts := genDoc.AppOptions cnt := 1 + len(opts.Accounts) + len(opts.pluginOptions) - res := make([]InitState, cnt) + res := make([]Option, cnt) - res[0] = InitState{ModuleNameBase, ChainKey, genDoc.ChainID} + res[0] = Option{ModuleNameBase, ChainKey, genDoc.ChainID} i := 1 // set accounts for _, acct := range opts.Accounts { - res[i] = InitState{"coin", "account", string(acct)} + res[i] = Option{"coin", "account", string(acct)} i++ } // set plugin options for _, kv := range opts.pluginOptions { module, key := splitKey(kv.Key) - res[i] = InitState{module, key, kv.Value} + res[i] = Option{module, key, kv.Value} i++ } diff --git a/app/genesis_test.go b/app/genesis_test.go index ccb1229dcc..ee73ceb0f4 100644 --- a/app/genesis_test.go +++ b/app/genesis_test.go @@ -25,7 +25,7 @@ func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) { logger) require.Nil(t, err, "%+v", err) - err = app.LoadGenesis("./testdata/genesis3.json") + err = LoadGenesis(app, "./testdata/genesis3.json") require.Nil(t, err, "%+v", err) } @@ -39,7 +39,7 @@ func TestLoadGenesis(t *testing.T) { logger) require.Nil(err, "%+v", err) - err = app.LoadGenesis(genesisFilepath) + err = LoadGenesis(app, genesisFilepath) require.Nil(err, "%+v", err) // check the chain id @@ -73,7 +73,7 @@ func TestLoadGenesisAccountAddress(t *testing.T) { logger) require.Nil(err, "%+v", err) - err = app.LoadGenesis(genesisAcctFilepath) + err = LoadGenesis(app, genesisAcctFilepath) require.Nil(err, "%+v", err) // check the chain id diff --git a/server/commands/start.go b/server/commands/start.go index 825e9bfd35..117ec6b2bf 100644 --- a/server/commands/start.go +++ b/server/commands/start.go @@ -111,7 +111,7 @@ func start(rootDir string, basecoinApp *app.Basecoin) error { // If genesis file exists, set key-value options genesisFile := path.Join(rootDir, "genesis.json") if _, err := os.Stat(genesisFile); err == nil { - err := basecoinApp.LoadGenesis(genesisFile) + err = app.LoadGenesis(basecoinApp, genesisFile) if err != nil { return errors.Errorf("Error in LoadGenesis: %v\n", err) } From f65215ad922f55862e6baf33ef2dfe714655962c Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 16 Oct 2017 19:21:43 +0200 Subject: [PATCH 08/15] 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 From 3316bfcfb8928867e7f068a4948443d273345bca Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 16 Oct 2017 19:36:03 +0200 Subject: [PATCH 09/15] Abort on error with genesis app_options --- app/genesis.go | 9 ++--- app/genesis_test.go | 74 ++++++++++++++++++++++++------------- app/testdata/genesis2.json | 13 ------- app/testdata/genesis2b.json | 52 ++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 44 deletions(-) create mode 100644 app/testdata/genesis2b.json diff --git a/app/genesis.go b/app/genesis.go index 29031d269a..17d9bdebea 100644 --- a/app/genesis.go +++ b/app/genesis.go @@ -39,11 +39,10 @@ func LoadGenesis(app InitStater, filePath string) error { // execute all the genesis init options // abort on any error for _, opt := range opts { - _, _ = app.InitState(opt.Module, opt.Key, opt.Value) - // TODO: error out on bad options?? - // if err != nil { - // return err - // } + _, err = app.InitState(opt.Module, opt.Key, opt.Value) + if err != nil { + return err + } } return nil } diff --git a/app/genesis_test.go b/app/genesis_test.go index 14b841480b..f62164f696 100644 --- a/app/genesis_test.go +++ b/app/genesis_test.go @@ -17,6 +17,10 @@ import ( const genesisFilepath = "./testdata/genesis.json" const genesisAcctFilepath = "./testdata/genesis2.json" +// 2b is just like 2, but add carl who has inconsistent +// pubkey and address +const genesisBadAcctFilepath = "./testdata/genesis2b.json" + func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) { logger := log.TestingLogger() store, err := MockStoreApp("genesis", logger) @@ -27,35 +31,16 @@ func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) { require.Nil(t, err, "%+v", err) } -func TestLoadGenesis(t *testing.T) { - assert, require := assert.New(t), require.New(t) +func TestLoadGenesisFailsWithUnknownOptions(t *testing.T) { + require := require.New(t) logger := log.TestingLogger() 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) - - // check the chain id - assert.Equal("foo_bar_chain", app.GetChainID()) - - // and check the account info - previously calculated values - addr, _ := hex.DecodeString("eb98e0688217cfdeb70eddf4b33cdcc37fc53197") - - coins, err := getAddr(addr, app.Append()) - require.Nil(err) - assert.True(coins.IsPositive()) - - // make sure balance is proper - assert.Equal(2, len(coins)) - assert.True(coins.IsValid()) - // note, that we now sort them to be valid - assert.EqualValues(654321, coins[0].Amount) - assert.EqualValues("ETH", coins[0].Denom) - assert.EqualValues(12345, coins[1].Amount) - assert.EqualValues("blank", coins[1].Denom) + require.NotNil(err, "%+v", err) } // Fix for issue #89, change the parse format for accounts in genesis.json @@ -84,9 +69,6 @@ func TestLoadGenesisAccountAddress(t *testing.T) { {"62035D628DE7543332544AA60D90D3693B6AD51B", true, true, coin.Coins{{"one", 111}}}, // this comes from an address, should be stored proper (bob) {"C471FB670E44D219EE6DF2FC284BE38793ACBCE1", true, false, coin.Coins{{"two", 222}}}, - // this one had a mismatched address and pubkey, should not store under either (carl) - {"1234ABCDD18E8EFE3FFC4B0506BF9BF8E5B0D9E9", false, false, nil}, // this is given addr - {"700BEC5ED18E8EFE3FFC4B0506BF9BF8E5B0D9E9", false, false, nil}, // this is addr of the given pubkey // this comes from a secp256k1 public key, should be stored proper (sam) {"979F080B1DD046C452C2A8A250D18646C6B669D4", true, true, coin.Coins{{"four", 444}}}, } @@ -106,6 +88,19 @@ func TestLoadGenesisAccountAddress(t *testing.T) { } } +// When you define an account in genesis with address +// and pubkey that don't match +func TestLoadGenesisAccountInconsistentAddress(t *testing.T) { + require := require.New(t) + + logger := log.TestingLogger() + store, err := MockStoreApp("genesis", logger) + require.Nil(err, "%+v", err) + app := NewBaseApp(store, DefaultHandler("mycoin"), nil) + err = LoadGenesis(app, genesisBadAcctFilepath) + require.NotNil(err) +} + func TestParseGenesisList(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -126,3 +121,30 @@ func TestParseGenesisList(t *testing.T) { assert.Equal(genDoc.AppOptions.pluginOptions[0].Value, "value1") assert.Equal(genDoc.AppOptions.pluginOptions[1].Value, "value2") } + +func TestGetGenesisOptions(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + opts, err := GetGenesisOptions(genesisFilepath) + require.Nil(err, "loading genesis file %+v", err) + + require.Equal(4, len(opts)) + chain := opts[0] + assert.Equal(ModuleNameBase, chain.Module) + assert.Equal(ChainKey, chain.Key) + assert.Equal("foo_bar_chain", chain.Value) + + acct := opts[1] + assert.Equal("coin", acct.Module) + assert.Equal("account", acct.Key) + + p1 := opts[2] + assert.Equal("plugin1", p1.Module) + assert.Equal("key1", p1.Key) + assert.Equal("value1", p1.Value) + + p2 := opts[3] + assert.Equal("plugin1", p2.Module) + assert.Equal("key2", p2.Key) + assert.Equal("value2", p2.Value) +} diff --git a/app/testdata/genesis2.json b/app/testdata/genesis2.json index a880b3c64c..18ec87164f 100644 --- a/app/testdata/genesis2.json +++ b/app/testdata/genesis2.json @@ -22,19 +22,6 @@ "amount": 222 } ] - }, { - "name": "carl", - "address": "1234ABCDD18E8EFE3FFC4B0506BF9BF8E5B0D9E9", - "pub_key": { - "type": "ed25519", - "data": "177C0AC45E86257F0708DC085D592AB22AAEECD1D26381B757F7C96135921858" - }, - "coins": [ - { - "denom": "three", - "amount": 333 - } - ] }, { "name": "sam", "pub_key": { diff --git a/app/testdata/genesis2b.json b/app/testdata/genesis2b.json new file mode 100644 index 0000000000..a880b3c64c --- /dev/null +++ b/app/testdata/genesis2b.json @@ -0,0 +1,52 @@ +{ + "chain_id": "addr_accounts_chain", + "app_options": { + "accounts": [{ + "name": "alice", + "pub_key": { + "type": "ed25519", + "data": "DBD9A46C45868F0A37C92B53113C09B048FBD87B5FBC2F8B199052973B8FAA36" + }, + "coins": [ + { + "denom": "one", + "amount": 111 + } + ] + }, { + "name": "bob", + "address": "C471FB670E44D219EE6DF2FC284BE38793ACBCE1", + "coins": [ + { + "denom": "two", + "amount": 222 + } + ] + }, { + "name": "carl", + "address": "1234ABCDD18E8EFE3FFC4B0506BF9BF8E5B0D9E9", + "pub_key": { + "type": "ed25519", + "data": "177C0AC45E86257F0708DC085D592AB22AAEECD1D26381B757F7C96135921858" + }, + "coins": [ + { + "denom": "three", + "amount": 333 + } + ] + }, { + "name": "sam", + "pub_key": { + "type": "secp256k1", + "data": "02AA8342F63CCCCE6DDB128525BA048CE0B2993DA3B4308746E1F216361A87651E" + }, + "coins": [ + { + "denom": "four", + "amount": 444 + } + ] + }] + } +} From 9b206153e279feca2aebb8045af37a430c4656f7 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 16 Oct 2017 19:58:12 +0200 Subject: [PATCH 10/15] Eyes takes init state, fix cli test about genesis --- examples/basecoin/cmd/basecoin/main.go | 3 +++ examples/basecoin/tests/cli/init-server.sh | 6 +++--- modules/eyes/handler.go | 15 +++++++++++++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/examples/basecoin/cmd/basecoin/main.go b/examples/basecoin/cmd/basecoin/main.go index 2a96f76bd1..af31e2fe80 100644 --- a/examples/basecoin/cmd/basecoin/main.go +++ b/examples/basecoin/cmd/basecoin/main.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/modules/auth" "github.com/cosmos/cosmos-sdk/modules/base" "github.com/cosmos/cosmos-sdk/modules/coin" + "github.com/cosmos/cosmos-sdk/modules/eyes" "github.com/cosmos/cosmos-sdk/modules/fee" "github.com/cosmos/cosmos-sdk/modules/ibc" "github.com/cosmos/cosmos-sdk/modules/nonce" @@ -45,6 +46,8 @@ func BuildApp(feeDenom string) sdk.Handler { coin.NewHandler(), stack.WrapHandler(roles.NewHandler()), stack.WrapHandler(ibc.NewHandler()), + // and just for run, add eyes as well + stack.WrapHandler(eyes.NewHandler()), ) } diff --git a/examples/basecoin/tests/cli/init-server.sh b/examples/basecoin/tests/cli/init-server.sh index dfd503df7d..9923540819 100755 --- a/examples/basecoin/tests/cli/init-server.sh +++ b/examples/basecoin/tests/cli/init-server.sh @@ -12,7 +12,7 @@ test01initOption() { GENESIS_FILE=${SERVE_DIR}/genesis.json HEX="deadbeef1234deadbeef1234deadbeef1234aaaa" - ${SERVER_EXE} init ${HEX} --home="$SERVE_DIR" -p=app1/key1/val1 -p='"app2/key2/{""name"": ""joe"", ""age"": ""100""}"' >/dev/null + ${SERVER_EXE} init ${HEX} --home="$SERVE_DIR" -p=eyes/key1/val1 -p='"eyes/key2/{""name"": ""joe"", ""age"": ""100""}"' >/dev/null if ! assertTrue "line=${LINENO}" $?; then return 1; fi OPTION1KEY=$(cat ${GENESIS_FILE} | jq '.app_options.plugin_options[2]') @@ -21,9 +21,9 @@ test01initOption() { OPTION2VAL=$(cat ${GENESIS_FILE} | jq '.app_options.plugin_options[5]') OPTION2VALEXPECTED=$(echo '{"name": "joe", "age": "100"}' | jq '.') - assertEquals "line=${LINENO}" '"app1/key1"' $OPTION1KEY + assertEquals "line=${LINENO}" '"eyes/key1"' $OPTION1KEY assertEquals "line=${LINENO}" '"val1"' $OPTION1VAL - assertEquals "line=${LINENO}" '"app2/key2"' $OPTION2KEY + assertEquals "line=${LINENO}" '"eyes/key2"' $OPTION2KEY assertEquals "line=${LINENO}" "$OPTION2VALEXPECTED" "$OPTION2VAL" } diff --git a/modules/eyes/handler.go b/modules/eyes/handler.go index 478019b33d..34a54877e4 100644 --- a/modules/eyes/handler.go +++ b/modules/eyes/handler.go @@ -1,10 +1,12 @@ package eyes import ( + wire "github.com/tendermint/go-wire" + "github.com/tendermint/tmlibs/log" + sdk "github.com/cosmos/cosmos-sdk" "github.com/cosmos/cosmos-sdk/errors" "github.com/cosmos/cosmos-sdk/state" - wire "github.com/tendermint/go-wire" ) const ( @@ -19,7 +21,6 @@ const ( // Handler allows us to set and remove data type Handler struct { - sdk.NopInitState sdk.NopInitValidate } @@ -35,6 +36,16 @@ func (Handler) Name() string { return Name } +// InitState - sets the genesis state +func (h Handler) InitState(l log.Logger, store state.SimpleDB, + module, key, value string) (log string, err error) { + if module != Name { + return "", errors.ErrUnknownModule(module) + } + store.Set([]byte(key), []byte(value)) + return key, nil +} + // CheckTx verifies if the transaction is properly formated func (h Handler) CheckTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx) (res sdk.CheckResult, err error) { err = tx.ValidateBasic() From 4855b3c9d97e4a2b1e99af9f0aaaf7bf43fd41bf Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 17 Oct 2017 11:54:51 +0200 Subject: [PATCH 11/15] StoreApp explicitly exposes some State methods, not embedded, for better godoc --- app/store.go | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/app/store.go b/app/store.go index 8385515916..8c31d9cb6f 100644 --- a/app/store.go +++ b/app/store.go @@ -32,8 +32,8 @@ type StoreApp struct { Name string // this is the database state - info *sm.ChainState - *sm.State + info *sm.ChainState + state *sm.State // cached validator changes from DeliverTx pending []*abci.Validator @@ -52,7 +52,7 @@ func NewStoreApp(appName, dbName string, cacheSize int, logger log.Logger) (*Sto } app := &StoreApp{ Name: appName, - State: state, + state: state, height: state.LatestHeight(), info: sm.NewChainState(), logger: logger.With("module", "app"), @@ -67,7 +67,7 @@ func MockStoreApp(appName string, logger log.Logger) (*StoreApp, error) { // GetChainID returns the currently stored chain func (app *StoreApp) GetChainID() string { - return app.info.GetChainID(app.Committed()) + return app.info.GetChainID(app.state.Committed()) } // Logger returns the application base logger @@ -77,7 +77,23 @@ func (app *StoreApp) Logger() log.Logger { // Hash gets the last hash stored in the database func (app *StoreApp) Hash() []byte { - return app.State.LatestHash() + return app.state.LatestHash() +} + +// Committed returns the committed state, +// also exposing historical queries +// func (app *StoreApp) Committed() *Bonsai { +// return app.state.committed +// } + +// Append returns the working state for DeliverTx +func (app *StoreApp) Append() sm.SimpleDB { + return app.state.Append() +} + +// Check returns the working state for CheckTx +func (app *StoreApp) Check() sm.SimpleDB { + return app.state.Check() } // CommittedHeight gets the last block height committed @@ -123,7 +139,7 @@ func (app *StoreApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQu } // set the query response height to current - tree := app.State.Committed() + tree := app.state.Committed() height := reqQuery.Height if height == 0 { @@ -168,7 +184,7 @@ func (app *StoreApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQu func (app *StoreApp) Commit() (res abci.Result) { app.height++ - hash, err := app.State.Commit(app.height) + hash, err := app.state.Commit(app.height) if err != nil { // die if we can't commit, not to recover panic(err) @@ -178,7 +194,7 @@ func (app *StoreApp) Commit() (res abci.Result) { "hash", fmt.Sprintf("%X", hash), ) - if app.State.Size() == 0 { + if app.state.Size() == 0 { return abci.NewResultOK(nil, "Empty hash for empty tree") } return abci.NewResultOK(hash, "") From fbe88ee27571dfd582dbe494e5709e5795fb9205 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 17 Oct 2017 12:13:37 +0200 Subject: [PATCH 12/15] Clean up state.State as we only support persistence --- state/merkle.go | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/state/merkle.go b/state/merkle.go index ec81c6d002..2373d27d3b 100644 --- a/state/merkle.go +++ b/state/merkle.go @@ -5,19 +5,17 @@ import "github.com/tendermint/iavl" // State represents the app states, separating the commited state (for queries) // from the working state (for CheckTx and AppendTx) type State struct { - committed *Bonsai - deliverTx SimpleDB - checkTx SimpleDB - persistent bool + committed *Bonsai + deliverTx SimpleDB + checkTx SimpleDB } func NewState(tree *iavl.VersionedTree) *State { base := NewBonsai(tree) return &State{ - committed: base, - deliverTx: base.Checkpoint(), - checkTx: base.Checkpoint(), - persistent: true, + committed: base, + deliverTx: base.Checkpoint(), + checkTx: base.Checkpoint(), } } @@ -45,14 +43,6 @@ func (s State) LatestHash() []byte { return s.committed.Tree.Hash() } -// BatchSet is used for some weird magic in storing the new height -func (s *State) BatchSet(key, value []byte) { - if s.persistent { - // This is in the batch with the Save, but not in the tree - s.committed.Tree.BatchSet(key, value) - } -} - // Commit save persistent nodes to the database and re-copies the trees func (s *State) Commit(version uint64) ([]byte, error) { // commit (if we didn't do hash earlier) @@ -62,15 +52,11 @@ func (s *State) Commit(version uint64) ([]byte, error) { } var hash []byte - if s.persistent { - if s.committed.Tree.Size() > 0 || s.committed.Tree.LatestVersion() > 0 { - hash, err = s.committed.Tree.SaveVersion(version) - if err != nil { - return nil, err - } + if s.committed.Tree.Size() > 0 || s.committed.Tree.LatestVersion() > 0 { + hash, err = s.committed.Tree.SaveVersion(version) + if err != nil { + return nil, err } - } else { - hash = s.committed.Tree.Hash() } s.deliverTx = s.committed.Checkpoint() From 6305399baf2b82955e5525da79943bb2a23dbe14 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 17 Oct 2017 15:47:33 +0200 Subject: [PATCH 13/15] Pulled genesis file parsing into own package, for clarity --- app/app_test.go | 16 ------ app/base.go | 4 +- app/genesis_test.go | 59 ++-------------------- app/store.go | 5 -- app/genesis.go => genesis/parse.go | 12 ++--- genesis/parse_test.go | 78 ++++++++++++++++++++++++++++++ genesis/testdata/genesis.json | 22 +++++++++ handler.go | 7 +++ server/commands/start.go | 3 +- 9 files changed, 120 insertions(+), 86 deletions(-) rename app/genesis.go => genesis/parse.go (95%) create mode 100644 genesis/parse_test.go create mode 100644 genesis/testdata/genesis.json diff --git a/app/app_test.go b/app/app_test.go index 0e90f65413..5d6f84a3c0 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -291,19 +291,3 @@ func TestQuery(t *testing.T) { }) assert.NotEqual(resQueryPreCommit, resQueryPostCommit, "Query should change before/after commit") } - -func TestSplitKey(t *testing.T) { - assert := assert.New(t) - prefix, suffix := splitKey("foo/bar") - assert.EqualValues("foo", prefix) - assert.EqualValues("bar", suffix) - - prefix, suffix = splitKey("foobar") - assert.EqualValues("base", prefix) - assert.EqualValues("foobar", suffix) - - prefix, suffix = splitKey("some/complex/issue") - assert.EqualValues("some", prefix) - assert.EqualValues("complex/issue", suffix) - -} diff --git a/app/base.go b/app/base.go index a0ddbb7e21..8d7e73a1ea 100644 --- a/app/base.go +++ b/app/base.go @@ -95,8 +95,8 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) { func (app *BaseApp) InitState(module, key, value string) (string, error) { state := app.Append() - if module == ModuleNameBase { - if key == ChainKey { + if module == sdk.ModuleNameBase { + if key == sdk.ChainKey { app.info.SetChainID(state, value) return "Success", nil } diff --git a/app/genesis_test.go b/app/genesis_test.go index f62164f696..4707d8c4ee 100644 --- a/app/genesis_test.go +++ b/app/genesis_test.go @@ -2,15 +2,14 @@ package app import ( "encoding/hex" - "encoding/json" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" + "github.com/cosmos/cosmos-sdk/genesis" "github.com/cosmos/cosmos-sdk/modules/coin" ) @@ -27,7 +26,7 @@ func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) { require.Nil(t, err, "%+v", err) app := NewBaseApp(store, DefaultHandler("mycoin"), nil) - err = LoadGenesis(app, "./testdata/genesis3.json") + err = genesis.LoadGenesis(app, "./testdata/genesis3.json") require.Nil(t, err, "%+v", err) } @@ -39,7 +38,7 @@ func TestLoadGenesisFailsWithUnknownOptions(t *testing.T) { require.Nil(err, "%+v", err) app := NewBaseApp(store, DefaultHandler("mycoin"), nil) - err = LoadGenesis(app, genesisFilepath) + err = genesis.LoadGenesis(app, genesisFilepath) require.NotNil(err, "%+v", err) } @@ -52,7 +51,7 @@ func TestLoadGenesisAccountAddress(t *testing.T) { require.Nil(err, "%+v", err) app := NewBaseApp(store, DefaultHandler("mycoin"), nil) - err = LoadGenesis(app, genesisAcctFilepath) + err = genesis.LoadGenesis(app, genesisAcctFilepath) require.Nil(err, "%+v", err) // check the chain id @@ -97,54 +96,6 @@ func TestLoadGenesisAccountInconsistentAddress(t *testing.T) { store, err := MockStoreApp("genesis", logger) require.Nil(err, "%+v", err) app := NewBaseApp(store, DefaultHandler("mycoin"), nil) - err = LoadGenesis(app, genesisBadAcctFilepath) + err = genesis.LoadGenesis(app, genesisBadAcctFilepath) require.NotNil(err) } - -func TestParseGenesisList(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - bytes, err := cmn.ReadFile(genesisFilepath) - require.Nil(err, "loading genesis file %+v", err) - - // the basecoin genesis go-wire/data :) - genDoc := new(FullGenesisDoc) - err = json.Unmarshal(bytes, genDoc) - require.Nil(err, "unmarshaling genesis file %+v", err) - - pluginOpts, err := parseGenesisList(genDoc.AppOptions.PluginOptions) - require.Nil(err, "%+v", err) - genDoc.AppOptions.pluginOptions = pluginOpts - - assert.Equal(genDoc.AppOptions.pluginOptions[0].Key, "plugin1/key1") - assert.Equal(genDoc.AppOptions.pluginOptions[1].Key, "plugin1/key2") - assert.Equal(genDoc.AppOptions.pluginOptions[0].Value, "value1") - assert.Equal(genDoc.AppOptions.pluginOptions[1].Value, "value2") -} - -func TestGetGenesisOptions(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - opts, err := GetGenesisOptions(genesisFilepath) - require.Nil(err, "loading genesis file %+v", err) - - require.Equal(4, len(opts)) - chain := opts[0] - assert.Equal(ModuleNameBase, chain.Module) - assert.Equal(ChainKey, chain.Key) - assert.Equal("foo_bar_chain", chain.Value) - - acct := opts[1] - assert.Equal("coin", acct.Module) - assert.Equal("account", acct.Key) - - p1 := opts[2] - assert.Equal("plugin1", p1.Module) - assert.Equal("key1", p1.Key) - assert.Equal("value1", p1.Value) - - p2 := opts[3] - assert.Equal("plugin1", p2.Module) - assert.Equal("key2", p2.Key) - assert.Equal("value2", p2.Value) -} diff --git a/app/store.go b/app/store.go index 8c31d9cb6f..2ad14915ab 100644 --- a/app/store.go +++ b/app/store.go @@ -17,11 +17,6 @@ import ( sm "github.com/cosmos/cosmos-sdk/state" ) -//nolint -const ( - ChainKey = "chain_id" -) - // StoreApp contains a data store and all info needed // to perform queries and handshakes. // diff --git a/app/genesis.go b/genesis/parse.go similarity index 95% rename from app/genesis.go rename to genesis/parse.go index 17d9bdebea..f0eefd2138 100644 --- a/app/genesis.go +++ b/genesis/parse.go @@ -1,19 +1,15 @@ -package app +package genesis import ( "encoding/json" "strings" + sdk "github.com/cosmos/cosmos-sdk" "github.com/pkg/errors" cmn "github.com/tendermint/tmlibs/common" ) -//nolint -const ( - ModuleNameBase = "base" -) - // Option just holds module/key/value triples from // parsing the genesis file type Option struct { @@ -59,7 +55,7 @@ func GetGenesisOptions(path string) ([]Option, error) { cnt := 1 + len(opts.Accounts) + len(opts.pluginOptions) res := make([]Option, cnt) - res[0] = Option{ModuleNameBase, ChainKey, genDoc.ChainID} + res[0] = Option{sdk.ModuleNameBase, sdk.ChainKey, genDoc.ChainID} i := 1 // set accounts @@ -152,5 +148,5 @@ func splitKey(key string) (string, string) { keyParts := strings.SplitN(key, "/", 2) return keyParts[0], keyParts[1] } - return ModuleNameBase, key + return sdk.ModuleNameBase, key } diff --git a/genesis/parse_test.go b/genesis/parse_test.go new file mode 100644 index 0000000000..5c81d794cc --- /dev/null +++ b/genesis/parse_test.go @@ -0,0 +1,78 @@ +package genesis + +import ( + "encoding/json" + "testing" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + cmn "github.com/tendermint/tmlibs/common" +) + +const genesisFilepath = "./testdata/genesis.json" + +func TestParseGenesisList(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + bytes, err := cmn.ReadFile(genesisFilepath) + require.Nil(err, "loading genesis file %+v", err) + + // the basecoin genesis go-wire/data :) + genDoc := new(FullGenesisDoc) + err = json.Unmarshal(bytes, genDoc) + require.Nil(err, "unmarshaling genesis file %+v", err) + + pluginOpts, err := parseGenesisList(genDoc.AppOptions.PluginOptions) + require.Nil(err, "%+v", err) + genDoc.AppOptions.pluginOptions = pluginOpts + + assert.Equal(genDoc.AppOptions.pluginOptions[0].Key, "plugin1/key1") + assert.Equal(genDoc.AppOptions.pluginOptions[1].Key, "plugin1/key2") + assert.Equal(genDoc.AppOptions.pluginOptions[0].Value, "value1") + assert.Equal(genDoc.AppOptions.pluginOptions[1].Value, "value2") +} + +func TestGetGenesisOptions(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + opts, err := GetGenesisOptions(genesisFilepath) + require.Nil(err, "loading genesis file %+v", err) + + require.Equal(4, len(opts)) + chain := opts[0] + assert.Equal(sdk.ModuleNameBase, chain.Module) + assert.Equal(sdk.ChainKey, chain.Key) + assert.Equal("foo_bar_chain", chain.Value) + + acct := opts[1] + assert.Equal("coin", acct.Module) + assert.Equal("account", acct.Key) + + p1 := opts[2] + assert.Equal("plugin1", p1.Module) + assert.Equal("key1", p1.Key) + assert.Equal("value1", p1.Value) + + p2 := opts[3] + assert.Equal("plugin1", p2.Module) + assert.Equal("key2", p2.Key) + assert.Equal("value2", p2.Value) +} + +func TestSplitKey(t *testing.T) { + assert := assert.New(t) + prefix, suffix := splitKey("foo/bar") + assert.EqualValues("foo", prefix) + assert.EqualValues("bar", suffix) + + prefix, suffix = splitKey("foobar") + assert.EqualValues("base", prefix) + assert.EqualValues("foobar", suffix) + + prefix, suffix = splitKey("some/complex/issue") + assert.EqualValues("some", prefix) + assert.EqualValues("complex/issue", suffix) + +} diff --git a/genesis/testdata/genesis.json b/genesis/testdata/genesis.json new file mode 100644 index 0000000000..ee8879fd28 --- /dev/null +++ b/genesis/testdata/genesis.json @@ -0,0 +1,22 @@ +{ + "chain_id": "foo_bar_chain", + "app_options": { + "accounts": [{ + "pub_key": { + "type": "ed25519", + "data": "6880db93598e283a67c4d88fc67a8858aa2de70f713fe94a5109e29c137100c2" + }, + "coins": [ + { + "denom": "blank", + "amount": 12345 + }, + { + "denom": "ETH", + "amount": 654321 + } + ] + }], + "plugin_options": ["plugin1/key1", "value1", "plugin1/key2", "value2"] + } +} diff --git a/handler.go b/handler.go index 51d8e4a127..f7d6638029 100644 --- a/handler.go +++ b/handler.go @@ -8,6 +8,13 @@ import ( "github.com/cosmos/cosmos-sdk/state" ) +const ( + // ModuleNameBase is the module name for internal functionality + ModuleNameBase = "base" + // ChainKey is the option key for setting the chain id + ChainKey = "chain_id" +) + // Handler is anything that processes a transaction type Handler interface { // Checker verifies there are valid fees and estimates work diff --git a/server/commands/start.go b/server/commands/start.go index 22e79ec1c7..a7f41a329b 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/genesis" "github.com/cosmos/cosmos-sdk/version" ) @@ -119,7 +120,7 @@ func start(rootDir string, basecoinApp *app.BaseApp) error { // If genesis file exists, set key-value options genesisFile := path.Join(rootDir, "genesis.json") if _, err := os.Stat(genesisFile); err == nil { - err = app.LoadGenesis(basecoinApp, genesisFile) + err = genesis.LoadGenesis(basecoinApp, genesisFile) if err != nil { return errors.Errorf("Error in LoadGenesis: %v\n", err) } From 763168015970de5edafb42fb3311e5b0d72a8d74 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 17 Oct 2017 15:51:13 +0200 Subject: [PATCH 14/15] Cleanup names in genesis package --- app/genesis_test.go | 8 ++++---- genesis/parse.go | 34 +++++++++++++++++----------------- genesis/parse_test.go | 10 +++++----- server/commands/start.go | 2 +- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/app/genesis_test.go b/app/genesis_test.go index 4707d8c4ee..a519bc9982 100644 --- a/app/genesis_test.go +++ b/app/genesis_test.go @@ -26,7 +26,7 @@ func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) { require.Nil(t, err, "%+v", err) app := NewBaseApp(store, DefaultHandler("mycoin"), nil) - err = genesis.LoadGenesis(app, "./testdata/genesis3.json") + err = genesis.Load(app, "./testdata/genesis3.json") require.Nil(t, err, "%+v", err) } @@ -38,7 +38,7 @@ func TestLoadGenesisFailsWithUnknownOptions(t *testing.T) { require.Nil(err, "%+v", err) app := NewBaseApp(store, DefaultHandler("mycoin"), nil) - err = genesis.LoadGenesis(app, genesisFilepath) + err = genesis.Load(app, genesisFilepath) require.NotNil(err, "%+v", err) } @@ -51,7 +51,7 @@ func TestLoadGenesisAccountAddress(t *testing.T) { require.Nil(err, "%+v", err) app := NewBaseApp(store, DefaultHandler("mycoin"), nil) - err = genesis.LoadGenesis(app, genesisAcctFilepath) + err = genesis.Load(app, genesisAcctFilepath) require.Nil(err, "%+v", err) // check the chain id @@ -96,6 +96,6 @@ func TestLoadGenesisAccountInconsistentAddress(t *testing.T) { store, err := MockStoreApp("genesis", logger) require.Nil(err, "%+v", err) app := NewBaseApp(store, DefaultHandler("mycoin"), nil) - err = genesis.LoadGenesis(app, genesisBadAcctFilepath) + err = genesis.Load(app, genesisBadAcctFilepath) require.NotNil(err) } diff --git a/genesis/parse.go b/genesis/parse.go index f0eefd2138..6a183464c5 100644 --- a/genesis/parse.go +++ b/genesis/parse.go @@ -24,10 +24,10 @@ type InitStater interface { InitState(module, key, value string) (string, error) } -// LoadGenesis parses the genesis file and sets the initial +// Load parses the genesis file and sets the initial // state based on that -func LoadGenesis(app InitStater, filePath string) error { - opts, err := GetGenesisOptions(filePath) +func Load(app InitStater, filePath string) error { + opts, err := GetOptions(filePath) if err != nil { return err } @@ -43,10 +43,10 @@ func LoadGenesis(app InitStater, filePath string) error { return nil } -// GetGenesisOptions parses the genesis file in a format +// GetOptions parses the genesis file in a format // that can easily be handed into InitStaters -func GetGenesisOptions(path string) ([]Option, error) { - genDoc, err := loadGenesis(path) +func GetOptions(path string) ([]Option, error) { + genDoc, err := load(path) if err != nil { return nil, err } @@ -79,38 +79,38 @@ type keyValue struct { Value string `json:"value"` } -// FullGenesisDoc - includes tendermint (in the json, we ignore here) -type FullGenesisDoc struct { - ChainID string `json:"chain_id"` - AppOptions *GenesisDoc `json:"app_options"` +// FullDoc - includes tendermint (in the json, we ignore here) +type FullDoc struct { + ChainID string `json:"chain_id"` + AppOptions *Doc `json:"app_options"` } -// GenesisDoc - All genesis values -type GenesisDoc struct { +// Doc - All genesis values +type Doc struct { Accounts []json.RawMessage `json:"accounts"` PluginOptions []json.RawMessage `json:"plugin_options"` pluginOptions []keyValue // unmarshaled rawmessages } -func loadGenesis(filePath string) (*FullGenesisDoc, error) { +func load(filePath string) (*FullDoc, error) { bytes, err := cmn.ReadFile(filePath) if err != nil { return nil, errors.Wrap(err, "loading genesis file") } // the basecoin genesis go-wire/data :) - genDoc := new(FullGenesisDoc) + genDoc := new(FullDoc) err = json.Unmarshal(bytes, genDoc) if err != nil { return nil, errors.Wrap(err, "unmarshaling genesis file") } if genDoc.AppOptions == nil { - genDoc.AppOptions = new(GenesisDoc) + genDoc.AppOptions = new(Doc) } - pluginOpts, err := parseGenesisList(genDoc.AppOptions.PluginOptions) + pluginOpts, err := parseList(genDoc.AppOptions.PluginOptions) if err != nil { return nil, err } @@ -118,7 +118,7 @@ func loadGenesis(filePath string) (*FullGenesisDoc, error) { return genDoc, nil } -func parseGenesisList(kvzIn []json.RawMessage) (kvz []keyValue, err error) { +func parseList(kvzIn []json.RawMessage) (kvz []keyValue, err error) { if len(kvzIn)%2 != 0 { return nil, errors.New("genesis cannot have an odd number of items. Format = [key1, value1, key2, value2, ...]") } diff --git a/genesis/parse_test.go b/genesis/parse_test.go index 5c81d794cc..822e2630ac 100644 --- a/genesis/parse_test.go +++ b/genesis/parse_test.go @@ -13,18 +13,18 @@ import ( const genesisFilepath = "./testdata/genesis.json" -func TestParseGenesisList(t *testing.T) { +func TestParseList(t *testing.T) { assert, require := assert.New(t), require.New(t) bytes, err := cmn.ReadFile(genesisFilepath) require.Nil(err, "loading genesis file %+v", err) // the basecoin genesis go-wire/data :) - genDoc := new(FullGenesisDoc) + genDoc := new(FullDoc) err = json.Unmarshal(bytes, genDoc) require.Nil(err, "unmarshaling genesis file %+v", err) - pluginOpts, err := parseGenesisList(genDoc.AppOptions.PluginOptions) + pluginOpts, err := parseList(genDoc.AppOptions.PluginOptions) require.Nil(err, "%+v", err) genDoc.AppOptions.pluginOptions = pluginOpts @@ -34,10 +34,10 @@ func TestParseGenesisList(t *testing.T) { assert.Equal(genDoc.AppOptions.pluginOptions[1].Value, "value2") } -func TestGetGenesisOptions(t *testing.T) { +func TestGetOptions(t *testing.T) { assert, require := assert.New(t), require.New(t) - opts, err := GetGenesisOptions(genesisFilepath) + opts, err := GetOptions(genesisFilepath) require.Nil(err, "loading genesis file %+v", err) require.Equal(4, len(opts)) diff --git a/server/commands/start.go b/server/commands/start.go index a7f41a329b..5580ce3036 100644 --- a/server/commands/start.go +++ b/server/commands/start.go @@ -120,7 +120,7 @@ func start(rootDir string, basecoinApp *app.BaseApp) error { // If genesis file exists, set key-value options genesisFile := path.Join(rootDir, "genesis.json") if _, err := os.Stat(genesisFile); err == nil { - err = genesis.LoadGenesis(basecoinApp, genesisFile) + err = genesis.Load(basecoinApp, genesisFile) if err != nil { return errors.Errorf("Error in LoadGenesis: %v\n", err) } From acdc083821566e76e78ed2da9e9025d23c69bbc7 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 18 Oct 2017 09:59:05 +0200 Subject: [PATCH 15/15] Clean up genesis, add godoc to genesis and app --- app/app_test.go | 16 ++--- app/base.go | 16 ++--- app/doc.go | 19 ++++++ benchmarks/app_test.go | 4 +- .../counter/plugins/counter/counter_test.go | 4 +- genesis/doc.go | 61 +++++++++++++++++++ genesis/parse.go | 7 ++- 7 files changed, 105 insertions(+), 22 deletions(-) create mode 100644 app/doc.go create mode 100644 genesis/doc.go diff --git a/app/app_test.go b/app/app_test.go index 5d6f84a3c0..c5e4880229 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -100,7 +100,7 @@ func (at *appTest) feeTx(coins coin.Coins, toll coin.Coin, sequence uint32) sdk. // set the account on the app through InitState func (at *appTest) initAccount(acct *coin.AccountWithKey) { - _, err := at.app.InitState("coin", "account", acct.MakeOption()) + err := at.app.InitState("coin", "account", acct.MakeOption()) require.Nil(at.t, err, "%+v", err) } @@ -117,7 +117,7 @@ func (at *appTest) reset() { require.Nil(at.t, err, "%+v", err) at.app = NewBaseApp(store, DefaultHandler("mycoin"), nil) - _, err = at.app.InitState("base", "chain_id", at.chainID) + err = at.app.InitState("base", "chain_id", at.chainID) require.Nil(at.t, err, "%+v", err) at.initAccount(at.acctIn) @@ -174,14 +174,14 @@ func TestInitState(t *testing.T) { //testing ChainID chainID := "testChain" - _, err = app.InitState("base", "chain_id", chainID) + err = app.InitState("base", "chain_id", chainID) require.Nil(err, "%+v", err) assert.EqualValues(app.GetChainID(), chainID) // make a nice account... bal := coin.Coins{{"atom", 77}, {"eth", 12}} acct := coin.NewAccountWithKey(bal) - _, err = app.InitState("coin", "account", acct.MakeOption()) + err = app.InitState("coin", "account", acct.MakeOption()) require.Nil(err, "%+v", err) // make sure it is set correctly, with some balance @@ -209,7 +209,7 @@ func TestInitState(t *testing.T) { } ] }` - _, err = app.InitState("coin", "account", unsortAcc) + err = app.InitState("coin", "account", unsortAcc) require.Nil(err, "%+v", err) coins, err = getAddr(unsortAddr, app.Append()) @@ -217,13 +217,13 @@ func TestInitState(t *testing.T) { assert.True(coins.IsValid()) assert.Equal(unsortCoins, coins) - _, err = app.InitState("base", "dslfkgjdas", "") + err = app.InitState("base", "dslfkgjdas", "") require.NotNil(err) - _, err = app.InitState("", "dslfkgjdas", "") + err = app.InitState("", "dslfkgjdas", "") require.NotNil(err) - _, err = app.InitState("dslfkgjdas", "szfdjzs", "") + err = app.InitState("dslfkgjdas", "szfdjzs", "") require.NotNil(err) } diff --git a/app/base.go b/app/base.go index 8d7e73a1ea..2ba14fd646 100644 --- a/app/base.go +++ b/app/base.go @@ -92,22 +92,24 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) { // to be used by InitChain later // // TODO: rethink this a bit more.... -func (app *BaseApp) InitState(module, key, value string) (string, error) { +func (app *BaseApp) InitState(module, key, value string) error { state := app.Append() + logger := app.Logger().With("module", module, "key", key) if module == sdk.ModuleNameBase { if key == sdk.ChainKey { app.info.SetChainID(state, value) - return "Success", nil + return nil } - return "", fmt.Errorf("unknown base option: %s", key) + logger.Error("Invalid genesis option") + return fmt.Errorf("Unknown base option: %s", key) } - log, err := app.handler.InitState(app.Logger(), state, module, key, value) + log, err := app.handler.InitState(logger, state, module, key, value) if err != nil { - app.Logger().Error("Genesis App Options", "err", err) + logger.Error("Invalid genesis option", "err", err) } else { - app.Logger().Info(log) + logger.Info(log) } - return log, err + return err } diff --git a/app/doc.go b/app/doc.go new file mode 100644 index 0000000000..d880368658 --- /dev/null +++ b/app/doc.go @@ -0,0 +1,19 @@ +/* +Package app contains data structures that provide basic +data storage functionality and act as a bridge between the abci +interface and the internal sdk representations. + +StoreApp handles creating a datastore or loading an existing one +from disk, provides helpers to use in the transaction workflow +(check/deliver/commit), and provides bindings to the ABCI interface +for functionality such as handshaking with tendermint on restart, +querying the data store, and handling begin/end block and commit messages. +It does not handle CheckTx or DeliverTx, or have any logic for modifying +the state, and is quite generic if you don't wish to use the standard Handlers. + +BaseApp embeds StoreApp and extends it for the standard sdk usecase, where +we dispatch all CheckTx/DeliverTx messages to a handler (which may contain +decorators and a router to multiple modules), and supports a Ticker which +is called every BeginBlock. +*/ +package app diff --git a/benchmarks/app_test.go b/benchmarks/app_test.go index 5d72736aa3..dc9e544743 100644 --- a/benchmarks/app_test.go +++ b/benchmarks/app_test.go @@ -65,7 +65,7 @@ func NewBenchApp(h sdk.Handler, chainID string, n int, } app := sdkapp.NewBaseApp(store, h, nil) - _, err = app.InitState("base", "chain_id", chainID) + err = app.InitState("base", "chain_id", chainID) if err != nil { panic("cannot set chain") } @@ -75,7 +75,7 @@ func NewBenchApp(h sdk.Handler, chainID string, n int, accts := make([]*coin.AccountWithKey, n) for i := 0; i < n; i++ { accts[i] = coin.NewAccountWithKey(money) - _, err = app.InitState("coin", "account", accts[i].MakeOption()) + err = app.InitState("coin", "account", accts[i].MakeOption()) if err != nil { panic("can't set account") } diff --git a/examples/counter/plugins/counter/counter_test.go b/examples/counter/plugins/counter/counter_test.go index 07861dc29b..5c1ef254a2 100644 --- a/examples/counter/plugins/counter/counter_test.go +++ b/examples/counter/plugins/counter/counter_test.go @@ -31,13 +31,13 @@ func TestCounterPlugin(t *testing.T) { store, err := app.MockStoreApp("counter", logger) require.Nil(err, "%+v", err) bcApp := app.NewBaseApp(store, h, nil) - _, err = bcApp.InitState("base", "chain_id", chainID) + err = bcApp.InitState("base", "chain_id", chainID) require.Nil(err, "%+v", err) // Account initialization bal := coin.Coins{{"", 1000}, {"gold", 1000}} acct := coin.NewAccountWithKey(bal) - _, err = bcApp.InitState("coin", "account", acct.MakeOption()) + err = bcApp.InitState("coin", "account", acct.MakeOption()) require.Nil(err, "%+v", err) // Deliver a CounterTx diff --git a/genesis/doc.go b/genesis/doc.go new file mode 100644 index 0000000000..40636fc078 --- /dev/null +++ b/genesis/doc.go @@ -0,0 +1,61 @@ +/* +Package genesis provides some utility functions for parsing +a standard genesis file to initialize your abci application. + +We wish to support using one genesis file to initialize both +tendermint and the application, so this file format is designed +to be embedable in the tendermint genesis.json file. We reuse +the same chain_id field for tendermint, ignore the other fields, +and add a special app_options field that contains information just +for the abci app (and ignored by tendermint). + +The use of this file format for your application is not required by +the sdk and is only used by default in the start command, if you wish +to write your own start command, you can use any other method to +store and parse options for your abci application. The important part is +that the same data is available on every node. + +Example file format: + + { + "chain_id": "foo_bar_chain", + "app_options": { + "accounts": [{ + "address": "C471FB670E44D219EE6DF2FC284BE38793ACBCE1", + "pub_key": { + "type": "ed25519", + "data": "6880DB93598E283A67C4D88FC67A8858AA2DE70F713FE94A5109E29C137100C2" + }, + "coins": [ + { + "denom": "ETH", + "amount": 654321 + } + ] + }], + "plugin_options": [ + "plugin1/key1", "value1", + "profile/set", {"name": "john", age: 37} + ] + } + } + +Note that there are two subfields under app_options. The first one "accounts" +is a special case for the coin module, which is assumed to be used by most +applications. It is simply a list of accounts with an identifier and their +initial balance. The account must be identified by EITHER an address +(20 bytes in hex) or a pubkey (in the go-crypto json format), not both as in +this example. "coins" defines the initial balance of the account. + +Configuration options for every other module should be placed under +"plugin_options" as key value pairs (there must be an even number of items). +The first value must be "/" to define the option to be set. +The second value is parsed as raw json and is the value to pass to the +application. This may be a string, an array, a map or any other valid json +structure that the module can parse. + +Note that we don't use a map for plugin_options, as we will often wish +to have many values for the same key, to run this setup many times, +just as we support setting many accounts. +*/ +package genesis diff --git a/genesis/parse.go b/genesis/parse.go index 6a183464c5..67bc32ef1d 100644 --- a/genesis/parse.go +++ b/genesis/parse.go @@ -19,9 +19,10 @@ type Option struct { } // InitStater is anything that can handle app options -// from genesis file. +// from genesis file. Setting the merkle store, config options, +// or anything else type InitStater interface { - InitState(module, key, value string) (string, error) + InitState(module, key, value string) error } // Load parses the genesis file and sets the initial @@ -35,7 +36,7 @@ func Load(app InitStater, filePath string) error { // execute all the genesis init options // abort on any error for _, opt := range opts { - _, err = app.InitState(opt.Module, opt.Key, opt.Value) + err = app.InitState(opt.Module, opt.Key, opt.Value) if err != nil { return err }