Fix app state to commit properly

This commit is contained in:
Ethan Frey 2017-07-27 10:40:17 -04:00
parent 6b96fa4554
commit ea8bdd3f92
4 changed files with 138 additions and 112 deletions

View File

@ -129,7 +129,6 @@ func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result {
res, err := app.handler.DeliverTx(ctx, app.state.Append(), tx)
if err != nil {
// discard the cache...
return errors.Result(err)
}
return res.ToABCI()

View File

@ -7,12 +7,28 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/merkleeyes/iavl"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/state"
)
func makeState() state.SimpleDB {
// return state.NewMemKVStore()
return state.NewBonsai(iavl.NewIAVLTree(0, nil))
// 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.NewIAVLTree(500, db)
// return state.NewBonsai(persist)
}
func TestCheckpointer(t *testing.T) {
assert, require := assert.New(t), require.New(t)
@ -69,7 +85,7 @@ func TestCheckpointer(t *testing.T) {
for i, tc := range cases {
ctx := NewContext("foo", 100, log.NewNopLogger())
store := state.NewMemKVStore()
store := makeState()
_, err := app.CheckTx(ctx, store, tc.tx)
if tc.valid {
require.Nil(err, "%+v", err)
@ -81,7 +97,7 @@ func TestCheckpointer(t *testing.T) {
assert.EqualValues(m.Value, val, "%d: %#v", i, m)
}
store = state.NewMemKVStore()
store = makeState()
_, err = app.DeliverTx(ctx, store, tc.tx)
if tc.valid {
require.Nil(err, "%+v", err)

100
state/bonsai.go Normal file
View File

@ -0,0 +1,100 @@
package state
import (
"errors"
"math/rand"
"github.com/tendermint/tmlibs/merkle"
)
// 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 _ SimpleDB = &Bonsai{}
// NewBonsai wraps a merkle tree and tags it to track children
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 {
_, value, _ := b.Tree.Get(key)
return value
}
// 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) List(start, end []byte, limit int) []Model {
res := []Model{}
stopAtCount := func(key []byte, value []byte) (stop bool) {
m := Model{key, value}
res = append(res, m)
// return false
return limit > 0 && len(res) >= limit
}
b.Tree.IterateRange(start, end, true, stopAtCount)
return res
}
func (b *Bonsai) First(start, end []byte) Model {
var m Model
stopAtFirst := func(key []byte, value []byte) (stop bool) {
m = Model{key, value}
return true
}
b.Tree.IterateRange(start, end, true, stopAtFirst)
return m
}
func (b *Bonsai) Last(start, end []byte) Model {
var m Model
stopAtFirst := func(key []byte, value []byte) (stop bool) {
m = Model{key, value}
return true
}
b.Tree.IterateRange(start, end, false, stopAtFirst)
return m
}
func (b *Bonsai) Checkpoint() 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 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
}

View File

@ -1,9 +1,6 @@
package state
import (
"errors"
"math/rand"
"github.com/tendermint/merkleeyes/iavl"
"github.com/tendermint/tmlibs/merkle"
)
@ -11,31 +8,32 @@ import (
// State represents the app states, separating the commited state (for queries)
// from the working state (for CheckTx and AppendTx)
type State struct {
committed merkle.Tree
deliverTx merkle.Tree
checkTx merkle.Tree
committed *Bonsai
deliverTx *Bonsai
checkTx *Bonsai
persistent bool
}
func NewState(tree merkle.Tree, persistent bool) State {
base := NewBonsai(tree)
return State{
committed: tree,
deliverTx: tree.Copy(),
checkTx: tree.Copy(),
committed: base,
deliverTx: base.Checkpoint().(*Bonsai),
checkTx: base.Checkpoint().(*Bonsai),
persistent: persistent,
}
}
func (s State) Committed() *Bonsai {
return NewBonsai(s.committed)
return s.committed
}
func (s State) Append() *Bonsai {
return NewBonsai(s.deliverTx)
return s.deliverTx
}
func (s State) Check() *Bonsai {
return NewBonsai(s.checkTx)
return s.checkTx
}
// Hash updates the tree
@ -47,7 +45,7 @@ func (s *State) Hash() []byte {
func (s *State) BatchSet(key, value []byte) {
if s.persistent {
// This is in the batch with the Save, but not in the tree
tree, ok := s.deliverTx.(*iavl.IAVLTree)
tree, ok := s.deliverTx.Tree.(*iavl.IAVLTree)
if ok {
tree.BatchSet(key, value)
}
@ -56,106 +54,19 @@ func (s *State) BatchSet(key, value []byte) {
// Commit save persistent nodes to the database and re-copies the trees
func (s *State) Commit() []byte {
err := s.committed.Commit(s.deliverTx)
if err != nil {
panic(err) // ugh, TODO?
}
var hash []byte
if s.persistent {
hash = s.deliverTx.Save()
hash = s.committed.Tree.Save()
} else {
hash = s.deliverTx.Hash()
hash = s.committed.Tree.Hash()
}
s.committed = s.deliverTx
s.deliverTx = s.committed.Copy()
s.checkTx = s.committed.Copy()
s.deliverTx = s.committed.Checkpoint().(*Bonsai)
s.checkTx = s.committed.Checkpoint().(*Bonsai)
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 _ 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 {
_, value, _ := b.Tree.Get(key)
return value
}
// 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) List(start, end []byte, limit int) []Model {
res := []Model{}
stopAtCount := func(key []byte, value []byte) (stop bool) {
m := Model{key, value}
res = append(res, m)
// return false
return limit > 0 && len(res) >= limit
}
b.Tree.IterateRange(start, end, true, stopAtCount)
return res
}
func (b *Bonsai) First(start, end []byte) Model {
var m Model
stopAtFirst := func(key []byte, value []byte) (stop bool) {
m = Model{key, value}
return true
}
b.Tree.IterateRange(start, end, true, stopAtFirst)
return m
}
func (b *Bonsai) Last(start, end []byte) Model {
var m Model
stopAtFirst := func(key []byte, value []byte) (stop bool) {
m = Model{key, value}
return true
}
b.Tree.IterateRange(start, end, false, stopAtFirst)
return m
}
func (b *Bonsai) Checkpoint() 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 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
}