diff --git a/app/store.go b/app/store.go index 4c3bc8adac0a..adf51280661d 100644 --- a/app/store.go +++ b/app/store.go @@ -112,7 +112,7 @@ func (s *Store) Info() abci.ResponseInfo { "height", s.height, "hash", fmt.Sprintf("%X", s.hash)) return abci.ResponseInfo{ - Data: cmn.Fmt("size:%v", s.State.Committed().Size()), + Data: cmn.Fmt("size:%v", s.State.Size()), LastBlockHeight: s.height - 1, LastBlockAppHash: s.hash, } @@ -144,7 +144,7 @@ func (s *Store) Commit() abci.Result { return abci.NewError(abci.CodeType_InternalError, "AppHash is incorrect") } - if s.State.Committed().Size() == 0 { + if s.State.Size() == 0 { return abci.NewResultOK(nil, "Empty hash for empty tree") } return abci.NewResultOK(s.hash, "") diff --git a/state/bonsai.go b/state/bonsai.go index 2a98732985a1..9800ba54fb88 100644 --- a/state/bonsai.go +++ b/state/bonsai.go @@ -11,8 +11,8 @@ type nonce int64 // Bonsai is a deformed tree forced to fit in a small pot type Bonsai struct { - id nonce - merkle.Tree + id nonce + Tree merkle.Tree } var _ SimpleDB = &Bonsai{} @@ -31,6 +31,11 @@ func (b *Bonsai) Get(key []byte) []byte { return value } +// Get matches the signature of KVStore +func (b *Bonsai) Has(key []byte) bool { + return b.Tree.Has(key) +} + // Set matches the signature of KVStore func (b *Bonsai) Set(key, value []byte) { b.Tree.Set(key, value) @@ -41,6 +46,10 @@ func (b *Bonsai) Remove(key []byte) (value []byte) { return } +func (b *Bonsai) Proof(key []byte) (value []byte, proof []byte, exists bool) { + return b.Tree.Proof(key) +} + func (b *Bonsai) List(start, end []byte, limit int) []Model { res := []Model{} stopAtCount := func(key []byte, value []byte) (stop bool) { diff --git a/state/kvcache.go b/state/kvcache.go index 1141d20e3e11..25ec29f9e672 100644 --- a/state/kvcache.go +++ b/state/kvcache.go @@ -117,7 +117,12 @@ func (c *MemKVCache) Commit(sub SimpleDB) error { if !ok { return ErrNotASubTransaction() } - // TODO: see if it points to us + + // see if it points to us + ref, ok := cache.store.(*MemKVCache) + if !ok || ref != c { + return ErrNotASubTransaction() + } // apply the cached data to us cache.applyCache() @@ -126,7 +131,8 @@ func (c *MemKVCache) Commit(sub SimpleDB) error { // applyCache will apply all the cache methods to the underlying store func (c *MemKVCache) applyCache() { - for k, v := range c.cache.m { + for _, k := range c.cache.keysInRange(nil, nil) { + v := c.cache.m[k] if v == nil { c.store.Remove([]byte(k)) } else { diff --git a/state/kvstore.go b/state/kvstore.go index 08dd85f21736..9af9b57534e7 100644 --- a/state/kvstore.go +++ b/state/kvstore.go @@ -149,7 +149,12 @@ func (m *MemKVStore) Commit(sub SimpleDB) error { if !ok { return ErrNotASubTransaction() } - // TODO: see if it points to us + + // see if it points to us + ref, ok := cache.store.(*MemKVStore) + if !ok || ref != m { + return ErrNotASubTransaction() + } // apply the cached data to us cache.applyCache() diff --git a/state/merkle.go b/state/merkle.go index f383fd0d5a23..666cac0bc453 100644 --- a/state/merkle.go +++ b/state/merkle.go @@ -24,6 +24,10 @@ func NewState(tree merkle.Tree, persistent bool) State { } } +func (s State) Size() int { + return s.committed.Tree.Size() +} + func (s State) Committed() *Bonsai { return s.committed } diff --git a/state/merkle_test.go b/state/merkle_test.go new file mode 100644 index 000000000000..d6c9f577b955 --- /dev/null +++ b/state/merkle_test.go @@ -0,0 +1,100 @@ +package state + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/tendermint/merkleeyes/iavl" +) + +type keyVal struct { + key string + val string +} + +func (kv keyVal) getKV() ([]byte, []byte) { + return []byte(kv.key), []byte(kv.val) +} + +type round []keyVal + +// make sure the commits are deterministic +func TestStateCommitHash(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + cases := [...]struct { + rounds []round + }{ + // simple, two rounds, no overlap + 0: { + []round{ + []keyVal{{"abc", "123"}, {"def", "456"}}, + []keyVal{{"more", "news"}, {"good", "news"}}, + }, + }, + // more complex, order should change if applyCache is not deterministic + 1: { + []round{ + []keyVal{{"abc", "123"}, {"def", "456"}, {"foo", "789"}, {"dings", "646"}}, + []keyVal{{"hf", "123"}, {"giug", "456"}, {"kgiuvgi", "789"}, {"kjguvgk", "646"}}, + []keyVal{{"one", "more"}, {"two", "things"}, {"uh", "oh"}, {"a", "2"}}, + }, + }, + // make sure ordering works with overwriting as well + 2: { + []round{ + []keyVal{{"abc", "123"}, {"def", "456"}, {"foo", "789"}, {"dings", "646"}}, + []keyVal{{"hf", "123"}, {"giug", "456"}, {"kgiuvgi", "789"}, {"kjguvgk", "646"}}, + []keyVal{{"abc", "qqq"}, {"def", "www"}, {"foo", "ee"}, {"dings", "ff"}}, + []keyVal{{"one", "more"}, {"uh", "oh"}, {"a", "2"}}, + []keyVal{{"hf", "dd"}, {"giug", "gg"}, {"kgiuvgi", "jj"}, {"kjguvgk", "uu"}}, + }, + }, + } + + for i, tc := range cases { + // let's run all rounds... they must each be different, + // and they must have the same results each run + var hashes [][]byte + + // try each 5 times for deterministic check + for j := 0; j < 5; j++ { + result := make([][]byte, len(tc.rounds)) + + // make the store... + tree := iavl.NewIAVLTree(0, nil) + store := NewState(tree, false) + + for n, r := range tc.rounds { + // start the cache + deliver := store.Append() + for _, kv := range r { + // add the value to cache + k, v := kv.getKV() + deliver.Set(k, v) + } + // commit and add hash to result + hash, err := store.Commit() + require.Nil(err, "tc:%d / rnd:%d - %+v", i, n, err) + result[n] = hash + } + + // make sure result is all unique + for n := 0; n < len(result)-1; n++ { + assert.NotEqual(result[n], result[n+1], "tc:%d / rnd:%d", i, n) + } + + // if hashes != nil, make sure same as last trial + if hashes != nil { + for n := 0; n < len(result); n++ { + assert.Equal(hashes[n], result[n], "tc:%d / rnd:%d", i, n) + } + } + // store to check against next trial + hashes = result + } + } + +}