Merge pull request #185 from tendermint/feature/cleanup-state

Cleanup storage
This commit is contained in:
Ethan Frey 2017-07-27 15:38:15 -04:00 committed by GitHub
commit 761fb0d0c6
54 changed files with 1561 additions and 609 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"
@ -30,27 +29,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 *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 *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,
}
}
@ -68,23 +64,27 @@ func DefaultHandler(feeDenom string) basecoin.Handler {
stack.Recovery{},
auth.Signatures{},
base.Chain{},
stack.Checkpoint{OnCheck: true},
nonce.ReplayCheck{},
roles.NewMiddleware(),
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
stack.Checkpoint{OnDeliver: true},
).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.SimpleDB {
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),
@ -97,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
}
@ -120,21 +121,16 @@ func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result {
return errors.Result(err)
}
// TODO: can we abstract this setup and commit logic??
cache := app.state.CacheWrap()
ctx := stack.NewContext(
app.state.GetChainID(),
app.GetChainID(),
app.height,
app.logger.With("call", "delivertx"),
)
res, err := app.handler.DeliverTx(ctx, cache, tx)
res, err := app.handler.DeliverTx(ctx, app.state.Append(), tx)
if err != nil {
// discard the cache...
return errors.Result(err)
}
// commit the cache and return result
cache.CacheSync()
return res.ToABCI()
}
@ -145,22 +141,16 @@ func (app *Basecoin) CheckTx(txBytes []byte) abci.Result {
return errors.Result(err)
}
// we also need to discard error changes, so we don't increment checktx
// sequence on error, but not delivertx
cache := app.cacheState.CacheWrap()
ctx := stack.NewContext(
app.state.GetChainID(),
app.GetChainID(),
app.height,
app.logger.With("call", "checktx"),
)
// checktx generally shouldn't touch the state, but we don't care
// here on the framework level, since the cacheState is thrown away next block
res, err := app.handler.CheckTx(ctx, cache, tx)
res, err := app.handler.CheckTx(ctx, app.state.Check(), tx)
if err != nil {
return errors.Result(err)
}
cache.CacheSync()
return res.ToABCI()
}
@ -172,24 +162,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"
@ -18,7 +17,6 @@ import (
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/state"
wire "github.com/tendermint/go-wire"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
)
@ -82,14 +80,16 @@ 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, err := NewStore("", 0, logger.With("module", "store"))
require.Nil(at.t, err, "%+v", err)
at.app = NewBasecoin(
DefaultHandler("mycoin"),
eyesCli,
logger,
store,
logger.With("module", "app"),
)
res := at.app.SetOption("base/chain_id", at.chainID)
@ -102,13 +102,13 @@ func (at *appTest) reset() {
require.True(at.t, resabci.IsOK(), resabci)
}
func getBalance(key basecoin.Actor, store state.KVStore) (coin.Coins, error) {
func getBalance(key basecoin.Actor, store state.SimpleDB) (coin.Coins, error) {
cspace := stack.PrefixedStore(coin.NameCoin, store)
acct, err := coin.GetAccount(cspace, key)
return acct.Coins, err
}
func getAddr(addr []byte, state state.KVStore) (coin.Coins, error) {
func getAddr(addr []byte, state state.SimpleDB) (coin.Coins, error) {
actor := auth.SigPerm(addr)
return getBalance(actor, state)
}
@ -142,17 +142,20 @@ func TestSetOption(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
eyesCli := eyes.NewLocalClient("", 0)
logger := log.TestingLogger()
store, err := NewStore("", 0, logger.With("module", "store"))
require.Nil(err, "%+v", err)
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 +165,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 +192,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 +216,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,7 +8,6 @@ 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"
@ -19,27 +18,32 @@ 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())
err := app.LoadGenesis("./testdata/genesis3.json")
logger := log.TestingLogger()
store, err := NewStore("", 0, logger)
require.Nil(t, err, "%+v", err)
app := NewBasecoin(DefaultHandler("mycoin"), store, logger)
err = app.LoadGenesis("./testdata/genesis3.json")
require.Nil(t, err, "%+v", err)
}
func TestLoadGenesis(t *testing.T) {
assert, require := assert.New(t), require.New(t)
eyesCli := eyescli.NewLocalClient("", 0)
app := NewBasecoin(DefaultHandler("mycoin"), eyesCli, log.TestingLogger())
err := app.LoadGenesis(genesisFilepath)
logger := log.TestingLogger()
store, err := NewStore("", 0, logger)
require.Nil(err, "%+v", err)
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 +61,16 @@ 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())
err := app.LoadGenesis(genesisAcctFilepath)
logger := log.TestingLogger()
store, err := NewStore("", 0, logger)
require.Nil(err, "%+v", err)
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 +93,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)

189
app/store.go Normal file
View File

@ -0,0 +1,189 @@
package app
import (
"bytes"
"fmt"
"path"
"path/filepath"
"strings"
"github.com/pkg/errors"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/go-wire"
"github.com/tendermint/merkleeyes/iavl"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin/state"
)
// Store contains the merkle tree, and all info to handle abci requests
type Store struct {
state.State
height uint64
hash []byte
persisted bool
logger log.Logger
}
var stateKey = []byte("merkle:state") // Database key for merkle tree save value db values
// ChainState contains the latest Merkle root hash and the number of times `Commit` has been called
type ChainState 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, error) {
// 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,
)
store := &Store{
State: state.NewState(tree, false),
height: initialHeight,
logger: logger,
}
return store, nil
}
// Expand the path fully
dbPath, err := filepath.Abs(dbName)
if err != nil {
return nil, errors.Wrap(err, "Invalid Database Name")
}
// 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 chainState ChainState
if empty {
logger.Info("no existing db, creating new db")
chainState = ChainState{
Hash: tree.Save(),
Height: initialHeight,
}
db.Set(stateKey, wire.BinaryBytes(chainState))
} else {
logger.Info("loading existing db")
eyesStateBytes := db.Get(stateKey)
err = wire.ReadBinaryBytes(eyesStateBytes, &chainState)
if err != nil {
return nil, errors.Wrap(err, "Reading MerkleEyesState")
}
tree.Load(chainState.Hash)
}
res := &Store{
State: state.NewState(tree, true),
height: chainState.Height,
hash: chainState.Hash,
persisted: true,
logger: logger,
}
return res, nil
}
// 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 {
var err error
s.height++
s.hash, err = s.State.Hash()
if err != nil {
return abci.NewError(abci.CodeType_InternalError, err.Error())
}
s.logger.Debug("Commit synced",
"height", s.height,
"hash", fmt.Sprintf("%X", s.hash))
s.State.BatchSet(stateKey, wire.BinaryBytes(ChainState{
Hash: s.hash,
Height: s.height,
}))
hash, err := s.State.Commit()
if err != nil {
return abci.NewError(abci.CodeType_InternalError, err.Error())
}
if !bytes.Equal(hash, s.hash) {
return abci.NewError(abci.CodeType_InternalError, "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 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
tree := s.State.Committed()
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
} else {
value := tree.Get(key)
resQuery.Value = value
}
default:
resQuery.Code = abci.CodeType_UnknownRequest
resQuery.Log = cmn.Fmt("Unexpected Query path: %v", reqQuery.Path)
}
return
}

View File

@ -6,8 +6,6 @@ import (
"testing"
wire "github.com/tendermint/go-wire"
eyesApp "github.com/tendermint/merkleeyes/app"
eyes "github.com/tendermint/merkleeyes/client"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
@ -56,18 +54,22 @@ func NewBenchApp(h basecoin.Handler, chainID string, n int,
// logger = log.NewTracingLogger(logger)
// TODO: disk writing
var eyesCli *eyes.Client
var store *app.Store
var err error
if persist {
tmpDir, _ := ioutil.TempDir("", "bc-app-benchmark")
eyesCli = eyes.NewLocalClient(tmpDir, 500)
store, err = app.NewStore(tmpDir, 500, logger)
} else {
eyesCli = eyes.NewLocalClient("", 0)
store, err = app.NewStore("", 0, logger)
}
if err != nil {
panic(err)
}
eyesApp.SetLogger(logger.With("module", "merkle"))
app := app.NewBasecoin(
h,
eyesCli,
store,
logger.With("module", "app"),
)
res := app.SetOption("base/chain_id", chainID)

View File

@ -0,0 +1,19 @@
BenchmarkMakeTx-4 2000 603153 ns/op
BenchmarkSimpleTransfer/100-10-nofee-memdb-4 5000 313154 ns/op
BenchmarkSimpleTransfer/100-10-fee-memdb-4 5000 366534 ns/op
BenchmarkSimpleTransfer/100-200-nofee-memdb-4 5000 296381 ns/op
BenchmarkSimpleTransfer/100-200-fee-memdb-4 5000 350973 ns/op
BenchmarkSimpleTransfer/10000-10-nofee-memdb-4 5000 351425 ns/op
BenchmarkSimpleTransfer/10000-10-fee-memdb-4 3000 410855 ns/op
BenchmarkSimpleTransfer/10000-200-nofee-memdb-4 5000 344839 ns/op
BenchmarkSimpleTransfer/10000-200-fee-memdb-4 5000 394080 ns/op
BenchmarkSimpleTransfer/100-10-nofee-persist-4 3000 433890 ns/op
BenchmarkSimpleTransfer/100-10-fee-persist-4 3000 496133 ns/op
BenchmarkSimpleTransfer/100-200-nofee-persist-4 5000 310174 ns/op
BenchmarkSimpleTransfer/100-200-fee-persist-4 5000 366868 ns/op
BenchmarkSimpleTransfer/10000-10-nofee-persist-4 2000 815755 ns/op
BenchmarkSimpleTransfer/10000-10-fee-persist-4 2000 874532 ns/op
BenchmarkSimpleTransfer/10000-200-nofee-persist-4 5000 567349 ns/op
BenchmarkSimpleTransfer/10000-200-fee-persist-4 5000 621833 ns/op
PASS
ok github.com/tendermint/basecoin/benchmarks 93.047s

View File

@ -0,0 +1,19 @@
BenchmarkMakeTx-4 2000 648379 ns/op
BenchmarkSimpleTransfer/100-10-nofee-memdb-4 5000 356487 ns/op
BenchmarkSimpleTransfer/100-10-fee-memdb-4 5000 413435 ns/op
BenchmarkSimpleTransfer/100-200-nofee-memdb-4 5000 321859 ns/op
BenchmarkSimpleTransfer/100-200-fee-memdb-4 5000 393578 ns/op
BenchmarkSimpleTransfer/10000-10-nofee-memdb-4 5000 379129 ns/op
BenchmarkSimpleTransfer/10000-10-fee-memdb-4 3000 480334 ns/op
BenchmarkSimpleTransfer/10000-200-nofee-memdb-4 5000 384398 ns/op
BenchmarkSimpleTransfer/10000-200-fee-memdb-4 3000 443481 ns/op
BenchmarkSimpleTransfer/100-10-nofee-persist-4 3000 498460 ns/op
BenchmarkSimpleTransfer/100-10-fee-persist-4 3000 559034 ns/op
BenchmarkSimpleTransfer/100-200-nofee-persist-4 5000 314090 ns/op
BenchmarkSimpleTransfer/100-200-fee-persist-4 5000 397457 ns/op
BenchmarkSimpleTransfer/10000-10-nofee-persist-4 2000 845872 ns/op
BenchmarkSimpleTransfer/10000-10-fee-persist-4 2000 929205 ns/op
BenchmarkSimpleTransfer/10000-200-nofee-persist-4 5000 596601 ns/op
BenchmarkSimpleTransfer/10000-200-fee-persist-4 5000 667093 ns/op
PASS
ok github.com/tendermint/basecoin/benchmarks 97.097s

View File

@ -0,0 +1,19 @@
BenchmarkMakeTx-4 2000 660064 ns/op
BenchmarkSimpleTransfer/100-10-nofee-memdb-4 5000 338378 ns/op
BenchmarkSimpleTransfer/100-10-fee-memdb-4 5000 380171 ns/op
BenchmarkSimpleTransfer/100-200-nofee-memdb-4 5000 306365 ns/op
BenchmarkSimpleTransfer/100-200-fee-memdb-4 5000 359344 ns/op
BenchmarkSimpleTransfer/10000-10-nofee-memdb-4 5000 366057 ns/op
BenchmarkSimpleTransfer/10000-10-fee-memdb-4 3000 433549 ns/op
BenchmarkSimpleTransfer/10000-200-nofee-memdb-4 5000 351662 ns/op
BenchmarkSimpleTransfer/10000-200-fee-memdb-4 3000 421573 ns/op
BenchmarkSimpleTransfer/100-10-nofee-persist-4 3000 479848 ns/op
BenchmarkSimpleTransfer/100-10-fee-persist-4 3000 544164 ns/op
BenchmarkSimpleTransfer/100-200-nofee-persist-4 5000 327999 ns/op
BenchmarkSimpleTransfer/100-200-fee-persist-4 5000 385751 ns/op
BenchmarkSimpleTransfer/10000-10-nofee-persist-4 2000 852128 ns/op
BenchmarkSimpleTransfer/10000-10-fee-persist-4 2000 1055130 ns/op
BenchmarkSimpleTransfer/10000-200-nofee-persist-4 5000 642872 ns/op
BenchmarkSimpleTransfer/10000-200-fee-persist-4 3000 686337 ns/op
PASS
ok github.com/tendermint/basecoin/benchmarks 91.717s

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"
@ -37,7 +35,6 @@ const EyesCacheSize = 10000
//nolint
const (
FlagAddress = "address"
FlagEyes = "eyes"
FlagWithoutTendermint = "without-tendermint"
)
@ -50,7 +47,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 +54,22 @@ 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, err := app.NewStore(
path.Join(rootDir, "data", "merkleeyes.db"),
EyesCacheSize,
logger.With("module", "store"),
)
if err != nil {
return err
}
// 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 +82,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

@ -102,8 +102,10 @@ func NewHandler(feeDenom string) basecoin.Handler {
stack.Recovery{},
auth.Signatures{},
base.Chain{},
stack.Checkpoint{OnCheck: true},
nonce.ReplayCheck{},
fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank),
stack.Checkpoint{OnDeliver: true},
).Use(dispatcher)
}
@ -123,13 +125,13 @@ func (Handler) Name() string {
func (Handler) AssertDispatcher() {}
// CheckTx checks if the tx is properly structured
func (h Handler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, _ basecoin.Checker) (res basecoin.Result, err error) {
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, _ basecoin.Checker) (res basecoin.Result, err error) {
_, err = checkTx(ctx, tx)
return
}
// DeliverTx executes the tx if valid
func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, dispatch basecoin.Deliver) (res basecoin.Result, err error) {
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, dispatch basecoin.Deliver) (res basecoin.Result, err error) {
ctr, err := checkTx(ctx, tx)
if err != nil {
return res, err
@ -201,7 +203,7 @@ func StateKey() []byte {
}
// LoadState - retrieve the counter state from the store
func LoadState(store state.KVStore) (state State, err error) {
func LoadState(store state.SimpleDB) (state State, err error) {
bytes := store.Get(StateKey())
if len(bytes) > 0 {
err = wire.ReadBinaryBytes(bytes, &state)
@ -213,7 +215,7 @@ func LoadState(store state.KVStore) (state State, err error) {
}
// SaveState - save the counter state to the provided store
func SaveState(store state.KVStore, state State) error {
func SaveState(store state.SimpleDB, state State) error {
bytes := wire.BinaryBytes(state)
store.Set(StateKey(), bytes)
return nil

View File

@ -1,37 +1,40 @@
package counter
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/go-wire"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/app"
"github.com/tendermint/basecoin/modules/auth"
"github.com/tendermint/basecoin/modules/base"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/modules/nonce"
"github.com/tendermint/go-wire"
eyescli "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
)
func TestCounterPlugin(t *testing.T) {
assert := assert.New(t)
require := require.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, err := app.NewStore("", 0, logger.With("module", "store"))
require.Nil(err, "%+v", err)
h := NewHandler("gold")
bcApp := app.NewBasecoin(
NewHandler("gold"),
eyesCli,
logger,
h,
store,
logger.With("module", "app"),
)
bcApp.SetOption("base/chain_id", chainID)
@ -39,7 +42,7 @@ func TestCounterPlugin(t *testing.T) {
bal := coin.Coins{{"", 1000}, {"gold", 1000}}
acct := coin.NewAccountWithKey(bal)
log := bcApp.SetOption("coin/account", acct.MakeOption())
require.Equal(t, "Success", log)
require.Equal("Success", log)
// Deliver a CounterTx
DeliverCounterTx := func(valid bool, counterFee coin.Coins, sequence uint32) abci.Result {
@ -60,11 +63,15 @@ func TestCounterPlugin(t *testing.T) {
res = DeliverCounterTx(false, nil, 2)
assert.True(res.IsErr(), res.String())
// Test an invalid send, with supported fee
res = DeliverCounterTx(true, coin.Coins{{"gold", 100}}, 2)
// Test an invalid sequence
res = DeliverCounterTx(true, nil, 2)
assert.True(res.IsErr(), res.String())
// Test an valid send, with supported fee
res = DeliverCounterTx(true, coin.Coins{{"gold", 100}}, 3)
assert.True(res.IsOK(), res.String())
// Test unsupported fee
res = DeliverCounterTx(true, coin.Coins{{"silver", 100}}, 3)
res = DeliverCounterTx(true, coin.Coins{{"silver", 100}}, 4)
assert.True(res.IsErr(), res.String())
}

11
glide.lock generated
View File

@ -1,5 +1,5 @@
hash: 8c438edb7d269da439141e62f3e0c931fa9efaee54b13ce1e7330dc99179fddd
updated: 2017-07-20T15:39:36.659717024+02:00
hash: 45eed61138603d4d03518ea822068cf32b45d0a219bb7f3b836e52129f2a3a2b
updated: 2017-07-26T19:44:39.753066441-04:00
imports:
- name: github.com/bgentry/speakeasy
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
@ -133,16 +133,15 @@ imports:
- data
- data/base58
- name: github.com/tendermint/light-client
version: d63415027075bc5d74a98a718393b59b5c4279a5
version: 1c53d04dcc65c2fd15526152ed0651af10a09982
subpackages:
- certifiers
- certifiers/client
- certifiers/files
- proofs
- name: github.com/tendermint/merkleeyes
version: 102aaf5a8ffda1846413fb22805a94def2045b9f
version: 0310013053953eef80def3619aeb1e3a3254f452
subpackages:
- app
- client
- iavl
- name: github.com/tendermint/tendermint
@ -172,7 +171,7 @@ imports:
- types
- version
- name: github.com/tendermint/tmlibs
version: efb56aaea7517220bb3f42ff87b8004d554a17ff
version: 2f6f3e6aa70bb19b70a6e73210273fa127041070
subpackages:
- autofile
- cli

View File

@ -22,14 +22,14 @@ import:
subpackages:
- data
- package: github.com/tendermint/light-client
version: unstable
version: 1c53d04dcc65c2fd15526152ed0651af10a09982
subpackages:
- proofs
- certifiers
- certifiers/client
- certifiers/files
- package: github.com/tendermint/merkleeyes
version: develop
version: unstable
subpackages:
- client
- iavl

View File

@ -15,9 +15,9 @@ type Handler interface {
SetOptioner
Named
// TODO: flesh these out as well
// InitChain(store state.KVStore, vals []*abci.Validator)
// BeginBlock(store state.KVStore, hash []byte, header *abci.Header)
// EndBlock(store state.KVStore, height uint64) abci.ResponseEndBlock
// InitChain(store state.SimpleDB, vals []*abci.Validator)
// BeginBlock(store state.SimpleDB, hash []byte, header *abci.Header)
// EndBlock(store state.SimpleDB, height uint64) abci.ResponseEndBlock
}
type Named interface {
@ -25,35 +25,35 @@ type Named interface {
}
type Checker interface {
CheckTx(ctx Context, store state.KVStore, tx Tx) (Result, error)
CheckTx(ctx Context, store state.SimpleDB, tx Tx) (Result, error)
}
// CheckerFunc (like http.HandlerFunc) is a shortcut for making wrapers
type CheckerFunc func(Context, state.KVStore, Tx) (Result, error)
type CheckerFunc func(Context, state.SimpleDB, Tx) (Result, error)
func (c CheckerFunc) CheckTx(ctx Context, store state.KVStore, tx Tx) (Result, error) {
func (c CheckerFunc) CheckTx(ctx Context, store state.SimpleDB, tx Tx) (Result, error) {
return c(ctx, store, tx)
}
type Deliver interface {
DeliverTx(ctx Context, store state.KVStore, tx Tx) (Result, error)
DeliverTx(ctx Context, store state.SimpleDB, tx Tx) (Result, error)
}
// DeliverFunc (like http.HandlerFunc) is a shortcut for making wrapers
type DeliverFunc func(Context, state.KVStore, Tx) (Result, error)
type DeliverFunc func(Context, state.SimpleDB, Tx) (Result, error)
func (c DeliverFunc) DeliverTx(ctx Context, store state.KVStore, tx Tx) (Result, error) {
func (c DeliverFunc) DeliverTx(ctx Context, store state.SimpleDB, tx Tx) (Result, error) {
return c(ctx, store, tx)
}
type SetOptioner interface {
SetOption(l log.Logger, store state.KVStore, module, key, value string) (string, error)
SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error)
}
// SetOptionFunc (like http.HandlerFunc) is a shortcut for making wrapers
type SetOptionFunc func(log.Logger, state.KVStore, string, string, string) (string, error)
type SetOptionFunc func(log.Logger, state.SimpleDB, string, string, string) (string, error)
func (c SetOptionFunc) SetOption(l log.Logger, store state.KVStore, module, key, value string) (string, error) {
func (c SetOptionFunc) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
return c(l, store, module, key, value)
}
@ -75,14 +75,14 @@ func (r Result) ToABCI() abci.Result {
// holders
type NopCheck struct{}
func (_ NopCheck) CheckTx(Context, state.KVStore, Tx) (r Result, e error) { return }
func (_ NopCheck) CheckTx(Context, state.SimpleDB, Tx) (r Result, e error) { return }
type NopDeliver struct{}
func (_ NopDeliver) DeliverTx(Context, state.KVStore, Tx) (r Result, e error) { return }
func (_ NopDeliver) DeliverTx(Context, state.SimpleDB, Tx) (r Result, e error) { return }
type NopOption struct{}
func (_ NopOption) SetOption(log.Logger, state.KVStore, string, string, string) (string, error) {
func (_ NopOption) SetOption(log.Logger, state.SimpleDB, string, string, string) (string, error) {
return "", nil
}

View File

@ -39,7 +39,7 @@ type Signable interface {
}
// CheckTx verifies the signatures are correct - fulfills Middlware interface
func (Signatures) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
func (Signatures) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
sigs, tnext, err := getSigners(tx)
if err != nil {
return res, err
@ -49,7 +49,7 @@ func (Signatures) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin
}
// DeliverTx verifies the signatures are correct - fulfills Middlware interface
func (Signatures) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
func (Signatures) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
sigs, tnext, err := getSigners(tx)
if err != nil {
return res, err

View File

@ -24,7 +24,7 @@ func (Chain) Name() string {
var _ stack.Middleware = Chain{}
// CheckTx makes sure we are on the proper chain - fulfills Middlware interface
func (c Chain) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
func (c Chain) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
stx, err := c.checkChainTx(ctx.ChainID(), ctx.BlockHeight(), tx)
if err != nil {
return res, err
@ -33,7 +33,7 @@ func (c Chain) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx
}
// DeliverTx makes sure we are on the proper chain - fulfills Middlware interface
func (c Chain) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
func (c Chain) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
stx, err := c.checkChainTx(ctx.ChainID(), ctx.BlockHeight(), tx)
if err != nil {
return res, err

View File

@ -26,7 +26,7 @@ func (Logger) Name() string {
var _ stack.Middleware = Logger{}
// CheckTx logs time and result - fulfills Middlware interface
func (Logger) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
func (Logger) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
start := time.Now()
res, err = next.CheckTx(ctx, store, tx)
delta := time.Now().Sub(start)
@ -41,7 +41,7 @@ func (Logger) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx,
}
// DeliverTx logs time and result - fulfills Middlware interface
func (Logger) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
func (Logger) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
start := time.Now()
res, err = next.DeliverTx(ctx, store, tx)
delta := time.Now().Sub(start)
@ -56,7 +56,7 @@ func (Logger) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.T
}
// SetOption logs time and result - fulfills Middlware interface
func (Logger) SetOption(l log.Logger, store state.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error) {
func (Logger) SetOption(l log.Logger, store state.SimpleDB, module, key, value string, next basecoin.SetOptioner) (string, error) {
start := time.Now()
res, err := next.SetOption(l, store, module, key, value)
delta := time.Now().Sub(start)

View File

@ -29,7 +29,7 @@ func (Multiplexer) Name() string {
var _ stack.Middleware = Multiplexer{}
// CheckTx splits the input tx and checks them all - fulfills Middlware interface
func (Multiplexer) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
func (Multiplexer) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
if mtx, ok := tx.Unwrap().(*MultiTx); ok {
return runAll(ctx, store, mtx.Txs, next.CheckTx)
}
@ -37,14 +37,14 @@ func (Multiplexer) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoi
}
// DeliverTx splits the input tx and checks them all - fulfills Middlware interface
func (Multiplexer) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
func (Multiplexer) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
if mtx, ok := tx.Unwrap().(*MultiTx); ok {
return runAll(ctx, store, mtx.Txs, next.DeliverTx)
}
return next.DeliverTx(ctx, store, tx)
}
func runAll(ctx basecoin.Context, store state.KVStore, txs []basecoin.Tx, next basecoin.CheckerFunc) (res basecoin.Result, err error) {
func runAll(ctx basecoin.Context, store state.SimpleDB, txs []basecoin.Tx, next basecoin.CheckerFunc) (res basecoin.Result, err error) {
// store all results, unless anything errors
rs := make([]basecoin.Result, len(txs))
for i, stx := range txs {

View File

@ -33,7 +33,7 @@ func accountQueryCmd(cmd *cobra.Command, args []string) error {
acc := coin.Account{}
proof, err := proofcmd.GetAndParseAppProof(key, &acc)
if lc.IsNoDataErr(err) {
return errors.Errorf("Account bytes are empty for address %X ", addr)
return errors.Errorf("Account bytes are empty for address %s ", addr)
} else if err != nil {
return err
}

View File

@ -58,16 +58,8 @@ func readSendTxFlags() (tx basecoin.Tx, err error) {
}
// craft the inputs and outputs
ins := []coin.TxInput{{
Address: fromAddr,
Coins: amountCoins,
}}
outs := []coin.TxOutput{{
Address: toAddr,
Coins: amountCoins,
}}
return coin.NewSendTx(ins, outs), nil
tx = coin.NewSendOneTx(fromAddr, toAddr, amountCoins)
return
}
func readFromAddr() (basecoin.Actor, error) {

View File

@ -29,7 +29,7 @@ func (Handler) Name() string {
}
// CheckTx checks if there is enough money in the account
func (h Handler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
send, err := checkTx(ctx, tx)
if err != nil {
return res, err
@ -48,7 +48,7 @@ func (h Handler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.
}
// DeliverTx moves the money
func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
send, err := checkTx(ctx, tx)
if err != nil {
return res, err
@ -75,7 +75,7 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoi
}
// SetOption - sets the genesis account balance
func (h Handler) SetOption(l log.Logger, store state.KVStore, module, key, value string) (log string, err error) {
func (h Handler) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (log string, err error) {
if module != NameCoin {
return "", errors.ErrUnknownModule(module)
}

View File

@ -11,7 +11,7 @@ import (
)
// GetAccount - Get account from store and address
func GetAccount(store state.KVStore, addr basecoin.Actor) (Account, error) {
func GetAccount(store state.SimpleDB, addr basecoin.Actor) (Account, error) {
acct, err := loadAccount(store, addr.Bytes())
// for empty accounts, don't return an error, but rather an empty account
@ -22,13 +22,13 @@ func GetAccount(store state.KVStore, addr basecoin.Actor) (Account, error) {
}
// CheckCoins makes sure there are funds, but doesn't change anything
func CheckCoins(store state.KVStore, addr basecoin.Actor, coins Coins) (Coins, error) {
func CheckCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (Coins, error) {
acct, err := updateCoins(store, addr, coins)
return acct.Coins, err
}
// ChangeCoins changes the money, returns error if it would be negative
func ChangeCoins(store state.KVStore, addr basecoin.Actor, coins Coins) (Coins, error) {
func ChangeCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (Coins, error) {
acct, err := updateCoins(store, addr, coins)
if err != nil {
return acct.Coins, err
@ -41,7 +41,7 @@ func ChangeCoins(store state.KVStore, addr basecoin.Actor, coins Coins) (Coins,
// updateCoins will load the account, make all checks, and return the updated account.
//
// it doesn't save anything, that is up to you to decide (Check/Change Coins)
func updateCoins(store state.KVStore, addr basecoin.Actor, coins Coins) (acct Account, err error) {
func updateCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (acct Account, err error) {
acct, err = loadAccount(store, addr.Bytes())
// we can increase an empty account...
if IsNoAccountErr(err) && coins.IsPositive() {
@ -66,7 +66,7 @@ type Account struct {
Coins Coins `json:"coins"`
}
func loadAccount(store state.KVStore, key []byte) (acct Account, err error) {
func loadAccount(store state.SimpleDB, key []byte) (acct Account, err error) {
// fmt.Printf("load: %X\n", key)
data := store.Get(key)
if len(data) == 0 {
@ -80,7 +80,7 @@ func loadAccount(store state.KVStore, key []byte) (acct Account, err error) {
return acct, nil
}
func storeAccount(store state.KVStore, key []byte, acct Account) error {
func storeAccount(store state.SimpleDB, key []byte, acct Account) error {
// fmt.Printf("store: %X\n", key)
bin := wire.BinaryBytes(acct)
store.Set(key, bin)

View File

@ -45,16 +45,16 @@ func (SimpleFeeMiddleware) Name() string {
}
// CheckTx - check the transaction
func (h SimpleFeeMiddleware) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
func (h SimpleFeeMiddleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
return h.doTx(ctx, store, tx, next.CheckTx)
}
// DeliverTx - send the fee handler transaction
func (h SimpleFeeMiddleware) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
func (h SimpleFeeMiddleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
return h.doTx(ctx, store, tx, next.DeliverTx)
}
func (h SimpleFeeMiddleware) doTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.CheckerFunc) (res basecoin.Result, err error) {
func (h SimpleFeeMiddleware) doTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.CheckerFunc) (res basecoin.Result, err error) {
feeTx, ok := tx.Unwrap().(Fee)
if !ok {
// the fee wrapper is not required if there is no minimum

View File

@ -24,7 +24,7 @@ func (ReplayCheck) Name() string {
var _ stack.Middleware = ReplayCheck{}
// CheckTx verifies tx is not being replayed - fulfills Middlware interface
func (r ReplayCheck) CheckTx(ctx basecoin.Context, store state.KVStore,
func (r ReplayCheck) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
stx, err := r.checkIncrementNonceTx(ctx, store, tx)
@ -38,7 +38,7 @@ func (r ReplayCheck) CheckTx(ctx basecoin.Context, store state.KVStore,
// DeliverTx verifies tx is not being replayed - fulfills Middlware interface
// NOTE It is okay to modify the sequence before running the wrapped TX because if the
// wrapped Tx fails, the state changes are not applied
func (r ReplayCheck) DeliverTx(ctx basecoin.Context, store state.KVStore,
func (r ReplayCheck) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
stx, err := r.checkIncrementNonceTx(ctx, store, tx)
@ -50,7 +50,7 @@ func (r ReplayCheck) DeliverTx(ctx basecoin.Context, store state.KVStore,
}
// checkNonceTx varifies the nonce sequence, an increment sequence number
func (r ReplayCheck) checkIncrementNonceTx(ctx basecoin.Context, store state.KVStore,
func (r ReplayCheck) checkIncrementNonceTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx) (basecoin.Tx, error) {
// make sure it is a the nonce Tx (Tx from this package)

View File

@ -9,7 +9,7 @@ import (
"github.com/tendermint/basecoin/state"
)
func getSeq(store state.KVStore, key []byte) (seq uint32, err error) {
func getSeq(store state.SimpleDB, key []byte) (seq uint32, err error) {
data := store.Get(key)
if len(data) == 0 {
//if the key is not stored, its a new key with a sequence of zero!
@ -23,7 +23,7 @@ func getSeq(store state.KVStore, key []byte) (seq uint32, err error) {
return seq, nil
}
func setSeq(store state.KVStore, key []byte, seq uint32) error {
func setSeq(store state.SimpleDB, key []byte, seq uint32) error {
bin := wire.BinaryBytes(seq)
store.Set(key, bin)
return nil // real stores can return error...

View File

@ -62,7 +62,7 @@ func (n Tx) ValidateBasic() error {
// and further increment the sequence number
// NOTE It is okay to modify the sequence before running the wrapped TX because if the
// wrapped Tx fails, the state changes are not applied
func (n Tx) CheckIncrementSeq(ctx basecoin.Context, store state.KVStore) error {
func (n Tx) CheckIncrementSeq(ctx basecoin.Context, store state.SimpleDB) error {
seqKey := n.getSeqKey()

View File

@ -27,7 +27,7 @@ func (Handler) Name() string {
}
// CheckTx verifies if the transaction is properly formated
func (h Handler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
var cr CreateRoleTx
cr, err = checkTx(ctx, tx)
if err != nil {
@ -40,7 +40,7 @@ func (h Handler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.
// DeliverTx tries to create a new role.
//
// Returns an error if the role already exists
func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
create, err := checkTx(ctx, tx)
if err != nil {
return res, err

View File

@ -27,7 +27,7 @@ func (Middleware) Name() string {
// CheckTx tries to assume the named role if requested.
// If no role is requested, do nothing.
// If insufficient authority to assume the role, return error.
func (m Middleware) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
func (m Middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
// if this is not an AssumeRoleTx, then continue
assume, ok := tx.Unwrap().(AssumeRoleTx)
if !ok { // this also breaks the recursion below
@ -46,7 +46,7 @@ func (m Middleware) CheckTx(ctx basecoin.Context, store state.KVStore, tx baseco
// DeliverTx tries to assume the named role if requested.
// If no role is requested, do nothing.
// If insufficient authority to assume the role, return error.
func (m Middleware) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
func (m Middleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
// if this is not an AssumeRoleTx, then continue
assume, ok := tx.Unwrap().(AssumeRoleTx)
if !ok { // this also breaks the recursion below
@ -62,7 +62,7 @@ func (m Middleware) DeliverTx(ctx basecoin.Context, store state.KVStore, tx base
return m.DeliverTx(ctx, store, assume.Tx, next)
}
func assumeRole(ctx basecoin.Context, store state.KVStore, assume AssumeRoleTx) (basecoin.Context, error) {
func assumeRole(ctx basecoin.Context, store state.SimpleDB, assume AssumeRoleTx) (basecoin.Context, error) {
err := assume.ValidateBasic()
if err != nil {
return nil, err

View File

@ -17,7 +17,7 @@ import (
// shortcut for the lazy
type ba []basecoin.Actor
func createRole(app basecoin.Handler, store state.KVStore,
func createRole(app basecoin.Handler, store state.SimpleDB,
name []byte, min uint32, sigs ...basecoin.Actor) (basecoin.Actor, error) {
tx := roles.NewCreateRoleTx(name, min, sigs)
ctx := stack.MockContext("foo", 1)

View File

@ -56,7 +56,7 @@ func (r Role) IsAuthorized(ctx basecoin.Context) bool {
return false
}
func loadRole(store state.KVStore, key []byte) (role Role, err error) {
func loadRole(store state.SimpleDB, key []byte) (role Role, err error) {
data := store.Get(key)
if len(data) == 0 {
return role, ErrNoRole()
@ -69,7 +69,7 @@ func loadRole(store state.KVStore, key []byte) (role Role, err error) {
return role, nil
}
func checkNoRole(store state.KVStore, key []byte) error {
func checkNoRole(store state.SimpleDB, key []byte) error {
if _, err := loadRole(store, key); !IsNoRoleErr(err) {
return ErrRoleExists()
}
@ -77,7 +77,7 @@ func checkNoRole(store state.KVStore, key []byte) error {
}
// we only have create here, no update, since we don't allow update yet
func createRole(store state.KVStore, key []byte, role Role) error {
func createRole(store state.SimpleDB, key []byte, role Role) error {
if err := checkNoRole(store, key); err != nil {
return err
}

View File

@ -72,7 +72,7 @@ package ibc
// // GetSequenceNumber gets the sequence number for packets being sent from the src chain to the dst chain.
// // The sequence number counts how many packets have been sent.
// // The next packet must include the latest sequence number.
// func GetSequenceNumber(store state.KVStore, src, dst string) uint64 {
// func GetSequenceNumber(store state.SimpleDB, src, dst string) uint64 {
// sequenceKey := toKey(_IBC, _EGRESS, src, dst)
// seqBytes := store.Get(sequenceKey)
// if seqBytes == nil {
@ -86,14 +86,14 @@ package ibc
// }
// // SetSequenceNumber sets the sequence number for packets being sent from the src chain to the dst chain
// func SetSequenceNumber(store state.KVStore, src, dst string, seq uint64) {
// func SetSequenceNumber(store state.SimpleDB, src, dst string, seq uint64) {
// sequenceKey := toKey(_IBC, _EGRESS, src, dst)
// store.Set(sequenceKey, []byte(strconv.FormatUint(seq, 10)))
// }
// // SaveNewIBCPacket creates an IBC packet with the given payload from the src chain to the dst chain
// // using the correct sequence number. It also increments the sequence number by 1
// func SaveNewIBCPacket(state state.KVStore, src, dst string, payload Payload) {
// func SaveNewIBCPacket(state state.SimpleDB, src, dst string, payload Payload) {
// // fetch sequence number and increment by 1
// seq := GetSequenceNumber(state, src, dst)
// SetSequenceNumber(state, src, dst, seq+1)
@ -104,7 +104,7 @@ package ibc
// save(state, packetKey, packet)
// }
// func GetIBCPacket(state state.KVStore, src, dst string, seq uint64) (Packet, error) {
// func GetIBCPacket(state state.SimpleDB, src, dst string, seq uint64) (Packet, error) {
// packetKey := toKey(_IBC, _EGRESS, src, dst, cmn.Fmt("%v", seq))
// packetBytes := state.Get(packetKey)
@ -251,11 +251,11 @@ package ibc
// return &IBCPlugin{}
// }
// func (ibc *IBCPlugin) SetOption(store state.KVStore, key string, value string) (log string) {
// func (ibc *IBCPlugin) SetOption(store state.SimpleDB, key string, value string) (log string) {
// return ""
// }
// func (ibc *IBCPlugin) RunTx(store state.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) {
// func (ibc *IBCPlugin) RunTx(store state.SimpleDB, ctx types.CallContext, txBytes []byte) (res abci.Result) {
// // Decode tx
// var tx IBCTx
// err := wire.ReadBinaryBytes(txBytes, &tx)
@ -295,7 +295,7 @@ package ibc
// }
// type IBCStateMachine struct {
// store state.KVStore
// store state.SimpleDB
// ctx types.CallContext
// res abci.Result
// }
@ -499,13 +499,13 @@ package ibc
// return
// }
// func (ibc *IBCPlugin) InitChain(store state.KVStore, vals []*abci.Validator) {
// func (ibc *IBCPlugin) InitChain(store state.SimpleDB, vals []*abci.Validator) {
// }
// func (cp *IBCPlugin) BeginBlock(store state.KVStore, hash []byte, header *abci.Header) {
// func (cp *IBCPlugin) BeginBlock(store state.SimpleDB, hash []byte, header *abci.Header) {
// }
// func (cp *IBCPlugin) EndBlock(store state.KVStore, height uint64) (res abci.ResponseEndBlock) {
// func (cp *IBCPlugin) EndBlock(store state.SimpleDB, height uint64) (res abci.ResponseEndBlock) {
// return
// }
@ -513,7 +513,7 @@ package ibc
// // TODO: move to utils
// // Returns true if exists, false if nil.
// func exists(store state.KVStore, key []byte) (exists bool) {
// func exists(store state.SimpleDB, key []byte) (exists bool) {
// value := store.Get(key)
// return len(value) > 0
// }
@ -521,7 +521,7 @@ package ibc
// // Load bytes from store by reading value for key and read into ptr.
// // Returns true if exists, false if nil.
// // Returns err if decoding error.
// func load(store state.KVStore, key []byte, ptr interface{}) (exists bool, err error) {
// func load(store state.SimpleDB, key []byte, ptr interface{}) (exists bool, err error) {
// value := store.Get(key)
// if len(value) > 0 {
// err = wire.ReadBinaryBytes(value, ptr)
@ -537,7 +537,7 @@ package ibc
// }
// // Save bytes to store by writing obj's go-wire binary bytes.
// func save(store state.KVStore, key []byte, obj interface{}) {
// func save(store state.SimpleDB, key []byte, obj interface{}) {
// store.Set(key, wire.BinaryBytes(obj))
// }

51
stack/checkpoint.go Normal file
View File

@ -0,0 +1,51 @@
package stack
import (
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/state"
)
//nolint
const (
NameCheckpoint = "check"
)
// Checkpoint isolates all data store below this
type Checkpoint struct {
OnCheck bool
OnDeliver bool
PassOption
}
// Name of the module - fulfills Middleware interface
func (Checkpoint) Name() string {
return NameCheckpoint
}
var _ Middleware = Checkpoint{}
// CheckTx reverts all data changes if there was an error
func (c Checkpoint) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
if !c.OnCheck {
return next.CheckTx(ctx, store, tx)
}
ps := store.Checkpoint()
res, err = next.CheckTx(ctx, ps, tx)
if err == nil {
err = store.Commit(ps)
}
return res, err
}
// DeliverTx reverts all data changes if there was an error
func (c Checkpoint) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
if !c.OnDeliver {
return next.DeliverTx(ctx, store, tx)
}
ps := store.Checkpoint()
res, err = next.DeliverTx(ctx, ps, tx)
if err == nil {
err = store.Commit(ps)
}
return res, err
}

113
stack/checkpoint_test.go Normal file
View File

@ -0,0 +1,113 @@
package stack
import (
"errors"
"testing"
"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)
good := writerHand{"foo", []byte{1, 2}, []byte("bar")}
bad := FailHandler{Err: errors.New("no go")}
app := New(
Checkpoint{OnCheck: true},
writerMid{"bing", []byte{1, 2}, []byte("bang")},
Checkpoint{OnDeliver: true},
).Use(
NewDispatcher(
WrapHandler(good),
WrapHandler(bad),
))
basecoin.TxMapper.RegisterImplementation(RawTx{}, good.Name(), byte(80))
mid := state.Model{
Key: []byte{'b', 'i', 'n', 'g', 0, 1, 2},
Value: []byte("bang"),
}
end := state.Model{
Key: []byte{'f', 'o', 'o', 0, 1, 2},
Value: []byte("bar"),
}
cases := []struct {
// tx to send down the line
tx basecoin.Tx
// expect no error?
valid bool
// models to check afterwards
toGetCheck []state.Model
// models to check afterwards
toGetDeliver []state.Model
}{
// everything writen on success
{
tx: NewRawTx([]byte{45, 67}),
valid: true,
toGetCheck: []state.Model{mid, end},
toGetDeliver: []state.Model{mid, end},
},
// mostly reverted on failure
{
tx: NewFailTx(),
valid: false,
toGetCheck: []state.Model{},
toGetDeliver: []state.Model{mid},
},
}
for i, tc := range cases {
ctx := NewContext("foo", 100, log.NewNopLogger())
store := makeState()
_, err := app.CheckTx(ctx, store, tc.tx)
if tc.valid {
require.Nil(err, "%+v", err)
} else {
require.NotNil(err)
}
for _, m := range tc.toGetCheck {
val := store.Get(m.Key)
assert.EqualValues(m.Value, val, "%d: %#v", i, m)
}
store = makeState()
_, err = app.DeliverTx(ctx, store, tc.tx)
if tc.valid {
require.Nil(err, "%+v", err)
} else {
require.NotNil(err)
}
for _, m := range tc.toGetDeliver {
val := store.Get(m.Key)
assert.EqualValues(m.Value, val, "%d: %#v", i, m)
}
}
}

View File

@ -76,7 +76,7 @@ func withApp(ctx basecoin.Context, app string) basecoin.Context {
}
func secureCheck(h basecoin.Checker, parent basecoin.Context) basecoin.Checker {
next := func(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
next := func(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
if !parent.IsParent(ctx) {
return res, errors.New("Passing in non-child Context")
}
@ -86,7 +86,7 @@ func secureCheck(h basecoin.Checker, parent basecoin.Context) basecoin.Checker {
}
func secureDeliver(h basecoin.Deliver, parent basecoin.Context) basecoin.Deliver {
next := func(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
next := func(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
if !parent.IsParent(ctx) {
return res, errors.New("Passing in non-child Context")
}

View File

@ -64,7 +64,7 @@ func (d *Dispatcher) Name() string {
// Tries to find a registered module (Dispatchable) based on the name of the tx.
// The tx name (as registered with go-data) should be in the form `<module name>/XXXX`,
// where `module name` must match the name of a dispatchable and XXX can be any string.
func (d *Dispatcher) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (d *Dispatcher) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
r, err := d.lookupTx(tx)
if err != nil {
return res, err
@ -85,7 +85,7 @@ func (d *Dispatcher) CheckTx(ctx basecoin.Context, store state.KVStore, tx basec
// Tries to find a registered module (Dispatchable) based on the name of the tx.
// The tx name (as registered with go-data) should be in the form `<module name>/XXXX`,
// where `module name` must match the name of a dispatchable and XXX can be any string.
func (d *Dispatcher) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (d *Dispatcher) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
r, err := d.lookupTx(tx)
if err != nil {
return res, err
@ -105,7 +105,7 @@ func (d *Dispatcher) DeliverTx(ctx basecoin.Context, store state.KVStore, tx bas
//
// Tries to find a registered module (Dispatchable) based on the
// module name from SetOption of the tx.
func (d *Dispatcher) SetOption(l log.Logger, store state.KVStore, module, key, value string) (string, error) {
func (d *Dispatcher) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
r, err := d.lookupModule(module)
if err != nil {
return "", err

View File

@ -20,9 +20,11 @@ const (
const (
ByteRawTx = 0xF0
ByteCheckTx = 0xF1
ByteFailTx = 0xF2
TypeRawTx = NameOK + "/raw" // this will just say a-ok to RawTx
TypeCheckTx = NameCheck + "/tx"
TypeFailTx = NameFail + "/tx"
rawMaxSize = 2000 * 1000
)
@ -30,7 +32,8 @@ const (
func init() {
basecoin.TxMapper.
RegisterImplementation(RawTx{}, TypeRawTx, ByteRawTx).
RegisterImplementation(CheckTx{}, TypeCheckTx, ByteCheckTx)
RegisterImplementation(CheckTx{}, TypeCheckTx, ByteCheckTx).
RegisterImplementation(FailTx{}, TypeFailTx, ByteFailTx)
}
// RawTx just contains bytes that can be hex-ified
@ -72,6 +75,22 @@ func (CheckTx) ValidateBasic() error {
return nil
}
// FailTx just gets routed to filaure
type FailTx struct{}
var _ basecoin.TxInner = FailTx{}
func NewFailTx() basecoin.Tx {
return FailTx{}.Wrap()
}
func (f FailTx) Wrap() basecoin.Tx {
return basecoin.Tx{f}
}
func (r FailTx) ValidateBasic() error {
return nil
}
// OKHandler just used to return okay to everything
type OKHandler struct {
Log string
@ -86,12 +105,12 @@ func (OKHandler) Name() string {
}
// CheckTx always returns an empty success tx
func (ok OKHandler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (ok OKHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
return basecoin.Result{Log: ok.Log}, nil
}
// DeliverTx always returns an empty success tx
func (ok OKHandler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (ok OKHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
return basecoin.Result{Log: ok.Log}, nil
}
@ -108,13 +127,13 @@ func (EchoHandler) Name() string {
}
// CheckTx always returns an empty success tx
func (EchoHandler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (EchoHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
data, err := data.ToWire(tx)
return basecoin.Result{Data: data}, err
}
// DeliverTx always returns an empty success tx
func (EchoHandler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (EchoHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
data, err := data.ToWire(tx)
return basecoin.Result{Data: data}, err
}
@ -133,12 +152,12 @@ func (FailHandler) Name() string {
}
// CheckTx always returns the given error
func (f FailHandler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (f FailHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
return res, errors.Wrap(f.Err)
}
// DeliverTx always returns the given error
func (f FailHandler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (f FailHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
return res, errors.Wrap(f.Err)
}
@ -157,7 +176,7 @@ func (PanicHandler) Name() string {
}
// CheckTx always panics
func (p PanicHandler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (p PanicHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
if p.Err != nil {
panic(p.Err)
}
@ -165,7 +184,7 @@ func (p PanicHandler) CheckTx(ctx basecoin.Context, store state.KVStore, tx base
}
// DeliverTx always panics
func (p PanicHandler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (p PanicHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
if p.Err != nil {
panic(p.Err)
}
@ -185,7 +204,7 @@ func (CheckHandler) Name() string {
}
// CheckTx verifies the permissions
func (c CheckHandler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (c CheckHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
check, ok := tx.Unwrap().(CheckTx)
if !ok {
return res, errors.ErrUnknownTxType(tx)
@ -200,7 +219,7 @@ func (c CheckHandler) CheckTx(ctx basecoin.Context, store state.KVStore, tx base
}
// DeliverTx verifies the permissions
func (c CheckHandler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (c CheckHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
// until something changes, just do the same as check
return c.CheckTx(ctx, store, tx)
}

View File

@ -25,14 +25,14 @@ func (_ CheckMiddleware) Name() string {
return NameCheck
}
func (p CheckMiddleware) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
func (p CheckMiddleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
if !ctx.HasPermission(p.Required) {
return res, errors.ErrUnauthorized()
}
return next.CheckTx(ctx, store, tx)
}
func (p CheckMiddleware) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
func (p CheckMiddleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
if !ctx.HasPermission(p.Required) {
return res, errors.ErrUnauthorized()
}
@ -51,12 +51,12 @@ func (_ GrantMiddleware) Name() string {
return NameGrant
}
func (g GrantMiddleware) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
func (g GrantMiddleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
ctx = ctx.WithPermissions(g.Auth)
return next.CheckTx(ctx, store, tx)
}
func (g GrantMiddleware) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
func (g GrantMiddleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
ctx = ctx.WithPermissions(g.Auth)
return next.DeliverTx(ctx, store, tx)
}

View File

@ -20,40 +20,40 @@ type Middleware interface {
}
type CheckerMiddle interface {
CheckTx(ctx basecoin.Context, store state.KVStore,
CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Checker) (basecoin.Result, error)
}
type CheckerMiddleFunc func(basecoin.Context, state.KVStore,
type CheckerMiddleFunc func(basecoin.Context, state.SimpleDB,
basecoin.Tx, basecoin.Checker) (basecoin.Result, error)
func (c CheckerMiddleFunc) CheckTx(ctx basecoin.Context, store state.KVStore,
func (c CheckerMiddleFunc) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Checker) (basecoin.Result, error) {
return c(ctx, store, tx, next)
}
type DeliverMiddle interface {
DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx,
DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx,
next basecoin.Deliver) (basecoin.Result, error)
}
type DeliverMiddleFunc func(basecoin.Context, state.KVStore,
type DeliverMiddleFunc func(basecoin.Context, state.SimpleDB,
basecoin.Tx, basecoin.Deliver) (basecoin.Result, error)
func (d DeliverMiddleFunc) DeliverTx(ctx basecoin.Context, store state.KVStore,
func (d DeliverMiddleFunc) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Deliver) (basecoin.Result, error) {
return d(ctx, store, tx, next)
}
type SetOptionMiddle interface {
SetOption(l log.Logger, store state.KVStore, module,
SetOption(l log.Logger, store state.SimpleDB, module,
key, value string, next basecoin.SetOptioner) (string, error)
}
type SetOptionMiddleFunc func(log.Logger, state.KVStore,
type SetOptionMiddleFunc func(log.Logger, state.SimpleDB,
string, string, string, basecoin.SetOptioner) (string, error)
func (c SetOptionMiddleFunc) SetOption(l log.Logger, store state.KVStore,
func (c SetOptionMiddleFunc) SetOption(l log.Logger, store state.SimpleDB,
module, key, value string, next basecoin.SetOptioner) (string, error) {
return c(l, store, module, key, value, next)
}
@ -61,28 +61,28 @@ func (c SetOptionMiddleFunc) SetOption(l log.Logger, store state.KVStore,
// holders
type PassCheck struct{}
func (_ PassCheck) CheckTx(ctx basecoin.Context, store state.KVStore,
func (_ PassCheck) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Checker) (basecoin.Result, error) {
return next.CheckTx(ctx, store, tx)
}
type PassDeliver struct{}
func (_ PassDeliver) DeliverTx(ctx basecoin.Context, store state.KVStore,
func (_ PassDeliver) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Deliver) (basecoin.Result, error) {
return next.DeliverTx(ctx, store, tx)
}
type PassOption struct{}
func (_ PassOption) SetOption(l log.Logger, store state.KVStore, module,
func (_ PassOption) SetOption(l log.Logger, store state.SimpleDB, module,
key, value string, next basecoin.SetOptioner) (string, error) {
return next.SetOption(l, store, module, key, value)
}
type NopOption struct{}
func (_ NopOption) SetOption(l log.Logger, store state.KVStore, module,
func (_ NopOption) SetOption(l log.Logger, store state.SimpleDB, module,
key, value string, next basecoin.SetOptioner) (string, error) {
return "", nil
}
@ -112,17 +112,17 @@ func (w wrapped) Name() string {
return w.h.Name()
}
func (w wrapped) CheckTx(ctx basecoin.Context, store state.KVStore,
func (w wrapped) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, _ basecoin.Checker) (basecoin.Result, error) {
return w.h.CheckTx(ctx, store, tx)
}
func (w wrapped) DeliverTx(ctx basecoin.Context, store state.KVStore,
func (w wrapped) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, _ basecoin.Deliver) (basecoin.Result, error) {
return w.h.DeliverTx(ctx, store, tx)
}
func (w wrapped) SetOption(l log.Logger, store state.KVStore,
func (w wrapped) SetOption(l log.Logger, store state.SimpleDB,
module, key, value string, _ basecoin.SetOptioner) (string, error) {
return w.h.SetOption(l, store, module, key, value)
}

View File

@ -22,7 +22,7 @@ func (m *middleware) Name() string {
}
// CheckTx always returns an empty success tx
func (m *middleware) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (basecoin.Result, error) {
func (m *middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (basecoin.Result, error) {
// make sure we pass in proper context to child
next := secureCheck(m.next, ctx)
// set the permissions for this app
@ -33,7 +33,7 @@ func (m *middleware) CheckTx(ctx basecoin.Context, store state.KVStore, tx basec
}
// DeliverTx always returns an empty success tx
func (m *middleware) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
func (m *middleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
// make sure we pass in proper context to child
next := secureDeliver(m.next, ctx)
// set the permissions for this app
@ -43,7 +43,7 @@ func (m *middleware) DeliverTx(ctx basecoin.Context, store state.KVStore, tx bas
return m.middleware.DeliverTx(ctx, store, tx, next)
}
func (m *middleware) SetOption(l log.Logger, store state.KVStore, module, key, value string) (string, error) {
func (m *middleware) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
// set the namespace for the app
store = stateSpace(store, m.Name())

View File

@ -1,13 +1,18 @@
package stack
import "github.com/tendermint/basecoin/state"
import (
"bytes"
"errors"
"github.com/tendermint/basecoin/state"
)
type prefixStore struct {
prefix []byte
store state.KVStore
store state.SimpleDB
}
var _ state.KVStore = prefixStore{}
var _ state.SimpleDB = prefixStore{}
func (p prefixStore) Set(key, value []byte) {
key = append(p.prefix, key...)
@ -19,11 +24,78 @@ func (p prefixStore) Get(key []byte) (value []byte) {
return p.store.Get(key)
}
func (p prefixStore) Has(key []byte) bool {
key = append(p.prefix, key...)
return p.store.Has(key)
}
func (p prefixStore) Remove(key []byte) (value []byte) {
key = append(p.prefix, key...)
return p.store.Remove(key)
}
func (p prefixStore) List(start, end []byte, limit int) []state.Model {
start = append(p.prefix, start...)
end = append(p.prefix, end...)
res := p.store.List(start, end, limit)
trim := len(p.prefix)
for i := range res {
res[i].Key = res[i].Key[trim:]
}
return res
}
func (p prefixStore) First(start, end []byte) state.Model {
start = append(p.prefix, start...)
end = append(p.prefix, end...)
res := p.store.First(start, end)
if len(res.Key) > 0 {
res.Key = res.Key[len(p.prefix):]
}
return res
}
func (p prefixStore) Last(start, end []byte) state.Model {
start = append(p.prefix, start...)
end = append(p.prefix, end...)
res := p.store.Last(start, end)
if len(res.Key) > 0 {
res.Key = res.Key[len(p.prefix):]
}
return res
}
func (p prefixStore) Checkpoint() state.SimpleDB {
return prefixStore{
prefix: p.prefix,
store: p.store.Checkpoint(),
}
}
func (p prefixStore) Commit(sub state.SimpleDB) error {
ps, ok := sub.(prefixStore)
if !ok {
return errors.New("Must commit prefixStore")
}
if !bytes.Equal(ps.prefix, p.prefix) {
return errors.New("Cannot commit sub-tx with different prefix")
}
// commit the wrapped data, don't worry about the prefix here
p.store.Commit(ps.store)
return nil
}
func (p prefixStore) Discard() {
p.store.Discard()
}
// stateSpace will unwrap any prefixStore and then add the prefix
//
// this can be used by the middleware and dispatcher to isolate one space,
// then unwrap and isolate another space
func stateSpace(store state.KVStore, app string) state.KVStore {
func stateSpace(store state.SimpleDB, app string) state.SimpleDB {
// unwrap one-level if wrapped
if pstore, ok := store.(prefixStore); ok {
store = pstore.store
@ -31,13 +103,21 @@ func stateSpace(store state.KVStore, app string) state.KVStore {
return PrefixedStore(app, store)
}
func unwrap(store state.SimpleDB) state.SimpleDB {
// 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
//
// This is useful for tests or utilities that have access to the global
// state to check individual app spaces. Individual apps should not be able
// to use this to read each other's space
func PrefixedStore(app string, store state.KVStore) state.KVStore {
func PrefixedStore(app string, store state.SimpleDB) state.SimpleDB {
prefix := append([]byte(app), byte(0))
return prefixStore{prefix, store}
}

View File

@ -26,7 +26,7 @@ func (Recovery) Name() string {
var _ Middleware = Recovery{}
// CheckTx catches any panic and converts to error - fulfills Middlware interface
func (Recovery) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
func (Recovery) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
defer func() {
if r := recover(); r != nil {
err = normalizePanic(r)
@ -36,7 +36,7 @@ func (Recovery) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.T
}
// DeliverTx catches any panic and converts to error - fulfills Middlware interface
func (Recovery) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
func (Recovery) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
defer func() {
if r := recover(); r != nil {
err = normalizePanic(r)
@ -46,7 +46,7 @@ func (Recovery) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin
}
// SetOption catches any panic and converts to error - fulfills Middlware interface
func (Recovery) SetOption(l log.Logger, store state.KVStore, module, key, value string, next basecoin.SetOptioner) (log string, err error) {
func (Recovery) SetOption(l log.Logger, store state.SimpleDB, module, key, value string, next basecoin.SetOptioner) (log string, err error) {
defer func() {
if r := recover(); r != nil {
err = normalizePanic(r)

View File

@ -23,19 +23,19 @@ var _ Middleware = writerMid{}
func (w writerMid) Name() string { return w.name }
func (w writerMid) CheckTx(ctx basecoin.Context, store state.KVStore,
func (w writerMid) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Checker) (basecoin.Result, error) {
store.Set(w.key, w.value)
return next.CheckTx(ctx, store, tx)
}
func (w writerMid) DeliverTx(ctx basecoin.Context, store state.KVStore,
func (w writerMid) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Deliver) (basecoin.Result, error) {
store.Set(w.key, w.value)
return next.DeliverTx(ctx, store, tx)
}
func (w writerMid) SetOption(l log.Logger, store state.KVStore, module,
func (w writerMid) SetOption(l log.Logger, store state.SimpleDB, module,
key, value string, next basecoin.SetOptioner) (string, error) {
store.Set([]byte(key), []byte(value))
return next.SetOption(l, store, module, key, value)
@ -51,19 +51,19 @@ var _ basecoin.Handler = writerHand{}
func (w writerHand) Name() string { return w.name }
func (w writerHand) CheckTx(ctx basecoin.Context, store state.KVStore,
func (w writerHand) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx) (basecoin.Result, error) {
store.Set(w.key, w.value)
return basecoin.Result{}, nil
}
func (w writerHand) DeliverTx(ctx basecoin.Context, store state.KVStore,
func (w writerHand) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx) (basecoin.Result, error) {
store.Set(w.key, w.value)
return basecoin.Result{}, nil
}
func (w writerHand) SetOption(l log.Logger, store state.KVStore, module,
func (w writerHand) SetOption(l log.Logger, store state.SimpleDB, module,
key, value string) (string, error) {
store.Set([]byte(key), []byte(value))
return "Success", nil

124
state/bonsai.go Normal file
View File

@ -0,0 +1,124 @@
package state
import (
"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 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 NewMemKVCache(b)
}
func (b *Bonsai) Commit(sub 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() 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 ErrNotASubTransaction()
// }
// b.Tree = bb.Tree
// return nil
// }
// Discard will remove reference to this
func (b *Bonsai) Discard() {
b.id = 0
b.Tree = nil
}

28
state/chainstate.go Normal file
View File

@ -0,0 +1,28 @@
package state
// 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 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 KVStore) string {
if s.chainID != "" {
return s.chainID
}
s.chainID = string(store.Get(baseChainIDKey))
return s.chainID
}

20
state/errors.go Normal file
View File

@ -0,0 +1,20 @@
//nolint
package state
import (
"fmt"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/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)
}

141
state/kvcache.go Normal file
View File

@ -0,0 +1,141 @@
package state
// MemKVCache is designed to wrap MemKVStore as a cache
type MemKVCache struct {
store SimpleDB
cache *MemKVStore
}
var _ 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 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) []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 []Model) []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) Model {
data := c.List(start, end, 0)
if len(data) == 0 {
return Model{}
}
return data[0]
}
// Last is done with List, but could be much more efficient
func (c *MemKVCache) Last(start, end []byte) Model {
data := c.List(start, end, 0)
if len(data) == 0 {
return 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() 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 SimpleDB) error {
cache, ok := sub.(*MemKVCache)
if !ok {
return ErrNotASubTransaction()
}
// TODO: see if it points to us
// 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, v := range c.cache.m {
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()
}

124
state/kvcache_test.go Normal file
View File

@ -0,0 +1,124 @@
package state
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCache(t *testing.T) {
assert := assert.New(t)
cases := []struct {
init []Model
toGet []Model
toList []listQuery
setCache []Model
removeCache []Model
getCache []Model
listCache []listQuery
}{
// simple add
{
init: []Model{m("a", "1"), m("c", "2")},
toGet: []Model{m("a", "1"), m("c", "2"), m("d", "")},
toList: []listQuery{{
"a", "e", 0,
[]Model{m("a", "1"), m("c", "2")},
m("c", "2"),
}},
setCache: []Model{m("d", "3")},
removeCache: []Model{m("a", "1")},
getCache: []Model{m("a", ""), m("c", "2"), m("d", "3")},
listCache: []listQuery{{
"a", "e", 0,
[]Model{m("c", "2"), m("d", "3")},
m("d", "3"),
}},
},
}
checkGet := func(db SimpleDB, m 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 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 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)
}
}
}
}

View File

@ -1,12 +1,12 @@
package state
import (
"container/list"
"fmt"
"sort"
. "github.com/tendermint/tmlibs/common"
"github.com/tendermint/go-wire/data"
)
// KVStore is a simple interface to get/set data
type KVStore interface {
Set(key, value []byte)
Get(key []byte) (value []byte)
@ -14,125 +14,157 @@ type KVStore interface {
//----------------------------------------
// Model grabs together key and value to allow easier return values
type Model struct {
Key data.Bytes
Value data.Bytes
}
// SimpleDB allows us to do some basic range queries on a db
type SimpleDB interface {
KVStore
Has(key []byte) (has bool)
Remove(key []byte) (value []byte) // returns old value if there was one
// Start is inclusive, End is exclusive...
// Thus List ([]byte{12, 13}, []byte{12, 14}) will return anything with
// the prefix []byte{12, 13}
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()
}
//----------------------------------------
// MemKVStore is a simple implementation of 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 _ SimpleDB = NewMemKVStore()
// NewMemKVStore initializes a MemKVStore
func NewMemKVStore() *MemKVStore {
return &MemKVStore{
m: make(map[string][]byte, 0),
}
}
func (mkv *MemKVStore) Set(key []byte, value []byte) {
mkv.m[string(key)] = value
func (m *MemKVStore) Set(key []byte, value []byte) {
m.m[string(key)] = value
}
func (mkv *MemKVStore) Get(key []byte) (value []byte) {
return mkv.m[string(key)]
func (m *MemKVStore) Get(key []byte) (value []byte) {
return m.m[string(key)]
}
//----------------------------------------
// A Cache that enforces deterministic sync order.
type KVCache struct {
store KVStore
cache map[string]kvCacheValue
keys *list.List
logging bool
logLines []string
func (m *MemKVStore) Has(key []byte) (has bool) {
_, ok := m.m[string(key)]
return ok
}
type kvCacheValue struct {
v []byte // The value of some key
e *list.Element // The KVCache.keys element
func (m *MemKVStore) Remove(key []byte) (value []byte) {
val := m.m[string(key)]
delete(m.m, string(key))
return val
}
// NOTE: If store is nil, creates a new MemKVStore
func NewKVCache(store KVStore) *KVCache {
if store == nil {
store = NewMemKVStore()
}
return (&KVCache{
store: store,
}).Reset()
}
func (kvc *KVCache) SetLogging() {
kvc.logging = true
}
func (kvc *KVCache) GetLogLines() []string {
return kvc.logLines
}
func (kvc *KVCache) ClearLogLines() {
kvc.logLines = nil
}
func (kvc *KVCache) Reset() *KVCache {
kvc.cache = make(map[string]kvCacheValue)
kvc.keys = list.New()
return kvc
}
func (kvc *KVCache) Set(key []byte, value []byte) {
if kvc.logging {
line := fmt.Sprintf("Set %v = %v", LegibleBytes(key), LegibleBytes(value))
kvc.logLines = append(kvc.logLines, line)
}
cacheValue, ok := kvc.cache[string(key)]
if ok {
kvc.keys.MoveToBack(cacheValue.e)
} else {
cacheValue.e = kvc.keys.PushBack(key)
}
cacheValue.v = value
kvc.cache[string(key)] = cacheValue
}
func (kvc *KVCache) Get(key []byte) (value []byte) {
cacheValue, ok := kvc.cache[string(key)]
if ok {
if kvc.logging {
line := fmt.Sprintf("Get (hit) %v = %v", LegibleBytes(key), LegibleBytes(cacheValue.v))
kvc.logLines = append(kvc.logLines, line)
func (m *MemKVStore) List(start, end []byte, limit int) []Model {
keys := m.keysInRange(start, end)
if limit > 0 && len(keys) > 0 {
if limit > len(keys) {
limit = len(keys)
}
return cacheValue.v
} else {
value := kvc.store.Get(key)
kvc.cache[string(key)] = kvCacheValue{
v: value,
e: kvc.keys.PushBack(key),
}
if kvc.logging {
line := fmt.Sprintf("Get (miss) %v = %v", LegibleBytes(key), LegibleBytes(value))
kvc.logLines = append(kvc.logLines, line)
}
return value
keys = keys[:limit]
}
}
//Update the store with the values from the cache
func (kvc *KVCache) Sync() {
for e := kvc.keys.Front(); e != nil; e = e.Next() {
key := e.Value.([]byte)
value := kvc.cache[string(key)]
kvc.store.Set(key, value.v)
}
kvc.Reset()
}
//----------------------------------------
func LegibleBytes(data []byte) string {
s := ""
for _, b := range data {
if 0x21 <= b && b < 0x7F {
s += Green(string(b))
} else {
s += Blue(Fmt("%02X", b))
res := make([]Model, len(keys))
for i, k := range keys {
res[i] = Model{
Key: []byte(k),
Value: m.m[k],
}
}
return s
return res
}
// First iterates through all keys to find the one that matches
func (m *MemKVStore) First(start, end []byte) Model {
key := ""
for _, k := range m.keysInRange(start, end) {
if key == "" || k < key {
key = k
}
}
if key == "" {
return Model{}
}
return Model{
Key: []byte(key),
Value: m.m[key],
}
}
func (m *MemKVStore) Last(start, end []byte) Model {
key := ""
for _, k := range m.keysInRange(start, end) {
if key == "" || k > key {
key = k
}
}
if key == "" {
return Model{}
}
return Model{
Key: []byte(key),
Value: m.m[key],
}
}
func (m *MemKVStore) Discard() {
m.m = make(map[string][]byte, 0)
}
func (m *MemKVStore) Checkpoint() SimpleDB {
return NewMemKVCache(m)
}
func (m *MemKVStore) Commit(sub SimpleDB) error {
cache, ok := sub.(*MemKVCache)
if !ok {
return ErrNotASubTransaction()
}
// TODO: see if it points to us
// 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
}

View File

@ -1,70 +0,0 @@
package state
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestKVStore(t *testing.T) {
assert := assert.New(t)
//stores to be tested
ms := NewMemKVStore()
store := NewMemKVStore()
kvc := NewKVCache(store)
//key value pairs to be tested within the system
var keyvalue = []struct {
key string
value string
}{
{"foo", "snake"},
{"bar", "mouse"},
}
//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))
}
}
//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 read/write for MemKVStore
setRecords(ms)
assert.True(storeHasAll(ms), "MemKVStore doesn't retrieve after Set")
//test read/write for KVCache
setRecords(kvc)
assert.True(storeHasAll(kvc), "KVCache doesn't retrieve after Set")
//test reset
kvc.Reset()
assert.False(storeHasAll(kvc), "KVCache retrieving after reset")
//test sync
setRecords(kvc)
assert.False(storeHasAll(store), "store retrieving before synced")
kvc.Sync()
assert.True(storeHasAll(store), "store isn't retrieving after synced")
//test logging
assert.Zero(len(kvc.GetLogLines()), "logging events existed before using SetLogging")
kvc.SetLogging()
setRecords(kvc)
assert.Equal(len(kvc.GetLogLines()), 2, "incorrect number of logging events recorded")
kvc.ClearLogLines()
assert.Zero(len(kvc.GetLogLines()), "logging events still exists after ClearLogLines")
}

83
state/merkle.go Normal file
View File

@ -0,0 +1,83 @@
package state
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 *Bonsai
deliverTx SimpleDB
checkTx SimpleDB
persistent bool
}
func NewState(tree merkle.Tree, persistent bool) State {
base := NewBonsai(tree)
return State{
committed: base,
deliverTx: base.Checkpoint(),
checkTx: base.Checkpoint(),
persistent: persistent,
}
}
func (s State) Committed() *Bonsai {
return s.committed
}
func (s State) Append() SimpleDB {
return s.deliverTx
}
func (s State) Check() SimpleDB {
return s.checkTx
}
// Hash applies deliverTx to committed and calculates hash
//
// Note the usage:
// Hash -> gets the calculated hash but doesn't save
// BatchSet -> adds some out-of-bounds data
// Commit -> Save everything to disk and the same hash
func (s *State) Hash() ([]byte, error) {
err := s.committed.Commit(s.deliverTx)
if err != nil {
return nil, err
}
s.deliverTx = s.committed.Checkpoint()
return s.committed.Tree.Hash(), nil
}
// 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.committed.Tree.(*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, error) {
// commit (if we didn't do hash earlier)
err := s.committed.Commit(s.deliverTx)
if err != nil {
return nil, err
}
var hash []byte
if s.persistent {
hash = s.committed.Tree.Save()
} else {
hash = s.committed.Tree.Hash()
}
s.deliverTx = s.committed.Checkpoint()
s.checkTx = s.committed.Checkpoint()
return hash, nil
}

View File

@ -1,84 +0,0 @@
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
}
func NewState(store KVStore, l log.Logger) *State {
return &State{
chainID: "",
store: store,
readCache: make(map[string][]byte),
writeCache: nil,
logger: l,
}
}
func (s *State) SetChainID(chainID string) {
s.chainID = chainID
s.store.Set([]byte("base/chain_id"), []byte(chainID))
}
func (s *State) GetChainID() string {
if s.chainID != "" {
return s.chainID
}
s.chainID = string(s.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 +0,0 @@
package state
import (
"bytes"
"testing"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
"github.com/stretchr/testify/assert"
)
func TestState(t *testing.T) {
assert := assert.New(t)
//States and Stores for tests
store := NewMemKVStore()
state := NewState(store, log.TestingLogger())
cache := state.CacheWrap()
eyesCli := eyes.NewLocalClient("", 0)
//reset the store/state/cache
reset := func() {
store = NewMemKVStore()
state = NewState(store, log.TestingLogger())
cache = state.CacheWrap()
}
//set the state to using the eyesCli instead of MemKVStore
useEyesCli := func() {
state = NewState(eyesCli, 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 kvc to have all the key value pairs
setRecords := func(kv KVStore) {
for _, n := range keyvalue {
kv.Set([]byte(n.key), []byte(n.value))
}
}
//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 chainID
state.SetChainID("testchain")
assert.Equal(state.GetChainID(), "testchain", "ChainID is improperly stored")
//test basic retrieve
setRecords(state)
assert.True(storeHasAll(state), "state doesn't retrieve after Set")
//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 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")
}

147
state/store_test.go Normal file
View File

@ -0,0 +1,147 @@
package state
import (
"io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tendermint/merkleeyes/iavl"
dbm "github.com/tendermint/tmlibs/db"
)
func GetDBs() []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.NewIAVLTree(500, db)
return []SimpleDB{
NewMemKVStore(),
NewBonsai(iavl.NewIAVLTree(0, nil)),
NewBonsai(persist),
}
}
func b(k string) []byte {
if k == "" {
return nil
}
return []byte(k)
}
func m(k, v string) Model {
return 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 []Model
// expected result from Last
last 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 []Model
toRemove []Model
toGet []Model
toList []listQuery
}{
// simple add
{
toSet: []Model{m("a", "b"), m("c", "d")},
toRemove: nil,
toGet: []Model{m("a", "b")},
toList: []listQuery{
{
"a", "d", 0,
[]Model{m("a", "b"), m("c", "d")},
m("c", "d"),
},
{
"a", "c", 10,
[]Model{m("a", "b")},
m("a", "b"),
},
},
},
// over-write data, remove
{
toSet: []Model{
m("a", "1"),
m("b", "2"),
m("c", "3"),
m("b", "4"),
},
toRemove: []Model{m("c", "3")},
toGet: []Model{
m("a", "1"),
m("b", "4"),
m("c", ""),
},
toList: []listQuery{
{
"0d", "h", 1,
[]Model{m("a", "1")},
m("b", "4"),
},
{
"ad", "ak", 10,
[]Model{},
Model{},
},
{
"ad", "k", 0,
[]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 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)
}
}
}
}
}

View File

@ -68,7 +68,7 @@ test03SendMultiFromRole() {
# let's try to send money from the role directly without multisig
FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --name=$POOR 2>/dev/null)
assertFalse "need to assume role" $?
FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --assume-role=bank --name=$POOR 2>/dev/null)
FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=2 --assume-role=bank --name=$POOR 2>/dev/null)
assertFalse "need two signatures" $?
# okay, begin a multisig transaction mr. poor...
@ -76,8 +76,8 @@ test03SendMultiFromRole() {
echo qwertyuiop | ${CLIENT_EXE} tx send --amount=6000mycoin --from=$BANK --to=$TWO --sequence=1 --assume-role=bank --name=$POOR --multi --prepare=$TX_FILE
assertTrue "line=${LINENO}, successfully prepare tx" $?
# and get some dude to sign it
FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx --in=$TX_FILE --name=$POOR 2>/dev/null)
assertFalse "line=${LINENO}, double signing doesn't get bank" $?
# FAIL=$(echo qwertyuiop | ${CLIENT_EXE} tx --in=$TX_FILE --name=$POOR 2>/dev/null)
# assertFalse "line=${LINENO}, double signing doesn't get bank" $?
# and get some dude to sign it for the full access
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx --in=$TX_FILE --name=$DUDE)
txSucceeded $? "$TX" "multi-bank"