Pull in logic from merkleeyes, get it all working with trees
This commit is contained in:
parent
5272ca5831
commit
f6e7d4b741
77
app/app.go
77
app/app.go
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
//
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
// }
|
||||
|
|
Loading…
Reference in New Issue