diff --git a/state/kvstore.go b/state/kvstore.go index b07d57109..7d0534948 100644 --- a/state/kvstore.go +++ b/state/kvstore.go @@ -4,6 +4,7 @@ import ( "container/list" "fmt" + "github.com/tendermint/go-wire/data" . "github.com/tendermint/tmlibs/common" ) @@ -14,6 +15,37 @@ type KVStore interface { //---------------------------------------- +type Model struct { + Key data.Bytes + Value data.Bytes +} + +// What I wished to have... +type SimpleDB interface { + KVStore + + Has(key []byte) (has bool) + Remove(key []byte) (value []byte) // returns old value if there was one + + List(start, end []byte, limit int) []Model + First(start, end []byte) Model + Last(start, end []byte) Model + + // Checkpoint returns the same state, but where writes + // are buffered and don't affect the parent + Checkpoint() SimpleDB + + // Commit will take all changes from the checkpoint and write + // them to the parent. + // Returns an error if this is not a child of this one + Commit(SimpleDB) error + + // Discard will remove reference to this + Discard() +} + +//---------------------------------------- + type MemKVStore struct { m map[string][]byte } diff --git a/state/merkle/state.go b/state/merkle/state.go index 423a9c6ad..17c4ee25e 100644 --- a/state/merkle/state.go +++ b/state/merkle/state.go @@ -1,6 +1,10 @@ package merkle import ( + "errors" + "math/rand" + + "github.com/tendermint/basecoin/state" "github.com/tendermint/merkleeyes/iavl" "github.com/tendermint/tmlibs/merkle" ) @@ -23,16 +27,16 @@ func NewState(tree merkle.Tree, persistent bool) State { } } -func (s State) Committed() Bonsai { - return Bonsai{s.committed} +func (s State) Committed() *Bonsai { + return NewBonsai(s.committed) } -func (s State) Append() Bonsai { - return Bonsai{s.deliverTx} +func (s State) Append() *Bonsai { + return NewBonsai(s.deliverTx) } -func (s State) Check() Bonsai { - return Bonsai{s.checkTx} +func (s State) Check() *Bonsai { + return NewBonsai(s.checkTx) } // Hash updates the tree @@ -66,18 +70,92 @@ func (s *State) Commit() []byte { return hash } +// store nonce as it's own type so no one can even try to fake it +type nonce int64 + // Bonsai is a deformed tree forced to fit in a small pot type Bonsai struct { + id nonce merkle.Tree } +var _ state.SimpleDB = &Bonsai{} + +func NewBonsai(tree merkle.Tree) *Bonsai { + return &Bonsai{ + id: nonce(rand.Int63()), + Tree: tree, + } +} + // Get matches the signature of KVStore -func (b Bonsai) Get(key []byte) []byte { +func (b *Bonsai) Get(key []byte) []byte { _, value, _ := b.Tree.Get(key) return value } // Set matches the signature of KVStore -func (b Bonsai) Set(key, value []byte) { +func (b *Bonsai) Set(key, value []byte) { b.Tree.Set(key, value) } + +func (b *Bonsai) Remove(key []byte) (value []byte) { + value, _ = b.Tree.Remove(key) + return +} + +func (b *Bonsai) List(start, end []byte, limit int) []state.Model { + var res []state.Model + stopAtCount := func(key []byte, value []byte) (stop bool) { + m := state.Model{key, value} + res = append(res, m) + return len(res) >= limit + } + b.Tree.IterateRange(start, end, true, stopAtCount) + return res +} + +func (b *Bonsai) First(start, end []byte) state.Model { + var m state.Model + stopAtFirst := func(key []byte, value []byte) (stop bool) { + m = state.Model{key, value} + return true + } + b.Tree.IterateRange(start, end, true, stopAtFirst) + return m +} + +func (b *Bonsai) Last(start, end []byte) state.Model { + var m state.Model + stopAtFirst := func(key []byte, value []byte) (stop bool) { + m = state.Model{key, value} + return true + } + b.Tree.IterateRange(start, end, false, stopAtFirst) + return m +} + +func (b *Bonsai) Checkpoint() state.SimpleDB { + return &Bonsai{ + id: b.id, + Tree: b.Tree.Copy(), + } +} + +// Commit will take all changes from the checkpoint and write +// them to the parent. +// Returns an error if this is not a child of this one +func (b *Bonsai) Commit(sub state.SimpleDB) error { + bb, ok := sub.(*Bonsai) + if !ok || (b.id != bb.id) { + return errors.New("Not a sub-transaction") + } + b.Tree = bb.Tree + return nil +} + +// Discard will remove reference to this +func (b *Bonsai) Discard() { + b.id = 0 + b.Tree = nil +} diff --git a/state/merkle/store.go b/state/merkle/store.go index 1f513e989..7064af722 100644 --- a/state/merkle/store.go +++ b/state/merkle/store.go @@ -28,8 +28,8 @@ type Store struct { var stateKey = []byte("merkle:state") // Database key for merkle tree save value db values -// MerkleState contains the latest Merkle root hash and the number of times `Commit` has been called -type MerkleState struct { +// ChainState contains the latest Merkle root hash and the number of times `Commit` has been called +type ChainState struct { Hash []byte Height uint64 } @@ -74,29 +74,29 @@ func NewStore(dbName string, cacheSize int, logger log.Logger) *Store { db := dbm.NewDB(name, dbm.LevelDBBackendStr, dir) tree := iavl.NewIAVLTree(cacheSize, db) - var eyesState MerkleState + var chainState ChainState if empty { logger.Info("no existing db, creating new db") - eyesState = MerkleState{ + chainState = ChainState{ Hash: tree.Save(), Height: initialHeight, } - db.Set(stateKey, wire.BinaryBytes(eyesState)) + db.Set(stateKey, wire.BinaryBytes(chainState)) } else { logger.Info("loading existing db") eyesStateBytes := db.Get(stateKey) - err = wire.ReadBinaryBytes(eyesStateBytes, &eyesState) + err = wire.ReadBinaryBytes(eyesStateBytes, &chainState) if err != nil { logger.Error("error reading MerkleEyesState", "err", err) panic(err) } - tree.Load(eyesState.Hash) + tree.Load(chainState.Hash) } return &Store{ State: NewState(tree, true), - height: eyesState.Height, - hash: eyesState.Hash, + height: chainState.Height, + hash: chainState.Hash, persisted: true, logger: logger, } @@ -112,7 +112,9 @@ func NewStore(dbName string, cacheSize int, logger log.Logger) *Store { // 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)) + s.logger.Info("Info synced", + "height", s.height, + "hash", fmt.Sprintf("%X", s.hash)) return abci.ResponseInfo{ Data: cmn.Fmt("size:%v", s.State.Committed().Size()), LastBlockHeight: s.height - 1, @@ -124,9 +126,11 @@ func (s *Store) Info() abci.ResponseInfo { func (s *Store) Commit() abci.Result { s.hash = s.State.Hash() s.height++ - s.logger.Debug("Commit synced", "height", s.height, "hash", fmt.Sprintf("%X", s.hash)) + s.logger.Debug("Commit synced", + "height", s.height, + "hash", fmt.Sprintf("%X", s.hash)) - s.State.BatchSet(stateKey, wire.BinaryBytes(MerkleState{ + s.State.BatchSet(stateKey, wire.BinaryBytes(ChainState{ Hash: s.hash, Height: s.height, })) @@ -144,10 +148,6 @@ func (s *Store) Commit() abci.Result { // Query implements abci.Application func (s *Store) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { - if len(reqQuery.Data) == 0 { - return - } - tree := s.State.Committed() if reqQuery.Height != 0 { // TODO: support older commits @@ -159,6 +159,8 @@ func (s *Store) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) // set the query response height to current resQuery.Height = s.height + tree := s.State.Committed() + switch reqQuery.Path { case "/store", "/key": // Get by key key := reqQuery.Data // Data holds the key bytes @@ -170,24 +172,11 @@ func (s *Store) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) } resQuery.Value = value resQuery.Proof = proof - // TODO: return index too? } else { value := tree.Get(key) resQuery.Value = value } - case "/index": // Get by Index - index := wire.GetInt64(reqQuery.Data) - key, value := tree.GetByIndex(int(index)) - resQuery.Key = key - resQuery.Index = int64(index) - resQuery.Value = value - - case "/size": // Get size - size := tree.Size() - sizeBytes := wire.BinaryBytes(size) - resQuery.Value = sizeBytes - default: resQuery.Code = abci.CodeType_UnknownRequest resQuery.Log = cmn.Fmt("Unexpected Query path: %v", reqQuery.Path)