Merge PR #2248: Fix PrefixIterator
This commit is contained in:
commit
b921ed224c
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue