Remove most of state/*

This commit is contained in:
Jae Kwon 2017-10-31 15:45:57 -05:00
parent f4b7a6ce40
commit 2b84b2cda2
18 changed files with 375 additions and 1428 deletions

1
TODO Normal file
View File

@ -0,0 +1 @@
Make Queue work with new types.go

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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)
}
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}
}
}

269
store/multistore.go Normal file
View File

@ -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/<version>
)
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()
}
}

View File

@ -1,4 +1,4 @@
package state
package store
import (
"encoding/binary"

View File

@ -1,4 +1,4 @@
package state
package store
import (
"testing"

103
store/types.go Normal file
View File

@ -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()
}