Add SimpleDB interface for merkle tree
This commit is contained in:
parent
243d767aaa
commit
caff0ad01b
|
@ -4,6 +4,7 @@ import (
|
|||
"container/list"
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/go-wire/data"
|
||||
. "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
|
@ -14,6 +15,37 @@ type KVStore interface {
|
|||
|
||||
//----------------------------------------
|
||||
|
||||
type Model struct {
|
||||
Key data.Bytes
|
||||
Value data.Bytes
|
||||
}
|
||||
|
||||
// What I wished to have...
|
||||
type SimpleDB interface {
|
||||
KVStore
|
||||
|
||||
Has(key []byte) (has bool)
|
||||
Remove(key []byte) (value []byte) // returns old value if there was one
|
||||
|
||||
List(start, end []byte, limit int) []Model
|
||||
First(start, end []byte) Model
|
||||
Last(start, end []byte) Model
|
||||
|
||||
// Checkpoint returns the same state, but where writes
|
||||
// are buffered and don't affect the parent
|
||||
Checkpoint() SimpleDB
|
||||
|
||||
// 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
|
||||
Commit(SimpleDB) error
|
||||
|
||||
// Discard will remove reference to this
|
||||
Discard()
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
|
||||
type MemKVStore struct {
|
||||
m map[string][]byte
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package merkle
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
|
||||
"github.com/tendermint/basecoin/state"
|
||||
"github.com/tendermint/merkleeyes/iavl"
|
||||
"github.com/tendermint/tmlibs/merkle"
|
||||
)
|
||||
|
@ -23,16 +27,16 @@ func NewState(tree merkle.Tree, persistent bool) State {
|
|||
}
|
||||
}
|
||||
|
||||
func (s State) Committed() Bonsai {
|
||||
return Bonsai{s.committed}
|
||||
func (s State) Committed() *Bonsai {
|
||||
return NewBonsai(s.committed)
|
||||
}
|
||||
|
||||
func (s State) Append() Bonsai {
|
||||
return Bonsai{s.deliverTx}
|
||||
func (s State) Append() *Bonsai {
|
||||
return NewBonsai(s.deliverTx)
|
||||
}
|
||||
|
||||
func (s State) Check() Bonsai {
|
||||
return Bonsai{s.checkTx}
|
||||
func (s State) Check() *Bonsai {
|
||||
return NewBonsai(s.checkTx)
|
||||
}
|
||||
|
||||
// Hash updates the tree
|
||||
|
@ -66,18 +70,92 @@ func (s *State) Commit() []byte {
|
|||
return hash
|
||||
}
|
||||
|
||||
// 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
|
||||
merkle.Tree
|
||||
}
|
||||
|
||||
var _ state.SimpleDB = &Bonsai{}
|
||||
|
||||
func NewBonsai(tree merkle.Tree) *Bonsai {
|
||||
return &Bonsai{
|
||||
id: nonce(rand.Int63()),
|
||||
Tree: tree,
|
||||
}
|
||||
}
|
||||
|
||||
// Get matches the signature of KVStore
|
||||
func (b Bonsai) Get(key []byte) []byte {
|
||||
func (b *Bonsai) Get(key []byte) []byte {
|
||||
_, value, _ := b.Tree.Get(key)
|
||||
return value
|
||||
}
|
||||
|
||||
// Set matches the signature of KVStore
|
||||
func (b Bonsai) Set(key, value []byte) {
|
||||
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) List(start, end []byte, limit int) []state.Model {
|
||||
var res []state.Model
|
||||
stopAtCount := func(key []byte, value []byte) (stop bool) {
|
||||
m := state.Model{key, value}
|
||||
res = append(res, m)
|
||||
return len(res) >= limit
|
||||
}
|
||||
b.Tree.IterateRange(start, end, true, stopAtCount)
|
||||
return res
|
||||
}
|
||||
|
||||
func (b *Bonsai) First(start, end []byte) state.Model {
|
||||
var m state.Model
|
||||
stopAtFirst := func(key []byte, value []byte) (stop bool) {
|
||||
m = state.Model{key, value}
|
||||
return true
|
||||
}
|
||||
b.Tree.IterateRange(start, end, true, stopAtFirst)
|
||||
return m
|
||||
}
|
||||
|
||||
func (b *Bonsai) Last(start, end []byte) state.Model {
|
||||
var m state.Model
|
||||
stopAtFirst := func(key []byte, value []byte) (stop bool) {
|
||||
m = state.Model{key, value}
|
||||
return true
|
||||
}
|
||||
b.Tree.IterateRange(start, end, false, stopAtFirst)
|
||||
return m
|
||||
}
|
||||
|
||||
func (b *Bonsai) Checkpoint() state.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 state.SimpleDB) error {
|
||||
bb, ok := sub.(*Bonsai)
|
||||
if !ok || (b.id != bb.id) {
|
||||
return errors.New("Not a sub-transaction")
|
||||
}
|
||||
b.Tree = bb.Tree
|
||||
return nil
|
||||
}
|
||||
|
||||
// Discard will remove reference to this
|
||||
func (b *Bonsai) Discard() {
|
||||
b.id = 0
|
||||
b.Tree = nil
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@ type Store struct {
|
|||
|
||||
var stateKey = []byte("merkle:state") // Database key for merkle tree save value db values
|
||||
|
||||
// MerkleState contains the latest Merkle root hash and the number of times `Commit` has been called
|
||||
type MerkleState struct {
|
||||
// ChainState contains the latest Merkle root hash and the number of times `Commit` has been called
|
||||
type ChainState struct {
|
||||
Hash []byte
|
||||
Height uint64
|
||||
}
|
||||
|
@ -74,29 +74,29 @@ func NewStore(dbName string, cacheSize int, logger log.Logger) *Store {
|
|||
db := dbm.NewDB(name, dbm.LevelDBBackendStr, dir)
|
||||
tree := iavl.NewIAVLTree(cacheSize, db)
|
||||
|
||||
var eyesState MerkleState
|
||||
var chainState ChainState
|
||||
if empty {
|
||||
logger.Info("no existing db, creating new db")
|
||||
eyesState = MerkleState{
|
||||
chainState = ChainState{
|
||||
Hash: tree.Save(),
|
||||
Height: initialHeight,
|
||||
}
|
||||
db.Set(stateKey, wire.BinaryBytes(eyesState))
|
||||
db.Set(stateKey, wire.BinaryBytes(chainState))
|
||||
} else {
|
||||
logger.Info("loading existing db")
|
||||
eyesStateBytes := db.Get(stateKey)
|
||||
err = wire.ReadBinaryBytes(eyesStateBytes, &eyesState)
|
||||
err = wire.ReadBinaryBytes(eyesStateBytes, &chainState)
|
||||
if err != nil {
|
||||
logger.Error("error reading MerkleEyesState", "err", err)
|
||||
panic(err)
|
||||
}
|
||||
tree.Load(eyesState.Hash)
|
||||
tree.Load(chainState.Hash)
|
||||
}
|
||||
|
||||
return &Store{
|
||||
State: NewState(tree, true),
|
||||
height: eyesState.Height,
|
||||
hash: eyesState.Hash,
|
||||
height: chainState.Height,
|
||||
hash: chainState.Hash,
|
||||
persisted: true,
|
||||
logger: logger,
|
||||
}
|
||||
|
@ -112,7 +112,9 @@ func NewStore(dbName string, cacheSize int, logger log.Logger) *Store {
|
|||
// Info implements abci.Application. It returns the height, hash and size (in the data).
|
||||
// The height is the block that holds the transactions, not the apphash itself.
|
||||
func (s *Store) Info() abci.ResponseInfo {
|
||||
s.logger.Info("Info synced", "height", s.height, "hash", fmt.Sprintf("%X", s.hash))
|
||||
s.logger.Info("Info synced",
|
||||
"height", s.height,
|
||||
"hash", fmt.Sprintf("%X", s.hash))
|
||||
return abci.ResponseInfo{
|
||||
Data: cmn.Fmt("size:%v", s.State.Committed().Size()),
|
||||
LastBlockHeight: s.height - 1,
|
||||
|
@ -124,9 +126,11 @@ func (s *Store) Info() abci.ResponseInfo {
|
|||
func (s *Store) Commit() abci.Result {
|
||||
s.hash = s.State.Hash()
|
||||
s.height++
|
||||
s.logger.Debug("Commit synced", "height", s.height, "hash", fmt.Sprintf("%X", s.hash))
|
||||
s.logger.Debug("Commit synced",
|
||||
"height", s.height,
|
||||
"hash", fmt.Sprintf("%X", s.hash))
|
||||
|
||||
s.State.BatchSet(stateKey, wire.BinaryBytes(MerkleState{
|
||||
s.State.BatchSet(stateKey, wire.BinaryBytes(ChainState{
|
||||
Hash: s.hash,
|
||||
Height: s.height,
|
||||
}))
|
||||
|
@ -144,10 +148,6 @@ func (s *Store) Commit() abci.Result {
|
|||
|
||||
// Query implements abci.Application
|
||||
func (s *Store) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) {
|
||||
if len(reqQuery.Data) == 0 {
|
||||
return
|
||||
}
|
||||
tree := s.State.Committed()
|
||||
|
||||
if reqQuery.Height != 0 {
|
||||
// TODO: support older commits
|
||||
|
@ -159,6 +159,8 @@ func (s *Store) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery)
|
|||
// set the query response height to current
|
||||
resQuery.Height = s.height
|
||||
|
||||
tree := s.State.Committed()
|
||||
|
||||
switch reqQuery.Path {
|
||||
case "/store", "/key": // Get by key
|
||||
key := reqQuery.Data // Data holds the key bytes
|
||||
|
@ -170,24 +172,11 @@ func (s *Store) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery)
|
|||
}
|
||||
resQuery.Value = value
|
||||
resQuery.Proof = proof
|
||||
// TODO: return index too?
|
||||
} else {
|
||||
value := tree.Get(key)
|
||||
resQuery.Value = value
|
||||
}
|
||||
|
||||
case "/index": // Get by Index
|
||||
index := wire.GetInt64(reqQuery.Data)
|
||||
key, value := tree.GetByIndex(int(index))
|
||||
resQuery.Key = key
|
||||
resQuery.Index = int64(index)
|
||||
resQuery.Value = value
|
||||
|
||||
case "/size": // Get size
|
||||
size := tree.Size()
|
||||
sizeBytes := wire.BinaryBytes(size)
|
||||
resQuery.Value = sizeBytes
|
||||
|
||||
default:
|
||||
resQuery.Code = abci.CodeType_UnknownRequest
|
||||
resQuery.Log = cmn.Fmt("Unexpected Query path: %v", reqQuery.Path)
|
||||
|
|
Loading…
Reference in New Issue