From 2b84b2cda2e9ba051736acf674c4b41590ff789b Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Tue, 31 Oct 2017 15:45:57 -0500 Subject: [PATCH] Remove most of state/* --- TODO | 1 + state/bonsai.go | 142 ----------------- state/chainstate.go | 30 ---- state/errors.go | 20 --- state/kvcache.go | 149 ------------------ state/kvcache_test.go | 125 --------------- state/kvstore.go | 134 ---------------- state/merkle.go | 94 ------------ state/merkle_test.go | 101 ------------- state/set.go | 158 ------------------- state/set_test.go | 77 ---------- state/span.go | 126 --------------- state/span_test.go | 122 --------------- state/store_test.go | 148 ------------------ store/multistore.go | 269 +++++++++++++++++++++++++++++++++ {state => store}/queue.go | 2 +- {state => store}/queue_test.go | 2 +- store/types.go | 103 +++++++++++++ 18 files changed, 375 insertions(+), 1428 deletions(-) create mode 100644 TODO delete mode 100644 state/bonsai.go delete mode 100644 state/chainstate.go delete mode 100644 state/errors.go delete mode 100644 state/kvcache.go delete mode 100644 state/kvcache_test.go delete mode 100644 state/kvstore.go delete mode 100644 state/merkle.go delete mode 100644 state/merkle_test.go delete mode 100644 state/set.go delete mode 100644 state/set_test.go delete mode 100644 state/span.go delete mode 100644 state/span_test.go delete mode 100644 state/store_test.go create mode 100644 store/multistore.go rename {state => store}/queue.go (99%) rename {state => store}/queue_test.go (98%) create mode 100644 store/types.go diff --git a/TODO b/TODO new file mode 100644 index 0000000000..3bf2069b57 --- /dev/null +++ b/TODO @@ -0,0 +1 @@ +Make Queue work with new types.go diff --git a/state/bonsai.go b/state/bonsai.go deleted file mode 100644 index 0a67840eaf..0000000000 --- a/state/bonsai.go +++ /dev/null @@ -1,142 +0,0 @@ -package state - -import ( - "math/rand" - - sdk "github.com/cosmos/cosmos-sdk" - "github.com/tendermint/iavl" -) - -// 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 - Tree *iavl.VersionedTree -} - -func (b *Bonsai) String() string { - return "Bonsai{" + b.Tree.String() + "}" -} - -var _ sdk.SimpleDB = &Bonsai{} - -// NewBonsai wraps a merkle tree and tags it to track children -func NewBonsai(tree *iavl.VersionedTree) *Bonsai { - return &Bonsai{ - id: nonce(rand.Int63()), - Tree: tree, - } -} - -// Get matches the signature of KVStore -func (b *Bonsai) Get(key []byte) []byte { - _, value := b.Tree.Get(key) - 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) -} - -func (b *Bonsai) Remove(key []byte) (value []byte) { - value, _ = b.Tree.Remove(key) - return -} - -func (b *Bonsai) GetWithProof(key []byte) ([]byte, iavl.KeyProof, error) { - return b.Tree.GetWithProof(key) -} - -func (b *Bonsai) GetVersionedWithProof(key []byte, version uint64) ([]byte, iavl.KeyProof, error) { - return b.Tree.GetVersionedWithProof(key, version) -} - -func (b *Bonsai) List(start, end []byte, limit int) []sdk.Model { - res := []sdk.Model{} - stopAtCount := func(key []byte, value []byte) (stop bool) { - m := sdk.Model{key, value} - res = append(res, m) - return limit > 0 && len(res) >= limit - } - b.Tree.IterateRange(start, end, true, stopAtCount) - return res -} - -func (b *Bonsai) First(start, end []byte) sdk.Model { - var m sdk.Model - stopAtFirst := func(key []byte, value []byte) (stop bool) { - m = sdk.Model{key, value} - return true - } - b.Tree.IterateRange(start, end, true, stopAtFirst) - return m -} - -func (b *Bonsai) Last(start, end []byte) sdk.Model { - var m sdk.Model - stopAtFirst := func(key []byte, value []byte) (stop bool) { - m = sdk.Model{key, value} - return true - } - b.Tree.IterateRange(start, end, false, stopAtFirst) - return m -} - -func (b *Bonsai) Checkpoint() sdk.SimpleDB { - return NewMemKVCache(b) -} - -func (b *Bonsai) Commit(sub sdk.SimpleDB) error { - cache, ok := sub.(*MemKVCache) - if !ok { - return ErrNotASubTransaction() - } - // see if it was wrapping this struct - bb, ok := cache.store.(*Bonsai) - if !ok || (b.id != bb.id) { - return ErrNotASubTransaction() - } - - // apply the cached data to the Bonsai - cache.applyCache() - return nil -} - -//---------------------------------------- -// This is the checkpointing I want, but apparently iavl-tree is not -// as immutable as I hoped... paniced in multiple go-routines :( -// -// FIXME: use this code when iavltree is improved - -// func (b *Bonsai) Checkpoint() sdk.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 sdk.SimpleDB) error { -// bb, ok := sub.(*Bonsai) -// if !ok || (b.id != bb.id) { -// return ErrNotASubTransaction() -// } -// 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/chainstate.go b/state/chainstate.go deleted file mode 100644 index 2b9377d701..0000000000 --- a/state/chainstate.go +++ /dev/null @@ -1,30 +0,0 @@ -package state - -import sdk "github.com/cosmos/cosmos-sdk" - -// ChainState maintains general information for the chain -type ChainState struct { - chainID string -} - -// NewChainState creates a blank state -func NewChainState() *ChainState { - return &ChainState{} -} - -var baseChainIDKey = []byte("base/chain_id") - -// SetChainID stores the chain id in the store -func (s *ChainState) SetChainID(store sdk.KVStore, chainID string) { - s.chainID = chainID - store.Set(baseChainIDKey, []byte(chainID)) -} - -// GetChainID gets the chain id from the cache or the store -func (s *ChainState) GetChainID(store sdk.KVStore) string { - if s.chainID != "" { - return s.chainID - } - s.chainID = string(store.Get(baseChainIDKey)) - return s.chainID -} diff --git a/state/errors.go b/state/errors.go deleted file mode 100644 index 54b5badd3d..0000000000 --- a/state/errors.go +++ /dev/null @@ -1,20 +0,0 @@ -//nolint -package state - -import ( - "fmt" - - abci "github.com/tendermint/abci/types" - "github.com/cosmos/cosmos-sdk/errors" -) - -var ( - errNotASubTransaction = fmt.Errorf("Not a sub-transaction") -) - -func ErrNotASubTransaction() errors.TMError { - return errors.WithCode(errNotASubTransaction, abci.CodeType_InternalError) -} -func IsNotASubTransactionErr(err error) bool { - return errors.IsSameError(errNotASubTransaction, err) -} diff --git a/state/kvcache.go b/state/kvcache.go deleted file mode 100644 index ed9af7a8ae..0000000000 --- a/state/kvcache.go +++ /dev/null @@ -1,149 +0,0 @@ -package state - -import sdk "github.com/cosmos/cosmos-sdk" - -// MemKVCache is designed to wrap MemKVStore as a cache -type MemKVCache struct { - store sdk.SimpleDB - cache *MemKVStore -} - -var _ sdk.SimpleDB = (*MemKVCache)(nil) - -// NewMemKVCache wraps a cache around MemKVStore -// -// You probably don't want to use directly, but rather -// via MemKVCache.Checkpoint() -func NewMemKVCache(store sdk.SimpleDB) *MemKVCache { - if store == nil { - panic("wtf") - } - - return &MemKVCache{ - store: store, - cache: NewMemKVStore(), - } -} - -// Set sets a key, fulfills KVStore interface -func (c *MemKVCache) Set(key []byte, value []byte) { - c.cache.Set(key, value) -} - -// Get gets a key, fulfills KVStore interface -func (c *MemKVCache) Get(key []byte) (value []byte) { - value, ok := c.cache.m[string(key)] - if !ok { - value = c.store.Get(key) - c.cache.Set(key, value) - } - return value -} - -// Has checks existence of a key, fulfills KVStore interface -func (c *MemKVCache) Has(key []byte) bool { - value := c.Get(key) - return value != nil -} - -// Remove uses nil value as a flag to delete... not ideal but good enough -// for testing -func (c *MemKVCache) Remove(key []byte) (value []byte) { - value = c.Get(key) - c.cache.Set(key, nil) - return value -} - -// List is also inefficiently implemented... -func (c *MemKVCache) List(start, end []byte, limit int) []sdk.Model { - orig := c.store.List(start, end, 0) - cached := c.cache.List(start, end, 0) - keys := c.combineLists(orig, cached) - - // apply limit (too late) - if limit > 0 && len(keys) > 0 { - if limit > len(keys) { - limit = len(keys) - } - keys = keys[:limit] - } - - return keys -} - -func (c *MemKVCache) combineLists(orig, cache []sdk.Model) []sdk.Model { - store := NewMemKVStore() - for _, m := range orig { - store.Set(m.Key, m.Value) - } - for _, m := range cache { - if m.Value == nil { - store.Remove([]byte(m.Key)) - } else { - store.Set([]byte(m.Key), m.Value) - } - } - - return store.List(nil, nil, 0) -} - -// First is done with List, but could be much more efficient -func (c *MemKVCache) First(start, end []byte) sdk.Model { - data := c.List(start, end, 0) - if len(data) == 0 { - return sdk.Model{} - } - return data[0] -} - -// Last is done with List, but could be much more efficient -func (c *MemKVCache) Last(start, end []byte) sdk.Model { - data := c.List(start, end, 0) - if len(data) == 0 { - return sdk.Model{} - } - return data[len(data)-1] -} - -// Checkpoint returns the same state, but where writes -// are buffered and don't affect the parent -func (c *MemKVCache) Checkpoint() sdk.SimpleDB { - return NewMemKVCache(c) -} - -// 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 (c *MemKVCache) Commit(sub sdk.SimpleDB) error { - cache, ok := sub.(*MemKVCache) - if !ok { - return ErrNotASubTransaction() - } - - // 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() - return nil -} - -// applyCache will apply all the cache methods to the underlying store -func (c *MemKVCache) applyCache() { - for _, k := range c.cache.keysInRange(nil, nil) { - v := c.cache.m[k] - if v == nil { - c.store.Remove([]byte(k)) - } else { - c.store.Set([]byte(k), v) - } - } -} - -// Discard will remove reference to this -func (c *MemKVCache) Discard() { - c.cache = NewMemKVStore() -} diff --git a/state/kvcache_test.go b/state/kvcache_test.go deleted file mode 100644 index 185ca2f695..0000000000 --- a/state/kvcache_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package state - -import ( - "fmt" - "testing" - - sdk "github.com/cosmos/cosmos-sdk" - "github.com/stretchr/testify/assert" -) - -func TestCache(t *testing.T) { - assert := assert.New(t) - - cases := []struct { - init []sdk.Model - toGet []sdk.Model - toList []listQuery - - setCache []sdk.Model - removeCache []sdk.Model - getCache []sdk.Model - listCache []listQuery - }{ - // simple add - { - init: []sdk.Model{m("a", "1"), m("c", "2")}, - toGet: []sdk.Model{m("a", "1"), m("c", "2"), m("d", "")}, - toList: []listQuery{{ - "a", "e", 0, - []sdk.Model{m("a", "1"), m("c", "2")}, - m("c", "2"), - }}, - setCache: []sdk.Model{m("d", "3")}, - removeCache: []sdk.Model{m("a", "1")}, - getCache: []sdk.Model{m("a", ""), m("c", "2"), m("d", "3")}, - listCache: []listQuery{{ - "a", "e", 0, - []sdk.Model{m("c", "2"), m("d", "3")}, - m("d", "3"), - }}, - }, - } - - checkGet := func(db sdk.SimpleDB, m sdk.Model, msg string) { - val := db.Get(m.Key) - assert.EqualValues(m.Value, val, msg) - has := db.Has(m.Key) - assert.Equal(len(m.Value) != 0, has, msg) - } - - checkList := func(db sdk.SimpleDB, lq listQuery, msg string) { - start, end := []byte(lq.start), []byte(lq.end) - list := db.List(start, end, lq.limit) - if assert.EqualValues(lq.expected, list, msg) { - var first sdk.Model - if len(lq.expected) > 0 { - first = lq.expected[0] - } - f := db.First(start, end) - assert.EqualValues(first, f, msg) - l := db.Last(start, end) - assert.EqualValues(lq.last, l, msg) - } - } - - for i, tc := range cases { - for j, db := range GetDBs() { - for _, s := range tc.init { - db.Set(s.Key, s.Value) - } - for k, g := range tc.toGet { - msg := fmt.Sprintf("%d/%d/%d: %#v", i, j, k, g) - checkGet(db, g, msg) - } - for k, lq := range tc.toList { - msg := fmt.Sprintf("%d/%d/%d", i, j, k) - checkList(db, lq, msg) - } - - // make cache - cache := db.Checkpoint() - - for _, s := range tc.setCache { - cache.Set(s.Key, s.Value) - } - for k, r := range tc.removeCache { - val := cache.Remove(r.Key) - assert.EqualValues(r.Value, val, "%d/%d/%d: %#v", i, j, k, r) - } - - // make sure data is in cache - for k, g := range tc.getCache { - msg := fmt.Sprintf("%d/%d/%d: %#v", i, j, k, g) - checkGet(cache, g, msg) - } - for k, lq := range tc.listCache { - msg := fmt.Sprintf("%d/%d/%d", i, j, k) - checkList(cache, lq, msg) - } - - // data not in basic store - for k, g := range tc.toGet { - msg := fmt.Sprintf("%d/%d/%d: %#v", i, j, k, g) - checkGet(db, g, msg) - } - for k, lq := range tc.toList { - msg := fmt.Sprintf("%d/%d/%d", i, j, k) - checkList(db, lq, msg) - } - - // commit - db.Commit(cache) - - // make sure data is in cache - for k, g := range tc.getCache { - msg := fmt.Sprintf("%d/%d/%d", i, j, k) - checkGet(db, g, msg) - } - for k, lq := range tc.listCache { - msg := fmt.Sprintf("%d/%d/%d", i, j, k) - checkList(db, lq, msg) - } - } - } -} diff --git a/state/kvstore.go b/state/kvstore.go deleted file mode 100644 index 707612e26b..0000000000 --- a/state/kvstore.go +++ /dev/null @@ -1,134 +0,0 @@ -package state - -import ( - "sort" - - sdk "github.com/cosmos/cosmos-sdk" -) - -//---------------------------------------- - -// MemKVStore is a simple implementation of sdk.SimpleDB. -// It is only intended for quick testing, not to be used -// in production or with large data stores. -type MemKVStore struct { - m map[string][]byte -} - -var _ sdk.SimpleDB = NewMemKVStore() - -// NewMemKVStore initializes a MemKVStore -func NewMemKVStore() *MemKVStore { - return &MemKVStore{ - m: make(map[string][]byte, 0), - } -} - -func (m *MemKVStore) Set(key []byte, value []byte) { - m.m[string(key)] = value -} - -func (m *MemKVStore) Get(key []byte) (value []byte) { - return m.m[string(key)] -} - -func (m *MemKVStore) Has(key []byte) (has bool) { - _, ok := m.m[string(key)] - return ok -} - -func (m *MemKVStore) Remove(key []byte) (value []byte) { - val := m.m[string(key)] - delete(m.m, string(key)) - return val -} - -func (m *MemKVStore) List(start, end []byte, limit int) []sdk.Model { - keys := m.keysInRange(start, end) - if limit > 0 && len(keys) > 0 { - if limit > len(keys) { - limit = len(keys) - } - keys = keys[:limit] - } - - res := make([]sdk.Model, len(keys)) - for i, k := range keys { - res[i] = sdk.Model{ - Key: []byte(k), - Value: m.m[k], - } - } - return res -} - -// First iterates through all keys to find the one that matches -func (m *MemKVStore) First(start, end []byte) sdk.Model { - key := "" - for _, k := range m.keysInRange(start, end) { - if key == "" || k < key { - key = k - } - } - if key == "" { - return sdk.Model{} - } - return sdk.Model{ - Key: []byte(key), - Value: m.m[key], - } -} - -func (m *MemKVStore) Last(start, end []byte) sdk.Model { - key := "" - for _, k := range m.keysInRange(start, end) { - if key == "" || k > key { - key = k - } - } - if key == "" { - return sdk.Model{} - } - return sdk.Model{ - Key: []byte(key), - Value: m.m[key], - } -} - -func (m *MemKVStore) Discard() { - m.m = make(map[string][]byte, 0) -} - -func (m *MemKVStore) Checkpoint() sdk.SimpleDB { - return NewMemKVCache(m) -} - -func (m *MemKVStore) Commit(sub sdk.SimpleDB) error { - cache, ok := sub.(*MemKVCache) - if !ok { - return ErrNotASubTransaction() - } - - // 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() - return nil -} - -func (m *MemKVStore) keysInRange(start, end []byte) (res []string) { - s, e := string(start), string(end) - for k := range m.m { - afterStart := s == "" || k >= s - beforeEnd := e == "" || k < e - if afterStart && beforeEnd { - res = append(res, k) - } - } - sort.Strings(res) - return -} diff --git a/state/merkle.go b/state/merkle.go deleted file mode 100644 index 8257ca2016..0000000000 --- a/state/merkle.go +++ /dev/null @@ -1,94 +0,0 @@ -package state - -import ( - sdk "github.com/cosmos/cosmos-sdk" - "github.com/tendermint/iavl" -) - -// State represents the app states, separating the commited state (for queries) -// from the working state (for CheckTx and AppendTx) -type State struct { - committed *Bonsai - deliverTx sdk.SimpleDB - checkTx sdk.SimpleDB - historySize uint64 -} - -// NewState wraps a versioned tree and maintains all needed -// states for the abci app -func NewState(tree *iavl.VersionedTree, historySize uint64) *State { - base := NewBonsai(tree) - return &State{ - committed: base, - deliverTx: base.Checkpoint(), - checkTx: base.Checkpoint(), - historySize: historySize, - } -} - -// Size is the number of nodes in the last commit -func (s State) Size() int { - return s.committed.Tree.Size() -} - -// IsEmpty is true is no data was ever in the tree -// (and signals it is unsafe to save) -func (s State) IsEmpty() bool { - return s.committed.Tree.IsEmpty() -} - -// Committed gives us read-only access to the committed -// state(s), including historical queries -func (s State) Committed() *Bonsai { - return s.committed -} - -// Append gives us read-write access to the current working -// state (to be committed at EndBlock) -func (s State) Append() sdk.SimpleDB { - return s.deliverTx -} - -// Append gives us read-write access to the current scratch -// state (to be reset at EndBlock) -func (s State) Check() sdk.SimpleDB { - return s.checkTx -} - -// LatestHeight is the last block height we have committed -func (s State) LatestHeight() uint64 { - return s.committed.Tree.LatestVersion() -} - -// LatestHash is the root hash of the last state we have -// committed -func (s State) LatestHash() []byte { - return s.committed.Tree.Hash() -} - -// Commit saves persistent nodes to the database and re-copies the trees -func (s *State) Commit(version uint64) ([]byte, error) { - // commit (if we didn't do hash earlier) - err := s.committed.Commit(s.deliverTx) - if err != nil { - return nil, err - } - - // store a new version - var hash []byte - if !s.IsEmpty() { - hash, err = s.committed.Tree.SaveVersion(version) - if err != nil { - return nil, err - } - } - - // release an old version - if version > s.historySize { - s.committed.Tree.DeleteVersion(version - s.historySize) - } - - s.deliverTx = s.committed.Checkpoint() - s.checkTx = s.committed.Checkpoint() - return hash, nil -} diff --git a/state/merkle_test.go b/state/merkle_test.go deleted file mode 100644 index 3e4fdb724b..0000000000 --- a/state/merkle_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package state - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/tendermint/iavl" - db "github.com/tendermint/tmlibs/db" -) - -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.NewVersionedTree(0, db.NewMemDB()) - store := NewState(tree, 2) - - 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(uint64(n + 1)) - 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 - } - } - -} diff --git a/state/set.go b/state/set.go deleted file mode 100644 index 322d2b87e6..0000000000 --- a/state/set.go +++ /dev/null @@ -1,158 +0,0 @@ -package state - -import ( - "bytes" - "sort" - - sdk "github.com/cosmos/cosmos-sdk" - wire "github.com/tendermint/go-wire" -) - -// SetKey returns the key to get all members of this set -func SetKey() []byte { - return keys -} - -// Set allows us to add arbitrary k-v pairs, check existence, -// as well as iterate through the set (always in key order) -// -// If we had full access to the IAVL tree, this would be completely -// trivial and redundant -type Set struct { - store sdk.KVStore - keys KeyList -} - -var _ sdk.KVStore = &Set{} - -// NewSet loads or initializes a span of keys -func NewSet(store sdk.KVStore) *Set { - s := &Set{store: store} - s.loadKeys() - return s -} - -// Set puts a value at a given height. -// If the value is nil, or an empty slice, remove the key from the list -func (s *Set) Set(key []byte, value []byte) { - s.store.Set(MakeBKey(key), value) - if len(value) > 0 { - s.addKey(key) - } else { - s.removeKey(key) - } - s.storeKeys() -} - -// Get returns the element with a key if it exists -func (s *Set) Get(key []byte) []byte { - return s.store.Get(MakeBKey(key)) -} - -// Remove deletes this key from the set (same as setting value = nil) -func (s *Set) Remove(key []byte) { - s.store.Set(key, nil) -} - -// Exists checks for the existence of the key in the set -func (s *Set) Exists(key []byte) bool { - return len(s.Get(key)) > 0 -} - -// Size returns how many elements are in the set -func (s *Set) Size() int { - return len(s.keys) -} - -// List returns all keys in the set -// It makes a copy, so we don't modify this in place -func (s *Set) List() (keys KeyList) { - out := make([][]byte, len(s.keys)) - for i := range s.keys { - out[i] = append([]byte(nil), s.keys[i]...) - } - return out -} - -// addKey inserts this key, maintaining sorted order, no duplicates -func (s *Set) addKey(key []byte) { - for i, k := range s.keys { - cmp := bytes.Compare(k, key) - // don't add duplicates - if cmp == 0 { - return - } - // insert before the first key greater than input - if cmp > 0 { - // https://github.com/golang/go/wiki/SliceTricks - s.keys = append(s.keys, nil) - copy(s.keys[i+1:], s.keys[i:]) - s.keys[i] = key - return - } - } - // if it is higher than all (or empty keys), append - s.keys = append(s.keys, key) -} - -// removeKey removes this key if it is present, maintaining sorted order -func (s *Set) removeKey(key []byte) { - for i, k := range s.keys { - cmp := bytes.Compare(k, key) - // if there is a match, remove - if cmp == 0 { - s.keys = append(s.keys[:i], s.keys[i+1:]...) - return - } - // if we has the proper location, without finding it, abort - if cmp > 0 { - return - } - } -} - -func (s *Set) loadKeys() { - b := s.store.Get(keys) - if b == nil { - return - } - err := wire.ReadBinaryBytes(b, &s.keys) - // hahaha... just like i love to hate :) - if err != nil { - panic(err) - } -} - -func (s *Set) storeKeys() { - b := wire.BinaryBytes(s.keys) - s.store.Set(keys, b) -} - -// MakeBKey prefixes the byte slice for the storage key -func MakeBKey(key []byte) []byte { - return append(dataKey, key...) -} - -// KeyList is a sortable list of byte slices -type KeyList [][]byte - -//nolint -func (kl KeyList) Len() int { return len(kl) } -func (kl KeyList) Less(i, j int) bool { return bytes.Compare(kl[i], kl[j]) < 0 } -func (kl KeyList) Swap(i, j int) { kl[i], kl[j] = kl[j], kl[i] } - -var _ sort.Interface = KeyList{} - -// Equals checks for if the two lists have the same content... -// needed as == doesn't work for slices of slices -func (kl KeyList) Equals(kl2 KeyList) bool { - if len(kl) != len(kl2) { - return false - } - for i := range kl { - if !bytes.Equal(kl[i], kl2[i]) { - return false - } - } - return true -} diff --git a/state/set_test.go b/state/set_test.go deleted file mode 100644 index 2c2f3a5d22..0000000000 --- a/state/set_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package state - -import ( - "strconv" - "testing" - - "github.com/stretchr/testify/assert" -) - -type pair struct { - k []byte - v []byte -} - -type setCase struct { - data []pair - // these are the tests to try out - gets []pair // for each item check the query matches - list KeyList // make sure the set returns the proper list -} - -func TestSet(t *testing.T) { - - a, b, c, d := []byte{0xaa}, []byte{0xbb}, []byte{0xcc}, []byte{0xdd} - - cases := []setCase{ - - // simplest queries - { - []pair{{a, a}, {b, b}, {c, c}}, - []pair{{c, c}, {d, nil}, {b, b}}, - KeyList{a, b, c}, - }, - // out of order - { - []pair{{c, a}, {a, b}, {d, c}, {b, d}}, - []pair{{a, b}, {b, d}}, - KeyList{a, b, c, d}, - }, - // duplicate and removing - { - []pair{{c, a}, {c, c}, {a, d}, {d, d}, {b, b}, {d, nil}, {a, nil}, {a, a}, {b, nil}}, - []pair{{a, a}, {c, c}, {b, nil}}, - KeyList{a, c}, - }, - } - - for i, tc := range cases { - store := NewMemKVStore() - - // initialize a queue and add items - s := NewSet(store) - for _, x := range tc.data { - s.Set(x.k, x.v) - } - - testSet(t, i, s, tc) - // reload and try the queries again - s2 := NewSet(store) - testSet(t, i+10, s2, tc) - } -} - -func testSet(t *testing.T, idx int, s *Set, tc setCase) { - assert := assert.New(t) - i := strconv.Itoa(idx) - - for _, g := range tc.gets { - v := s.Get(g.k) - assert.Equal(g.v, v, i) - e := s.Exists(g.k) - assert.Equal(e, (g.v != nil), i) - } - - l := s.List() - assert.True(tc.list.Equals(l), "%s: %v / %v", i, tc.list, l) -} diff --git a/state/span.go b/state/span.go deleted file mode 100644 index 32a2e45fd4..0000000000 --- a/state/span.go +++ /dev/null @@ -1,126 +0,0 @@ -package state - -import ( - sdk "github.com/cosmos/cosmos-sdk" - wire "github.com/tendermint/go-wire" -) - -var ( - keys = []byte("keys") - // uses dataKey from queue.go to prefix data -) - -// Span holds a number of different keys in a large range and allows -// use to make some basic range queries, like highest between, lowest between... -// All items are added with an index -// -// This becomes horribly inefficent as len(keys) => 1000+, but by then -// hopefully we have access to the iavl tree to do this well -// -// TODO: doesn't handle deleting.... -type Span struct { - store sdk.KVStore - // keys is sorted ascending and cannot contain duplicates - keys []uint64 -} - -// NewSpan loads or initializes a span of keys -func NewSpan(store sdk.KVStore) *Span { - s := &Span{store: store} - s.loadKeys() - return s -} - -// Set puts a value at a given height -func (s *Span) Set(h uint64, value []byte) { - key := makeKey(h) - s.store.Set(key, value) - s.addKey(h) - s.storeKeys() -} - -// Get returns the element at h if it exists -func (s *Span) Get(h uint64) []byte { - key := makeKey(h) - return s.store.Get(key) -} - -// Bottom returns the lowest element in the Span, along with its index -func (s *Span) Bottom() ([]byte, uint64) { - if len(s.keys) == 0 { - return nil, 0 - } - h := s.keys[0] - return s.Get(h), h -} - -// Top returns the highest element in the Span, along with its index -func (s *Span) Top() ([]byte, uint64) { - l := len(s.keys) - if l == 0 { - return nil, 0 - } - h := s.keys[l-1] - return s.Get(h), h -} - -// GTE returns the lowest element in the Span that is >= h, along with its index -func (s *Span) GTE(h uint64) ([]byte, uint64) { - for _, k := range s.keys { - if k >= h { - return s.Get(k), k - } - } - return nil, 0 -} - -// LTE returns the highest element in the Span that is <= h, -// along with its index -func (s *Span) LTE(h uint64) ([]byte, uint64) { - var k uint64 - // start from the highest and go down for the first match - for i := len(s.keys) - 1; i >= 0; i-- { - k = s.keys[i] - if k <= h { - return s.Get(k), k - } - } - return nil, 0 -} - -// addKey inserts this key, maintaining sorted order, no duplicates -func (s *Span) addKey(h uint64) { - for i, k := range s.keys { - // don't add duplicates - if h == k { - return - } - // insert before this key - if h < k { - // https://github.com/golang/go/wiki/SliceTricks - s.keys = append(s.keys, 0) - copy(s.keys[i+1:], s.keys[i:]) - s.keys[i] = h - return - } - } - // if it is higher than all (or empty keys), append - s.keys = append(s.keys, h) -} - -func (s *Span) loadKeys() { - b := s.store.Get(keys) - if b == nil { - return - } - err := wire.ReadBinaryBytes(b, &s.keys) - // hahaha... just like i love to hate :) - if err != nil { - panic(err) - } -} - -func (s *Span) storeKeys() { - b := wire.BinaryBytes(s.keys) - s.store.Set(keys, b) -} diff --git a/state/span_test.go b/state/span_test.go deleted file mode 100644 index 8362f3d90f..0000000000 --- a/state/span_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package state - -import ( - "strconv" - "testing" - - "github.com/stretchr/testify/assert" -) - -type kv struct { - k uint64 - v []byte -} - -type bscase struct { - data []kv - // these are the tests to try out - top kv - bottom kv - gets []kv // for each item check the query matches - lte []kv // value for lte queires... - gte []kv // value for gte -} - -func TestBasicSpan(t *testing.T) { - - a, b, c := []byte{0xaa}, []byte{0xbb}, []byte{0xcc} - - lots := make([]kv, 1000) - for i := range lots { - lots[i] = kv{uint64(3 * i), []byte{byte(i / 100), byte(i % 100)}} - } - - cases := []bscase{ - // simplest queries - { - []kv{{1, a}, {3, b}, {5, c}}, - kv{5, c}, - kv{1, a}, - []kv{{1, a}, {3, b}, {5, c}}, - []kv{{2, a}, {77, c}, {3, b}, {0, nil}}, // lte - []kv{{6, nil}, {2, b}, {1, a}}, // gte - }, - // add out of order - { - []kv{{7, a}, {2, b}, {6, c}}, - kv{7, a}, - kv{2, b}, - []kv{{2, b}, {6, c}, {7, a}}, - []kv{{4, b}, {7, a}, {1, nil}}, // lte - []kv{{4, c}, {7, a}, {1, b}}, // gte - }, - // add out of order and with duplicates - { - []kv{{7, a}, {2, b}, {6, c}, {7, c}, {6, b}, {2, a}}, - kv{7, c}, - kv{2, a}, - []kv{{2, a}, {6, b}, {7, c}}, - []kv{{5, a}, {6, b}, {123, c}}, // lte - []kv{{0, a}, {3, b}, {7, c}, {8, nil}}, // gte - }, - // try lots... - { - lots, - lots[len(lots)-1], - lots[0], - lots, - nil, - nil, - }, - } - - for i, tc := range cases { - store := NewMemKVStore() - - // initialize a queue and add items - s := NewSpan(store) - for _, x := range tc.data { - s.Set(x.k, x.v) - } - - testSpan(t, i, s, tc) - // reload and try the queries again - s2 := NewSpan(store) - testSpan(t, i+10, s2, tc) - } -} - -func testSpan(t *testing.T, idx int, s *Span, tc bscase) { - assert := assert.New(t) - i := strconv.Itoa(idx) - - v, k := s.Top() - assert.Equal(tc.top.k, k, i) - assert.Equal(tc.top.v, v, i) - - v, k = s.Bottom() - assert.Equal(tc.bottom.k, k, i) - assert.Equal(tc.bottom.v, v, i) - - for _, g := range tc.gets { - v = s.Get(g.k) - assert.Equal(g.v, v, i) - } - - for _, l := range tc.lte { - v, k = s.LTE(l.k) - assert.Equal(l.v, v, i) - if l.v != nil { - assert.True(k <= l.k, i) - } - } - - for _, t := range tc.gte { - v, k = s.GTE(t.k) - assert.Equal(t.v, v, i) - if t.v != nil { - assert.True(k >= t.k, i) - } - } - -} diff --git a/state/store_test.go b/state/store_test.go deleted file mode 100644 index c1405c1b53..0000000000 --- a/state/store_test.go +++ /dev/null @@ -1,148 +0,0 @@ -package state - -import ( - "io/ioutil" - "testing" - - sdk "github.com/cosmos/cosmos-sdk" - "github.com/stretchr/testify/assert" - - "github.com/tendermint/iavl" - dbm "github.com/tendermint/tmlibs/db" -) - -func GetDBs() []sdk.SimpleDB { - // tree with persistence.... - tmpDir, err := ioutil.TempDir("", "state-tests") - if err != nil { - panic(err) - } - db := dbm.NewDB("test-get-dbs", dbm.LevelDBBackendStr, tmpDir) - persist := iavl.NewVersionedTree(500, db) - - return []sdk.SimpleDB{ - NewMemKVStore(), - NewBonsai(iavl.NewVersionedTree(0, dbm.NewMemDB())), - NewBonsai(persist), - } -} - -func b(k string) []byte { - if k == "" { - return nil - } - return []byte(k) -} - -func m(k, v string) sdk.Model { - return sdk.Model{ - Key: b(k), - Value: b(v), - } -} - -type listQuery struct { - // this is the list query - start, end string - limit int - // expected result from List, first element also expected for First - expected []sdk.Model - // expected result from Last - last sdk.Model -} - -// TestKVStore makes sure that get/set/remove operations work, -// as well as list -func TestKVStore(t *testing.T) { - assert := assert.New(t) - - cases := []struct { - toSet []sdk.Model - toRemove []sdk.Model - toGet []sdk.Model - toList []listQuery - }{ - // simple add - { - toSet: []sdk.Model{m("a", "b"), m("c", "d")}, - toRemove: nil, - toGet: []sdk.Model{m("a", "b")}, - toList: []listQuery{ - { - "a", "d", 0, - []sdk.Model{m("a", "b"), m("c", "d")}, - m("c", "d"), - }, - { - "a", "c", 10, - []sdk.Model{m("a", "b")}, - m("a", "b"), - }, - }, - }, - // over-write data, remove - { - toSet: []sdk.Model{ - m("a", "1"), - m("b", "2"), - m("c", "3"), - m("b", "4"), - }, - toRemove: []sdk.Model{m("c", "3")}, - toGet: []sdk.Model{ - m("a", "1"), - m("b", "4"), - m("c", ""), - }, - toList: []listQuery{ - { - "0d", "h", 1, - []sdk.Model{m("a", "1")}, - m("b", "4"), - }, - { - "ad", "ak", 10, - []sdk.Model{}, - sdk.Model{}, - }, - { - "ad", "k", 0, - []sdk.Model{m("b", "4")}, - m("b", "4"), - }, - }, - }, - } - - for i, tc := range cases { - for j, db := range GetDBs() { - for _, s := range tc.toSet { - db.Set(s.Key, s.Value) - } - for k, r := range tc.toRemove { - val := db.Remove(r.Key) - assert.EqualValues(r.Value, val, "%d/%d/%d", i, j, k) - } - for k, g := range tc.toGet { - val := db.Get(g.Key) - assert.EqualValues(g.Value, val, "%d/%d/%d", i, j, k) - has := db.Has(g.Key) - assert.Equal(len(g.Value) != 0, has, "%d/%d/%d", i, j, k) - } - for k, lq := range tc.toList { - start, end := []byte(lq.start), []byte(lq.end) - list := db.List(start, end, lq.limit) - if assert.EqualValues(lq.expected, list, "%d/%d/%d", i, j, k) { - var first sdk.Model - if len(lq.expected) > 0 { - first = lq.expected[0] - } - f := db.First(start, end) - assert.EqualValues(first, f, "%d/%d/%d", i, j, k) - l := db.Last(start, end) - assert.EqualValues(lq.last, l, "%d/%d/%d", i, j, k) - } - } - } - } -} diff --git a/store/multistore.go b/store/multistore.go new file mode 100644 index 0000000000..e4262993cc --- /dev/null +++ b/store/multistore.go @@ -0,0 +1,269 @@ +package store + +import ( + "fmt" + "sort" + + "github.com/tendermint/go-wire" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/merkle" + "golang.org/x/crypto/ripemd160" +) + +const ( + msLatestKey = "s/latest" + msStateKeyFmt = "s/%d" // s/ +) + +type MultiStore interface { + CacheWrappable + + // Convenience + GetStore(name string) interface{} + GetKVStore(name string) KVStore + GetIterKVStore(name string) IterKVStore +} + +//---------------------------------------- + +// RootStore is composed of many Committers. +// Implements MultiStore. +type RootStore struct { + db dbm.DB + version uint64 + storeLoaders map[string]CommitterLoader + substores map[string]Committer +} + +func NewRootStore(db dbm.DB) *RootStore { + return &RootStore{ + db: db, + version: 0, + storeLoaders: make(map[string]CommitterLoader), + substores: make(map[string]Committer), + } +} + +func (rs *RootStore) SetCommitterLoader(name string, loader CommitterLoader) { + if _, ok := rs.storeLoaders[name]; ok { + panic(fmt.Sprintf("RootStore duplicate substore name " + name)) + } + rs.storeLoaders[name] = loader +} + +//---------------------------------------- +// RootStore state + +type msState struct { + Substores []substore +} + +func (rs *msState) Sort() { + rs.Substores.Sort() +} + +func (rs *msState) Hash() []byte { + m := make(map[string]interface{}, len(rs.Substores)) + for _, substore := range rs.Substores { + m[substore.name] = substore.ssState + } + return merkle.SimpleHashFromMap(m) +} + +//---------------------------------------- +// substore state + +type substore struct { + name string + ssState +} + +// This gets serialized by go-wire +type ssState struct { + CommitID CommitID + // ... maybe add more state +} + +func (ss ssState) Hash() []byte { + ssBytes, _ := wire.Marshal(ss) // Does not error + hasher := ripemd160.New() + hasher.Write(ssBytes) + return hasher.Sum(nil) +} + +//---------------------------------------- + +// Call once after all calls to SetStoreLoader are complete. +func (rs *RootStore) LoadLatestVersion() error { + ver := rs.getLatestVersion() + rs.LoadVersion(ver) +} + +func (rs *RootStore) getLatestVersion() uint64 { + var latest uint64 + latestBytes := rs.db.Get(msLatestKey) + if latestBytes == nil { + return 0 + } + err := wire.Unmarshal(latestBytes, &latest) + if err != nil { + panic(err) + } + return latest +} + +func (rs *RootStore) LoadVersion(ver uint64) error { + rs.version = ver + + // Special logic for version 0 + if ver == 0 { + for name, storeLoader := range rs.storeLoaders { + store, err := storeLoader(CommitID{Version: 0}) + if err != nil { + return fmt.Errorf("Failed to load RootStore: %v", err) + } + rs.substores[name] = store + } + return nil + } + // Otherwise, version is 1 or greater + + msStateKey := fmt.Sprintf(msStateKeyFmt, ver) + stateBytes := rs.db.Get(msStateKey, ver) + if bz == nil { + return fmt.Errorf("Failed to load RootStore: no data") + } + var state msState + err := wire.Unmarshal(stateBytes, &state) + if err != nil { + return fmt.Errorf("Failed to load RootStore: %v", err) + } + + // Load each Substore + for _, store := range state.Substores { + name, commitID := store.Name, store.CommitID + storeLoader := rs.storeLoaders[name] + if storeLoader == nil { + return fmt.Errorf("Failed to loadRootStore: StoreLoader missing for %v", name) + } + store, err := storeLoader(commitID) + if err != nil { + return fmt.Errorf("Failed to load RootStore: %v", err) + } + rs.substores[name] = store + } + + // If any StoreLoaders were not used, return error. + for name := range rs.storeLoaders { + if _, ok := rs.substores[name]; !ok { + return fmt.Errorf("Unused StoreLoader: %v", name) + } + } + + return nil +} + +// Implements Committer +func (rs *RootStore) Commit() CommitID { + + // Needs to be transactional + batch := rs.db.NewBatch() + + // Save msState + var state msState + for name, store := range rs.substores { + commitID := store.Commit() + state.Substores = append(state.Substores, + ssState{ + Name: name, + CommitID: commitID, + }, + ) + } + state.Sort() + stateBytes, err := wire.Marshal(state) + if err != nil { + panic(err) + } + msStateKey := fmt.Sprintf(msStateKeyFmt, rs.version) + batch.Set(msStateKey, stateBytes) + + // Save msLatest + latestBytes, _ := wire.Marshal(rs.version) // Does not error + batch.Set(msLatestKey, latestBytes) + + batch.Write() + batch.version += 1 +} + +// Implements MultiStore/CacheWrappable +func (rs *RootStore) CacheWrap() (o interface{}) { + return newCacheMultiStore(rs) +} + +// Implements MultiStore/CacheWrappable +func (rs *RootStore) GetCommitter(name string) Committer { + return rs.store[name] +} + +// Implements MultiStore/CacheWrappable +func (rs *RootStore) GetKVStore(name string) KVStore { + return rs.store[name].(KVStore) +} + +// Implements MultiStore/CacheWrappable +func (rs *RootStore) GetIterKVStore(name string) IterKVStore { + return rs.store[name].(IterKVStore) +} + +//---------------------------------------- +// ssStates + +type ssStates []ssState + +func (ssz ssStates) Len() int { return len(ssz) } +func (ssz ssStates) Less(i, j int) bool { return ssz[i].Key < ssz[j].Key } +func (ssz ssStates) Swap(i, j int) { ssz[i], ssz[j] = ssz[j], ssz[i] } +func (ssz ssStates) Sort() { sort.Sort(ssz) } + +func (ssz ssStates) Hash() []byte { + hz := make([]merkle.Hashable, len(ssz)) + for i, ss := range ssz { + hz[i] = ss + } + return merkle.SimpleHashFromHashables(hz) +} + +//---------------------------------------- +// cacheMultiStore + +type cwWriter interface { + Write() +} + +// cacheMultiStore holds many CacheWrap'd stores. +// Implements MultiStore. +type cacheMultiStore struct { + db dbm.DB + version uint64 + substores map[string]cwWriter +} + +func newCacheMultiStore(rs *RootStore) MultiStore { + cms := cacheMultiStore{ + db: db.CacheWrap(), + version: rs.version, + substores: make(map[string]cwwWriter), len(rs.substores), + } + for name, substore := range rs.substores { + cms.substores[name] = substore.CacheWrap().(cwWriter) + } + return cms +} + +func (cms cacheMultiStore) Write() { + cms.db.Write() + for substore := range rs.substores { + substore.(cwWriter).Write() + } +} diff --git a/state/queue.go b/store/queue.go similarity index 99% rename from state/queue.go rename to store/queue.go index 89e716b23a..a1512dc505 100644 --- a/state/queue.go +++ b/store/queue.go @@ -1,4 +1,4 @@ -package state +package store import ( "encoding/binary" diff --git a/state/queue_test.go b/store/queue_test.go similarity index 98% rename from state/queue_test.go rename to store/queue_test.go index d6212be90e..b0320b32fa 100644 --- a/state/queue_test.go +++ b/store/queue_test.go @@ -1,4 +1,4 @@ -package state +package store import ( "testing" diff --git a/store/types.go b/store/types.go new file mode 100644 index 0000000000..d88207f7a3 --- /dev/null +++ b/store/types.go @@ -0,0 +1,103 @@ +package store + +import ( + "github.com/tendermint/go-wire/data" +) + +type CommitID struct { + Version uint64 + Hash []byte +} + +type Committer interface { + + // Commit persists the state to disk. + Commit() CommitID +} + +type CommitterLoader func(id CommitID) (Committer, error) + +// A Store is anything that can be wrapped with a cache. +type CacheWrappable interface { + + // CacheWrap() wraps a thing with a cache. After calling + // .Write() on the CacheWrap, all previous CacheWraps on the + // object expire. + // + // CacheWrap() should not return a Committer, since Commit() on + // CacheWraps make no sense. It can return KVStore, IterKVStore, + // etc. + // + // NOTE: https://dave.cheney.net/2017/07/22/should-go-2-0-support-generics. + // The returned object may or may not implement CacheWrap() as well. + CacheWrap() interface{} +} + +// KVStore is a simple interface to get/set data +type KVStore interface { + CacheWrappable // CacheWrap returns KVStore + + Set(key, value []byte) (prev []byte) + Get(key []byte) (value []byte, exists bool) + Has(key []byte) (exists bool) + Remove(key []byte) (prev []byte, removed bool) +} + +// IterKVStore can be iterated on +// CONTRACT: No writes may happen within a domain while an iterator exists over it. +type IterKVStore interface { + KVStore // CacheWrap returns IterKVMap + + Iterator(start, end []byte) Iterator + ReverseIterator(start, end []byte) Iterator + + First(start, end []byte) (kv KVPair, ok bool) + Last(start, end []byte) (kv KVPair, ok bool) +} + +type KVPair struct { + Key data.Bytes + Value data.Bytes +} + +/* + Usage: + + for itr := kvm.Iterator(start, end); itr.Valid(); itr.Next() { + k, v := itr.Key(); itr.Value() + .... + } +*/ +type Iterator interface { + + // The start & end (exclusive) limits to iterate over. + // If end < start, then the Iterator goes in reverse order. + // A domain of ([]byte{12, 13}, []byte{12, 14}) will iterate + // over anything with the prefix []byte{12, 13} + Domain() (start []byte, end []byte) + + // Returns if the current position is valid. + Valid() bool + + // Next moves the iterator to the next key/value pair. + // + // If Valid returns false, this method will panic. + Next() + + // Key returns the key of the current key/value pair, or nil if done. + // The caller should not modify the contents of the returned slice, and + // its contents may change after calling Next(). + // + // If Valid returns false, this method will panic. + Key() []byte + + // Value returns the key of the current key/value pair, or nil if done. + // The caller should not modify the contents of the returned slice, and + // its contents may change after calling Next(). + // + // If Valid returns false, this method will panic. + Value() []byte + + // Releases any resources and iteration-locks + Release() +}