Pull in logic from merkleeyes, get it all working with trees

This commit is contained in:
Ethan Frey 2017-07-25 22:19:31 -04:00
parent 5272ca5831
commit f6e7d4b741
12 changed files with 454 additions and 249 deletions

View File

@ -5,11 +5,10 @@ import (
"strings"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin"
eyes "github.com/tendermint/merkleeyes/client"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/modules/auth"
"github.com/tendermint/basecoin/modules/base"
@ -19,6 +18,7 @@ import (
"github.com/tendermint/basecoin/modules/roles"
"github.com/tendermint/basecoin/stack"
sm "github.com/tendermint/basecoin/state"
"github.com/tendermint/basecoin/state/merkle"
"github.com/tendermint/basecoin/version"
)
@ -30,27 +30,24 @@ const (
// Basecoin - The ABCI application
type Basecoin struct {
eyesCli *eyes.Client
state *sm.State
cacheState *sm.State
handler basecoin.Handler
height uint64
logger log.Logger
info *sm.ChainState
state *merkle.Store
handler basecoin.Handler
height uint64
logger log.Logger
}
var _ abci.Application = &Basecoin{}
// NewBasecoin - create a new instance of the basecoin application
func NewBasecoin(handler basecoin.Handler, eyesCli *eyes.Client, logger log.Logger) *Basecoin {
state := sm.NewState(eyesCli, logger.With("module", "state"))
func NewBasecoin(handler basecoin.Handler, store *merkle.Store, logger log.Logger) *Basecoin {
return &Basecoin{
handler: handler,
eyesCli: eyesCli,
state: state,
cacheState: nil,
height: 0,
logger: logger,
handler: handler,
info: sm.NewChainState(),
state: store,
logger: logger,
}
}
@ -71,21 +68,23 @@ func DefaultHandler(feeDenom string) basecoin.Handler {
nonce.ReplayCheck{},
roles.NewMiddleware(),
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
base.Checkpoint{},
stack.Checkpoint{},
).Use(d)
}
// GetState - XXX For testing, not thread safe!
func (app *Basecoin) GetState() *sm.State {
return app.state.CacheWrap()
// GetChainID returns the currently stored chain
func (app *Basecoin) GetChainID() string {
return app.info.GetChainID(app.state.Committed())
}
// GetState is back... please kill me
func (app *Basecoin) GetState() sm.KVStore {
return app.state.Append()
}
// Info - ABCI
func (app *Basecoin) Info() abci.ResponseInfo {
resp, err := app.eyesCli.InfoSync()
if err != nil {
cmn.PanicCrisis(err)
}
resp := app.state.Info()
app.height = resp.LastBlockHeight
return abci.ResponseInfo{
Data: fmt.Sprintf("Basecoin v%v", version.Version),
@ -98,16 +97,17 @@ func (app *Basecoin) Info() abci.ResponseInfo {
func (app *Basecoin) SetOption(key string, value string) string {
module, key := splitKey(key)
state := app.state.Append()
if module == ModuleNameBase {
if key == ChainKey {
app.state.SetChainID(value)
app.info.SetChainID(state, value)
return "Success"
}
return fmt.Sprintf("Error: unknown base option: %s", key)
}
log, err := app.handler.SetOption(app.logger, app.state, module, key, value)
log, err := app.handler.SetOption(app.logger, state, module, key, value)
if err == nil {
return log
}
@ -122,12 +122,11 @@ func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result {
}
ctx := stack.NewContext(
app.state.GetChainID(),
app.GetChainID(),
app.height,
app.logger.With("call", "delivertx"),
)
fmt.Printf("state: %#v\n", app.state)
res, err := app.handler.DeliverTx(ctx, app.state, tx)
res, err := app.handler.DeliverTx(ctx, app.state.Append(), tx)
if err != nil {
// discard the cache...
@ -144,12 +143,11 @@ func (app *Basecoin) CheckTx(txBytes []byte) abci.Result {
}
ctx := stack.NewContext(
app.state.GetChainID(),
app.GetChainID(),
app.height,
app.logger.With("call", "checktx"),
)
fmt.Printf("state: %#v\n", app.cacheState)
res, err := app.handler.CheckTx(ctx, app.cacheState, tx)
res, err := app.handler.CheckTx(ctx, app.state.Check(), tx)
if err != nil {
return errors.Result(err)
@ -165,24 +163,13 @@ func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQu
return
}
resQuery, err := app.eyesCli.QuerySync(reqQuery)
if err != nil {
resQuery.Log = "Failed to query MerkleEyes: " + err.Error()
resQuery.Code = abci.CodeType_InternalError
return
}
return
return app.state.Query(reqQuery)
}
// Commit - ABCI
func (app *Basecoin) Commit() (res abci.Result) {
// Commit state
res = app.state.Commit()
// Wrap the committed state in cache for CheckTx
app.cacheState = app.state.CacheWrap()
if res.IsErr() {
cmn.PanicSanity("Error getting hash: " + res.Error())
}

View File

@ -2,7 +2,6 @@ package app
import (
"encoding/hex"
"os"
"testing"
"github.com/stretchr/testify/assert"
@ -17,8 +16,8 @@ import (
"github.com/tendermint/basecoin/modules/nonce"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/state"
"github.com/tendermint/basecoin/state/merkle"
wire "github.com/tendermint/go-wire"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
)
@ -82,14 +81,14 @@ func (at *appTest) reset() {
at.acctIn = coin.NewAccountWithKey(coin.Coins{{"mycoin", 7}})
at.acctOut = coin.NewAccountWithKey(coin.Coins{{"mycoin", 7}})
eyesCli := eyes.NewLocalClient("", 0)
// logger := log.TestingLogger().With("module", "app"),
logger := log.NewTMLogger(os.Stdout).With("module", "app")
logger = log.NewTracingLogger(logger)
// Note: switch logger if you want to get more info
logger := log.TestingLogger()
// logger := log.NewTracingLogger(log.NewTMLogger(os.Stdout))
store := merkle.NewStore("", 0, logger.With("module", "store"))
at.app = NewBasecoin(
DefaultHandler("mycoin"),
eyesCli,
logger,
store,
logger.With("module", "app"),
)
res := at.app.SetOption("base/chain_id", at.chainID)
@ -142,17 +141,18 @@ func TestSetOption(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
eyesCli := eyes.NewLocalClient("", 0)
logger := log.TestingLogger()
store := merkle.NewStore("", 0, logger.With("module", "store"))
app := NewBasecoin(
DefaultHandler("atom"),
eyesCli,
log.TestingLogger().With("module", "app"),
store,
logger.With("module", "app"),
)
//testing ChainID
chainID := "testChain"
res := app.SetOption("base/chain_id", chainID)
assert.EqualValues(app.GetState().GetChainID(), chainID)
assert.EqualValues(app.GetChainID(), chainID)
assert.EqualValues(res, "Success")
// make a nice account...
@ -162,7 +162,7 @@ func TestSetOption(t *testing.T) {
require.EqualValues(res, "Success")
// make sure it is set correctly, with some balance
coins, err := getBalance(acct.Actor(), app.state)
coins, err := getBalance(acct.Actor(), app.GetState())
require.Nil(err)
assert.Equal(bal, coins)
@ -189,7 +189,7 @@ func TestSetOption(t *testing.T) {
res = app.SetOption("coin/account", unsortAcc)
require.EqualValues(res, "Success")
coins, err = getAddr(unsortAddr, app.state)
coins, err = getAddr(unsortAddr, app.GetState())
require.Nil(err)
assert.True(coins.IsValid())
assert.Equal(unsortCoins, coins)
@ -213,6 +213,8 @@ func TestTx(t *testing.T) {
//Bad Balance
at.acctIn.Coins = coin.Coins{{"mycoin", 2}}
at.initAccount(at.acctIn)
at.app.Commit()
res, _, _ := at.exec(t, at.getTx(coin.Coins{{"mycoin", 5}}, 1), true)
assert.True(res.IsErr(), "ExecTx/Bad CheckTx: Expected error return from ExecTx, returned: %v", res)
res, diffIn, diffOut := at.exec(t, at.getTx(coin.Coins{{"mycoin", 5}}, 1), false)

View File

@ -8,19 +8,20 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
eyescli "github.com/tendermint/merkleeyes/client"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/state/merkle"
)
const genesisFilepath = "./testdata/genesis.json"
const genesisAcctFilepath = "./testdata/genesis2.json"
func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) {
eyesCli := eyescli.NewLocalClient("", 0)
app := NewBasecoin(DefaultHandler("mycoin"), eyesCli, log.TestingLogger())
logger := log.TestingLogger()
store := merkle.NewStore("", 0, logger)
app := NewBasecoin(DefaultHandler("mycoin"), store, logger)
err := app.LoadGenesis("./testdata/genesis3.json")
require.Nil(t, err, "%+v", err)
}
@ -28,18 +29,19 @@ func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) {
func TestLoadGenesis(t *testing.T) {
assert, require := assert.New(t), require.New(t)
eyesCli := eyescli.NewLocalClient("", 0)
app := NewBasecoin(DefaultHandler("mycoin"), eyesCli, log.TestingLogger())
logger := log.TestingLogger()
store := merkle.NewStore("", 0, logger)
app := NewBasecoin(DefaultHandler("mycoin"), store, logger)
err := app.LoadGenesis(genesisFilepath)
require.Nil(err, "%+v", err)
// check the chain id
assert.Equal("foo_bar_chain", app.GetState().GetChainID())
assert.Equal("foo_bar_chain", app.GetChainID())
// and check the account info - previously calculated values
addr, _ := hex.DecodeString("eb98e0688217cfdeb70eddf4b33cdcc37fc53197")
coins, err := getAddr(addr, app.state)
coins, err := getAddr(addr, app.GetState())
require.Nil(err)
assert.True(coins.IsPositive())
@ -57,13 +59,14 @@ func TestLoadGenesis(t *testing.T) {
func TestLoadGenesisAccountAddress(t *testing.T) {
assert, require := assert.New(t), require.New(t)
eyesCli := eyescli.NewLocalClient("", 0)
app := NewBasecoin(DefaultHandler("mycoin"), eyesCli, log.TestingLogger())
logger := log.TestingLogger()
store := merkle.NewStore("", 0, logger)
app := NewBasecoin(DefaultHandler("mycoin"), store, logger)
err := app.LoadGenesis(genesisAcctFilepath)
require.Nil(err, "%+v", err)
// check the chain id
assert.Equal("addr_accounts_chain", app.GetState().GetChainID())
assert.Equal("addr_accounts_chain", app.GetChainID())
// make sure the accounts were set properly
cases := []struct {
@ -86,7 +89,7 @@ func TestLoadGenesisAccountAddress(t *testing.T) {
for i, tc := range cases {
addr, err := hex.DecodeString(tc.addr)
require.Nil(err, tc.addr)
coins, err := getAddr(addr, app.state)
coins, err := getAddr(addr, app.GetState())
require.Nil(err, "%+v", err)
if !tc.exists {
assert.True(coins.IsZero(), "%d", i)

View File

@ -11,8 +11,6 @@ import (
"github.com/tendermint/abci/server"
"github.com/tendermint/basecoin"
eyesApp "github.com/tendermint/merkleeyes/app"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/cli"
cmn "github.com/tendermint/tmlibs/common"
@ -22,6 +20,7 @@ import (
"github.com/tendermint/tendermint/types"
"github.com/tendermint/basecoin/app"
"github.com/tendermint/basecoin/state/merkle"
)
// StartCmd - command to start running the basecoin node!
@ -37,7 +36,6 @@ const EyesCacheSize = 10000
//nolint
const (
FlagAddress = "address"
FlagEyes = "eyes"
FlagWithoutTendermint = "without-tendermint"
)
@ -50,7 +48,6 @@ var (
func init() {
flags := StartCmd.Flags()
flags.String(FlagAddress, "tcp://0.0.0.0:46658", "Listen address")
flags.String(FlagEyes, "local", "MerkleEyes address, or 'local' for embedded")
flags.Bool(FlagWithoutTendermint, false, "Only run basecoin abci app, assume external tendermint process")
// add all standard 'tendermint node' flags
tcmd.AddNodeFlags(StartCmd)
@ -58,27 +55,19 @@ func init() {
func startCmd(cmd *cobra.Command, args []string) error {
rootDir := viper.GetString(cli.HomeFlag)
meyes := viper.GetString(FlagEyes)
// Connect to MerkleEyes
var eyesCli *eyes.Client
if meyes == "local" {
eyesApp.SetLogger(logger.With("module", "merkleeyes"))
eyesCli = eyes.NewLocalClient(path.Join(rootDir, "data", "merkleeyes.db"), EyesCacheSize)
} else {
var err error
eyesCli, err = eyes.NewClient(meyes)
if err != nil {
return errors.Errorf("Error connecting to MerkleEyes: %v\n", err)
}
}
store := merkle.NewStore(
path.Join(rootDir, "data", "merkleeyes.db"),
EyesCacheSize,
logger.With("module", "store"),
)
// Create Basecoin app
basecoinApp := app.NewBasecoin(Handler, eyesCli, logger.With("module", "app"))
basecoinApp := app.NewBasecoin(Handler, store, logger.With("module", "app"))
// if chain_id has not been set yet, load the genesis.
// else, assume it's been loaded
if basecoinApp.GetState().GetChainID() == "" {
if basecoinApp.GetChainID() == "" {
// If genesis file exists, set key-value options
genesisFile := path.Join(rootDir, "genesis.json")
if _, err := os.Stat(genesisFile); err == nil {
@ -91,7 +80,7 @@ func startCmd(cmd *cobra.Command, args []string) error {
}
}
chainID := basecoinApp.GetState().GetChainID()
chainID := basecoinApp.GetChainID()
if viper.GetBool(FlagWithoutTendermint) {
logger.Info("Starting Basecoin without Tendermint", "chain_id", chainID)
// run just the abci app/server

View File

@ -104,7 +104,7 @@ func NewHandler(feeDenom string) basecoin.Handler {
base.Chain{},
nonce.ReplayCheck{},
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
base.Checkpoint{},
stack.Checkpoint{},
).Use(dispatcher)
}

View File

@ -12,8 +12,8 @@ import (
"github.com/tendermint/basecoin/modules/base"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/modules/nonce"
"github.com/tendermint/basecoin/state/merkle"
"github.com/tendermint/go-wire"
eyescli "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
)
@ -21,17 +21,16 @@ func TestCounterPlugin(t *testing.T) {
assert := assert.New(t)
// Basecoin initialization
eyesCli := eyescli.NewLocalClient("", 0)
chainID := "test_chain_id"
logger := log.TestingLogger()
// logger := log.NewTracingLogger(log.NewTMLogger(os.Stdout))
logger := log.TestingLogger().With("module", "app")
// logger := log.NewTMLogger(os.Stdout).With("module", "app")
logger = log.NewTracingLogger(logger)
store := merkle.NewStore("", 0, logger.With("module", "store"))
h := NewHandler("gold")
bcApp := app.NewBasecoin(
h,
eyesCli,
logger,
store,
logger.With("module", "app"),
)
bcApp.SetOption("base/chain_id", chainID)

View File

@ -1,10 +1,7 @@
package base
package stack
import (
"fmt"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/state"
)
@ -15,7 +12,7 @@ const (
// Checkpoint isolates all data store below this
type Checkpoint struct {
stack.PassOption
PassOption
}
// Name of the module - fulfills Middleware interface
@ -23,11 +20,11 @@ func (Checkpoint) Name() string {
return NameCheckpoint
}
var _ stack.Middleware = Chain{}
var _ Middleware = Checkpoint{}
// CheckTx reverts all data changes if there was an error
func (c Checkpoint) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
ps := state.NewKVCache(store)
ps := state.NewKVCache(unwrap(store))
res, err = next.CheckTx(ctx, ps, tx)
if err == nil {
ps.Sync()
@ -37,13 +34,10 @@ func (c Checkpoint) CheckTx(ctx basecoin.Context, store state.KVStore, tx baseco
// DeliverTx reverts all data changes if there was an error
func (c Checkpoint) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
ps := state.NewKVCache(store)
ps := state.NewKVCache(unwrap(store))
res, err = next.DeliverTx(ctx, ps, tx)
if err == nil {
fmt.Println("sync")
ps.Sync()
} else {
fmt.Println("reject")
}
return res, err
}

View File

@ -31,6 +31,14 @@ func stateSpace(store state.KVStore, app string) state.KVStore {
return PrefixedStore(app, store)
}
func unwrap(store state.KVStore) state.KVStore {
// unwrap one-level if wrapped
if pstore, ok := store.(prefixStore); ok {
store = pstore.store
}
return store
}
// PrefixedStore allows one to create an isolated state-space for a given
// app prefix, but it cannot easily be unwrapped
//

83
state/merkle/state.go Normal file
View File

@ -0,0 +1,83 @@
package merkle
import (
"github.com/tendermint/merkleeyes/iavl"
"github.com/tendermint/tmlibs/merkle"
)
// 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
persistent bool
}
func NewState(tree merkle.Tree, persistent bool) State {
return State{
committed: tree,
deliverTx: tree.Copy(),
checkTx: tree.Copy(),
persistent: persistent,
}
}
func (s State) Committed() Bonsai {
return Bonsai{s.committed}
}
func (s State) Append() Bonsai {
return Bonsai{s.deliverTx}
}
func (s State) Check() Bonsai {
return Bonsai{s.checkTx}
}
// Hash updates the tree
func (s *State) Hash() []byte {
return s.deliverTx.Hash()
}
// BatchSet is used for some weird magic in storing the new height
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)
if ok {
tree.BatchSet(key, value)
}
}
}
// Commit save persistent nodes to the database and re-copies the trees
func (s *State) Commit() []byte {
var hash []byte
if s.persistent {
hash = s.deliverTx.Save()
} else {
hash = s.deliverTx.Hash()
}
s.committed = s.deliverTx
s.deliverTx = s.committed.Copy()
s.checkTx = s.committed.Copy()
return hash
}
// Bonsai is a deformed tree forced to fit in a small pot
type Bonsai struct {
merkle.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)
}

196
state/merkle/store.go Normal file
View File

@ -0,0 +1,196 @@
package merkle
import (
"bytes"
"fmt"
"path"
"path/filepath"
"strings"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/go-wire"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/merkleeyes/iavl"
)
// Store contains the merkle tree, and all info to handle abci requests
type Store struct {
State
height uint64
hash []byte
persisted bool
logger log.Logger
}
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 {
Hash []byte
Height uint64
}
// NewStore initializes an in-memory IAVLTree, or attempts to load a persistant
// tree from disk
func NewStore(dbName string, cacheSize int, logger log.Logger) *Store {
// start at 1 so the height returned by query is for the
// next block, ie. the one that includes the AppHash for our current state
initialHeight := uint64(1)
// Non-persistent case
if dbName == "" {
tree := iavl.NewIAVLTree(
0,
nil,
)
return &Store{
State: NewState(tree, false),
height: initialHeight,
logger: logger,
}
}
// Expand the path fully
dbPath, err := filepath.Abs(dbName)
if err != nil {
panic(fmt.Sprintf("Invalid Database Name: %s", dbName))
}
// Some external calls accidently add a ".db", which is now removed
dbPath = strings.TrimSuffix(dbPath, path.Ext(dbPath))
// Split the database name into it's components (dir, name)
dir := path.Dir(dbPath)
name := path.Base(dbPath)
// Make sure the path exists
empty, _ := cmn.IsDirEmpty(dbPath + ".db")
// Open database called "dir/name.db", if it doesn't exist it will be created
db := dbm.NewDB(name, dbm.LevelDBBackendStr, dir)
tree := iavl.NewIAVLTree(cacheSize, db)
var eyesState MerkleState
if empty {
logger.Info("no existing db, creating new db")
eyesState = MerkleState{
Hash: tree.Save(),
Height: initialHeight,
}
db.Set(stateKey, wire.BinaryBytes(eyesState))
} else {
logger.Info("loading existing db")
eyesStateBytes := db.Get(stateKey)
err = wire.ReadBinaryBytes(eyesStateBytes, &eyesState)
if err != nil {
logger.Error("error reading MerkleEyesState", "err", err)
panic(err)
}
tree.Load(eyesState.Hash)
}
return &Store{
State: NewState(tree, true),
height: eyesState.Height,
hash: eyesState.Hash,
persisted: true,
logger: logger,
}
}
// CloseDB closes the database
// func (s *Store) CloseDB() {
// if s.db != nil {
// s.db.Close()
// }
// }
// 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))
return abci.ResponseInfo{
Data: cmn.Fmt("size:%v", s.State.Committed().Size()),
LastBlockHeight: s.height - 1,
LastBlockAppHash: s.hash,
}
}
// Commit implements abci.Application
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.State.BatchSet(stateKey, wire.BinaryBytes(MerkleState{
Hash: s.hash,
Height: s.height,
}))
hash := s.State.Commit()
if !bytes.Equal(hash, s.hash) {
panic("AppHash is incorrect")
}
if s.State.Committed().Size() == 0 {
return abci.NewResultOK(nil, "Empty hash for empty tree")
}
return abci.NewResultOK(s.hash, "")
}
// 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
resQuery.Code = abci.CodeType_InternalError
resQuery.Log = "merkleeyes only supports queries on latest commit"
return
}
// set the query response height to current
resQuery.Height = s.height
switch reqQuery.Path {
case "/store", "/key": // Get by key
key := reqQuery.Data // Data holds the key bytes
resQuery.Key = key
if reqQuery.Prove {
value, proof, exists := tree.Proof(key)
if !exists {
resQuery.Log = "Key not found"
}
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)
}
return
}

View File

@ -1,84 +1,26 @@
package state
import (
abci "github.com/tendermint/abci/types"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
)
// CONTRACT: State should be quick to copy.
// See CacheWrap().
type State struct {
chainID string
store KVStore
readCache map[string][]byte // optional, for caching writes to store
writeCache *KVCache // optional, for caching writes w/o writing to store
logger log.Logger
// ChainState maintains general information for the chain
type ChainState struct {
chainID string
}
func NewState(store KVStore, l log.Logger) *State {
return &State{
chainID: "",
store: store,
readCache: make(map[string][]byte),
writeCache: nil,
logger: l,
}
// NewChainState creates a blank state
func NewChainState() *ChainState {
return &ChainState{}
}
func (s *State) SetChainID(chainID string) {
// SetChainID stores the chain id in the store
func (s *ChainState) SetChainID(store KVStore, chainID string) {
s.chainID = chainID
s.store.Set([]byte("base/chain_id"), []byte(chainID))
store.Set([]byte("base/chain_id"), []byte(chainID))
}
func (s *State) GetChainID() string {
// GetChainID gets the chain id from the cache or the store
func (s *ChainState) GetChainID(store KVStore) string {
if s.chainID != "" {
return s.chainID
}
s.chainID = string(s.store.Get([]byte("base/chain_id")))
s.chainID = string(store.Get([]byte("base/chain_id")))
return s.chainID
}
func (s *State) Get(key []byte) (value []byte) {
if s.readCache != nil { //if not a cachewrap
value, ok := s.readCache[string(key)]
if ok {
return value
}
}
return s.store.Get(key)
}
func (s *State) Set(key []byte, value []byte) {
if s.readCache != nil { //if not a cachewrap
s.readCache[string(key)] = value
}
s.store.Set(key, value)
}
func (s *State) CacheWrap() *State {
cache := NewKVCache(s)
return &State{
chainID: s.chainID,
store: cache,
readCache: nil,
writeCache: cache,
logger: s.logger,
}
}
// NOTE: errors if s is not from CacheWrap()
func (s *State) CacheSync() {
s.writeCache.Sync()
}
func (s *State) Commit() abci.Result {
switch s.store.(type) {
case *eyes.Client:
s.readCache = make(map[string][]byte)
return s.store.(*eyes.Client).CommitSync()
default:
return abci.NewError(abci.CodeType_InternalError, "can only use Commit if store is merkleeyes")
}
}

View File

@ -1,86 +1,88 @@
package state
import (
"bytes"
"testing"
// TODO: something similar in the merkle package...
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
// import (
// "bytes"
// "testing"
"github.com/stretchr/testify/assert"
)
// eyes "github.com/tendermint/merkleeyes/client"
// "github.com/tendermint/tmlibs/log"
func TestState(t *testing.T) {
assert := assert.New(t)
// "github.com/stretchr/testify/assert"
// )
//States and Stores for tests
store := NewMemKVStore()
state := NewState(store, log.TestingLogger())
cache := state.CacheWrap()
eyesCli := eyes.NewLocalClient("", 0)
// func TestState(t *testing.T) {
// assert := assert.New(t)
//reset the store/state/cache
reset := func() {
store = NewMemKVStore()
state = NewState(store, log.TestingLogger())
cache = state.CacheWrap()
}
// //States and Stores for tests
// store := NewMemKVStore()
// state := NewState(store, log.TestingLogger())
// cache := state.CacheWrap()
// eyesCli := eyes.NewLocalClient("", 0)
//set the state to using the eyesCli instead of MemKVStore
useEyesCli := func() {
state = NewState(eyesCli, log.TestingLogger())
cache = state.CacheWrap()
}
// //reset the store/state/cache
// reset := func() {
// store = NewMemKVStore()
// state = NewState(store, log.TestingLogger())
// cache = state.CacheWrap()
// }
//key value pairs to be tested within the system
keyvalue := []struct {
key string
value string
}{
{"foo", "snake"},
{"bar", "mouse"},
}
// //set the state to using the eyesCli instead of MemKVStore
// useEyesCli := func() {
// state = NewState(eyesCli, log.TestingLogger())
// cache = state.CacheWrap()
// }
//set the kvc to have all the key value pairs
setRecords := func(kv KVStore) {
for _, n := range keyvalue {
kv.Set([]byte(n.key), []byte(n.value))
}
}
// //key value pairs to be tested within the system
// keyvalue := []struct {
// key string
// value string
// }{
// {"foo", "snake"},
// {"bar", "mouse"},
// }
//store has all the key value pairs
storeHasAll := func(kv KVStore) bool {
for _, n := range keyvalue {
if !bytes.Equal(kv.Get([]byte(n.key)), []byte(n.value)) {
return false
}
}
return true
}
// //set the kvc to have all the key value pairs
// setRecords := func(kv KVStore) {
// for _, n := range keyvalue {
// kv.Set([]byte(n.key), []byte(n.value))
// }
// }
//test chainID
state.SetChainID("testchain")
assert.Equal(state.GetChainID(), "testchain", "ChainID is improperly stored")
// //store has all the key value pairs
// storeHasAll := func(kv KVStore) bool {
// for _, n := range keyvalue {
// if !bytes.Equal(kv.Get([]byte(n.key)), []byte(n.value)) {
// return false
// }
// }
// return true
// }
//test basic retrieve
setRecords(state)
assert.True(storeHasAll(state), "state doesn't retrieve after Set")
// //test chainID
// state.SetChainID("testchain")
// assert.Equal(state.GetChainID(), "testchain", "ChainID is improperly stored")
//Test CacheWrap with local mem store
reset()
setRecords(cache)
assert.False(storeHasAll(store), "store retrieving before CacheSync")
cache.CacheSync()
assert.True(storeHasAll(store), "store doesn't retrieve after CacheSync")
// //test basic retrieve
// setRecords(state)
// assert.True(storeHasAll(state), "state doesn't retrieve after Set")
//Test Commit on state with non-merkle store
assert.True(state.Commit().IsErr(), "Commit shouldn't work with non-merkle store")
// //Test CacheWrap with local mem store
// reset()
// setRecords(cache)
// assert.False(storeHasAll(store), "store retrieving before CacheSync")
// cache.CacheSync()
// assert.True(storeHasAll(store), "store doesn't retrieve after CacheSync")
//Test CacheWrap with merkleeyes client store
useEyesCli()
setRecords(cache)
assert.False(storeHasAll(eyesCli), "eyesCli retrieving before Commit")
cache.CacheSync()
assert.True(state.Commit().IsOK(), "Bad Commit")
assert.True(storeHasAll(eyesCli), "eyesCli doesn't retrieve after Commit")
}
// //Test Commit on state with non-merkle store
// assert.True(state.Commit().IsErr(), "Commit shouldn't work with non-merkle store")
// //Test CacheWrap with merkleeyes client store
// useEyesCli()
// setRecords(cache)
// assert.False(storeHasAll(eyesCli), "eyesCli retrieving before Commit")
// cache.CacheSync()
// assert.True(state.Commit().IsOK(), "Bad Commit")
// assert.True(storeHasAll(eyesCli), "eyesCli doesn't retrieve after Commit")
// }