diff --git a/store/prefixstore.go b/store/prefixstore.go index 835a210385..55af1084e3 100644 --- a/store/prefixstore.go +++ b/store/prefixstore.go @@ -1,6 +1,7 @@ package store import ( + "bytes" "io" sdk "github.com/cosmos/cosmos-sdk/types" @@ -8,11 +9,27 @@ import ( var _ KVStore = prefixStore{} +// prefixStore is similar with tendermint/tendermint/libs/db/prefix_db +// both gives access only to the limited subset of the store +// for convinience or safety + type prefixStore struct { parent KVStore prefix []byte } +func cloneAppend(bz []byte, tail []byte) (res []byte) { + res = make([]byte, len(bz)+len(tail)) + copy(res, bz) + copy(res[len(bz):], tail) + return +} + +func (s prefixStore) key(key []byte) (res []byte) { + res = cloneAppend(s.prefix, key) + return +} + // Implements Store func (s prefixStore) GetStoreType() StoreType { return s.parent.GetStoreType() @@ -30,22 +47,23 @@ func (s prefixStore) CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap // Implements KVStore func (s prefixStore) Get(key []byte) []byte { - return s.parent.Get(append(s.prefix, key...)) + res := s.parent.Get(s.key(key)) + return res } // Implements KVStore func (s prefixStore) Has(key []byte) bool { - return s.parent.Has(append(s.prefix, key...)) + return s.parent.Has(s.key(key)) } // Implements KVStore func (s prefixStore) Set(key, value []byte) { - s.parent.Set(append(s.prefix, key...), value) + s.parent.Set(s.key(key), value) } // Implements KVStore func (s prefixStore) Delete(key []byte) { - s.parent.Delete(append(s.prefix, key...)) + s.parent.Delete(s.key(key)) } // Implements KVStore @@ -59,68 +77,147 @@ func (s prefixStore) Gas(meter GasMeter, config GasConfig) KVStore { } // Implements KVStore +// Check https://github.com/tendermint/tendermint/blob/master/libs/db/prefix_db.go#L106 func (s prefixStore) Iterator(start, end []byte) Iterator { + newstart := cloneAppend(s.prefix, start) + + var newend []byte if end == nil { - end = sdk.PrefixEndBytes(s.prefix) + newend = cpIncr(s.prefix) } else { - end = append(s.prefix, end...) - } - return prefixIterator{ - prefix: s.prefix, - iter: s.parent.Iterator(append(s.prefix, start...), end), + newend = cloneAppend(s.prefix, end) } + + iter := s.parent.Iterator(newstart, newend) + + return newPrefixIterator(s.prefix, start, end, iter) } // Implements KVStore +// Check https://github.com/tendermint/tendermint/blob/master/libs/db/prefix_db.go#L129 func (s prefixStore) ReverseIterator(start, end []byte) Iterator { - if end == nil { - end = sdk.PrefixEndBytes(s.prefix) + var newstart []byte + if start == nil { + newstart = cpIncr(s.prefix) } else { - end = append(s.prefix, end...) + newstart = cloneAppend(s.prefix, start) } - return prefixIterator{ - prefix: s.prefix, - iter: s.parent.ReverseIterator(start, end), + + var newend []byte + if end == nil { + newend = cpDecr(s.prefix) + } else { + newend = cloneAppend(s.prefix, end) } + + iter := s.parent.ReverseIterator(newstart, newend) + if start == nil { + skipOne(iter, cpIncr(s.prefix)) + } + + return newPrefixIterator(s.prefix, start, end, iter) } +var _ sdk.Iterator = (*prefixIterator)(nil) + type prefixIterator struct { - prefix []byte + prefix []byte + start, end []byte + iter Iterator + valid bool +} - iter Iterator +func newPrefixIterator(prefix, start, end []byte, parent Iterator) *prefixIterator { + return &prefixIterator{ + prefix: prefix, + start: start, + end: end, + iter: parent, + valid: parent.Valid() && bytes.HasPrefix(parent.Key(), prefix), + } } // Implements Iterator -func (iter prefixIterator) Domain() (start []byte, end []byte) { - start, end = iter.iter.Domain() - start = start[len(iter.prefix):] - end = end[len(iter.prefix):] - return +func (iter *prefixIterator) Domain() ([]byte, []byte) { + return iter.start, iter.end } // Implements Iterator -func (iter prefixIterator) Valid() bool { - return iter.iter.Valid() +func (iter *prefixIterator) Valid() bool { + return iter.valid && iter.iter.Valid() } // Implements Iterator -func (iter prefixIterator) Next() { +func (iter *prefixIterator) Next() { + if !iter.valid { + panic("prefixIterator invalid, cannot call Next()") + } iter.iter.Next() + if !iter.iter.Valid() || !bytes.HasPrefix(iter.iter.Key(), iter.prefix) { + iter.valid = false + } } // Implements Iterator -func (iter prefixIterator) Key() (key []byte) { +func (iter *prefixIterator) Key() (key []byte) { + if !iter.valid { + panic("prefixIterator invalid, cannot call Key()") + } key = iter.iter.Key() - key = key[len(iter.prefix):] + key = stripPrefix(key, iter.prefix) return } // Implements Iterator -func (iter prefixIterator) Value() []byte { +func (iter *prefixIterator) Value() []byte { + if !iter.valid { + panic("prefixIterator invalid, cannot call Value()") + } return iter.iter.Value() } // Implements Iterator -func (iter prefixIterator) Close() { +func (iter *prefixIterator) Close() { iter.iter.Close() } + +// copied from github.com/tendermint/tendermint/libs/db/prefix_db.go +func stripPrefix(key []byte, prefix []byte) []byte { + if len(key) < len(prefix) || !bytes.Equal(key[:len(prefix)], prefix) { + panic("should not happen") + } + return key[len(prefix):] +} + +// wrapping sdk.PrefixEndBytes +func cpIncr(bz []byte) []byte { + return sdk.PrefixEndBytes(bz) +} + +// copied from github.com/tendermint/tendermint/libs/db/util.go +func cpDecr(bz []byte) (ret []byte) { + if len(bz) == 0 { + panic("cpDecr expects non-zero bz length") + } + ret = make([]byte, len(bz)) + copy(ret, bz) + for i := len(bz) - 1; i >= 0; i-- { + if ret[i] > byte(0x00) { + ret[i]-- + return + } + ret[i] = byte(0xFF) + if i == 0 { + return nil + } + } + return nil +} + +func skipOne(iter Iterator, skipKey []byte) { + if iter.Valid() { + if bytes.Equal(iter.Key(), skipKey) { + iter.Next() + } + } +} diff --git a/store/prefixstore_test.go b/store/prefixstore_test.go index 49bc68037a..499f30d500 100644 --- a/store/prefixstore_test.go +++ b/store/prefixstore_test.go @@ -105,7 +105,286 @@ func TestPrefixStoreIterate(t *testing.T) { pIter.Next() } - require.Equal(t, bIter.Valid(), pIter.Valid()) bIter.Close() pIter.Close() } + +func TestPrefixStoreIteratorEdgeCase(t *testing.T) { + db := dbm.NewMemDB() + baseStore := dbStoreAdapter{db} + + // overflow in cpIncr + prefix := []byte{0xAA, 0xFF, 0xFF} + prefixStore := baseStore.Prefix(prefix) + + // ascending order + baseStore.Set([]byte{0xAA, 0xFF, 0xFE}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFE, 0x00}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFF}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFF, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00, 0x00}, []byte{}) + + iter := prefixStore.Iterator(nil, nil) + + checkDomain(t, iter, nil, nil) + checkItem(t, iter, []byte{}, bz("")) + checkNext(t, iter, true) + checkItem(t, iter, []byte{0x00}, bz("")) + checkNext(t, iter, false) + + checkInvalid(t, iter) + + iter.Close() +} + +func TestPrefixStoreReverseIteratorEdgeCase(t *testing.T) { + db := dbm.NewMemDB() + baseStore := dbStoreAdapter{db} + + // overflow in cpIncr + prefix := []byte{0xAA, 0xFF, 0xFF} + prefixStore := baseStore.Prefix(prefix) + + // descending order + baseStore.Set([]byte{0xAB, 0x00, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFF, 0x00}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFF}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFE, 0x00}, []byte{}) + baseStore.Set([]byte{0xAA, 0xFF, 0xFE}, []byte{}) + + iter := prefixStore.ReverseIterator(nil, nil) + + checkDomain(t, iter, nil, nil) + checkItem(t, iter, []byte{0x00}, bz("")) + checkNext(t, iter, true) + checkItem(t, iter, []byte{}, bz("")) + checkNext(t, iter, false) + + checkInvalid(t, iter) + + iter.Close() + + db = dbm.NewMemDB() + baseStore = dbStoreAdapter{db} + + // underflow in cpDecr + prefix = []byte{0xAA, 0x00, 0x00} + prefixStore = baseStore.Prefix(prefix) + + baseStore.Set([]byte{0xAB, 0x00, 0x01, 0x00, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00, 0x01, 0x00}, []byte{}) + baseStore.Set([]byte{0xAB, 0x00, 0x01}, []byte{}) + baseStore.Set([]byte{0xAA, 0x00, 0x00, 0x00}, []byte{}) + baseStore.Set([]byte{0xAA, 0x00, 0x00}, []byte{}) + baseStore.Set([]byte{0xA9, 0xFF, 0xFF, 0x00}, []byte{}) + baseStore.Set([]byte{0xA9, 0xFF, 0xFF}, []byte{}) + + iter = prefixStore.ReverseIterator(nil, nil) + + checkDomain(t, iter, nil, nil) + checkItem(t, iter, []byte{0x00}, bz("")) + checkNext(t, iter, true) + checkItem(t, iter, []byte{}, bz("")) + checkNext(t, iter, false) + + checkInvalid(t, iter) + + iter.Close() +} + +// Tests below are ported from https://github.com/tendermint/tendermint/blob/master/libs/db/prefix_db_test.go + +func mockStoreWithStuff() sdk.KVStore { + db := dbm.NewMemDB() + store := dbStoreAdapter{db} + // Under "key" prefix + store.Set(bz("key"), bz("value")) + store.Set(bz("key1"), bz("value1")) + store.Set(bz("key2"), bz("value2")) + store.Set(bz("key3"), bz("value3")) + store.Set(bz("something"), bz("else")) + store.Set(bz(""), bz("")) + store.Set(bz("k"), bz("val")) + store.Set(bz("ke"), bz("valu")) + store.Set(bz("kee"), bz("valuu")) + return store +} + +func checkValue(t *testing.T, store sdk.KVStore, key []byte, expected []byte) { + bz := store.Get(key) + require.Equal(t, expected, bz) +} + +func checkValid(t *testing.T, itr sdk.Iterator, expected bool) { + valid := itr.Valid() + require.Equal(t, expected, valid) +} + +func checkNext(t *testing.T, itr sdk.Iterator, expected bool) { + itr.Next() + valid := itr.Valid() + require.Equal(t, expected, valid) +} + +func checkDomain(t *testing.T, itr sdk.Iterator, start, end []byte) { + ds, de := itr.Domain() + require.Equal(t, start, ds) + require.Equal(t, end, de) +} + +func checkItem(t *testing.T, itr sdk.Iterator, key, value []byte) { + require.Exactly(t, key, itr.Key()) + require.Exactly(t, value, itr.Value()) +} + +func checkInvalid(t *testing.T, itr sdk.Iterator) { + checkValid(t, itr, false) + checkKeyPanics(t, itr) + checkValuePanics(t, itr) + checkNextPanics(t, itr) +} + +func checkKeyPanics(t *testing.T, itr sdk.Iterator) { + require.Panics(t, func() { itr.Key() }) +} + +func checkValuePanics(t *testing.T, itr sdk.Iterator) { + require.Panics(t, func() { itr.Value() }) +} + +func checkNextPanics(t *testing.T, itr sdk.Iterator) { + require.Panics(t, func() { itr.Next() }) +} + +func TestPrefixDBSimple(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + checkValue(t, pstore, bz("key"), nil) + checkValue(t, pstore, bz(""), bz("value")) + checkValue(t, pstore, bz("key1"), nil) + checkValue(t, pstore, bz("1"), bz("value1")) + checkValue(t, pstore, bz("key2"), nil) + checkValue(t, pstore, bz("2"), bz("value2")) + checkValue(t, pstore, bz("key3"), nil) + checkValue(t, pstore, bz("3"), bz("value3")) + checkValue(t, pstore, bz("something"), nil) + checkValue(t, pstore, bz("k"), nil) + checkValue(t, pstore, bz("ke"), nil) + checkValue(t, pstore, bz("kee"), nil) +} + +func TestPrefixDBIterator1(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.Iterator(nil, nil) + checkDomain(t, itr, nil, nil) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, true) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) + checkNext(t, itr, true) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBIterator2(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.Iterator(nil, bz("")) + checkDomain(t, itr, nil, bz("")) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBIterator3(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.Iterator(bz(""), nil) + checkDomain(t, itr, bz(""), nil) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, true) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) + checkNext(t, itr, true) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBIterator4(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.Iterator(bz(""), bz("")) + checkDomain(t, itr, bz(""), bz("")) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator1(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.ReverseIterator(nil, nil) + checkDomain(t, itr, nil, nil) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) + checkNext(t, itr, true) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, true) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator2(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.ReverseIterator(nil, bz("")) + checkDomain(t, itr, nil, bz("")) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) + checkNext(t, itr, true) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator3(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.ReverseIterator(bz(""), nil) + checkDomain(t, itr, bz(""), nil) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator4(t *testing.T) { + store := mockStoreWithStuff() + pstore := store.Prefix(bz("key")) + + itr := pstore.ReverseIterator(bz(""), bz("")) + checkInvalid(t, itr) + itr.Close() +}