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(),