Merge pull request #206 from tendermint/feature/fixup-store
Make state.Store / state.KVCache deterministic
This commit is contained in:
commit
5d45c22690
|
@ -112,7 +112,7 @@ func (s *Store) Info() abci.ResponseInfo {
|
||||||
"height", s.height,
|
"height", s.height,
|
||||||
"hash", fmt.Sprintf("%X", s.hash))
|
"hash", fmt.Sprintf("%X", s.hash))
|
||||||
return abci.ResponseInfo{
|
return abci.ResponseInfo{
|
||||||
Data: cmn.Fmt("size:%v", s.State.Committed().Size()),
|
Data: cmn.Fmt("size:%v", s.State.Size()),
|
||||||
LastBlockHeight: s.height - 1,
|
LastBlockHeight: s.height - 1,
|
||||||
LastBlockAppHash: s.hash,
|
LastBlockAppHash: s.hash,
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ func (s *Store) Commit() abci.Result {
|
||||||
return abci.NewError(abci.CodeType_InternalError, "AppHash is incorrect")
|
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(nil, "Empty hash for empty tree")
|
||||||
}
|
}
|
||||||
return abci.NewResultOK(s.hash, "")
|
return abci.NewResultOK(s.hash, "")
|
||||||
|
|
|
@ -11,8 +11,8 @@ type nonce int64
|
||||||
|
|
||||||
// Bonsai is a deformed tree forced to fit in a small pot
|
// Bonsai is a deformed tree forced to fit in a small pot
|
||||||
type Bonsai struct {
|
type Bonsai struct {
|
||||||
id nonce
|
id nonce
|
||||||
merkle.Tree
|
Tree merkle.Tree
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ SimpleDB = &Bonsai{}
|
var _ SimpleDB = &Bonsai{}
|
||||||
|
@ -31,6 +31,11 @@ func (b *Bonsai) Get(key []byte) []byte {
|
||||||
return value
|
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
|
// 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)
|
b.Tree.Set(key, value)
|
||||||
|
@ -41,6 +46,10 @@ func (b *Bonsai) Remove(key []byte) (value []byte) {
|
||||||
return
|
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 {
|
func (b *Bonsai) List(start, end []byte, limit int) []Model {
|
||||||
res := []Model{}
|
res := []Model{}
|
||||||
stopAtCount := func(key []byte, value []byte) (stop bool) {
|
stopAtCount := func(key []byte, value []byte) (stop bool) {
|
||||||
|
|
|
@ -117,7 +117,12 @@ func (c *MemKVCache) Commit(sub SimpleDB) error {
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrNotASubTransaction()
|
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
|
// apply the cached data to us
|
||||||
cache.applyCache()
|
cache.applyCache()
|
||||||
|
@ -126,7 +131,8 @@ func (c *MemKVCache) Commit(sub SimpleDB) error {
|
||||||
|
|
||||||
// applyCache will apply all the cache methods to the underlying store
|
// applyCache will apply all the cache methods to the underlying store
|
||||||
func (c *MemKVCache) applyCache() {
|
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 {
|
if v == nil {
|
||||||
c.store.Remove([]byte(k))
|
c.store.Remove([]byte(k))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -149,7 +149,12 @@ func (m *MemKVStore) Commit(sub SimpleDB) error {
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrNotASubTransaction()
|
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
|
// apply the cached data to us
|
||||||
cache.applyCache()
|
cache.applyCache()
|
||||||
|
|
|
@ -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 {
|
func (s State) Committed() *Bonsai {
|
||||||
return s.committed
|
return s.committed
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue