Remove most of state/*
This commit is contained in:
parent
f4b7a6ce40
commit
2b84b2cda2
142
state/bonsai.go
142
state/bonsai.go
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
149
state/kvcache.go
149
state/kvcache.go
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
134
state/kvstore.go
134
state/kvstore.go
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
158
state/set.go
158
state/set.go
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
126
state/span.go
126
state/span.go
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package state
|
||||
package store
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
|
@ -1,4 +1,4 @@
|
|||
package state
|
||||
package store
|
||||
|
||||
import (
|
||||
"testing"
|
|
@ -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()
|
||||
}
|
Loading…
Reference in New Issue