Merge pull request #196 from tendermint/feature/184-overhaul-handler-interface

Feature/184 overhaul handler interface
This commit is contained in:
Ethan Frey 2017-08-04 14:18:07 +02:00 committed by GitHub
commit 2113c9f884
47 changed files with 982 additions and 300 deletions

21
TODO.md Normal file
View File

@ -0,0 +1,21 @@
Alexis:
* merkle - proof (non-existence - maybe range)
* intro to light-client and proofs
light-client proofs:
* make this sensible -> very tied to merkle proofs and API
* support new proof types
* expose more proof types in basecoin.Query
* merkle - api cleanup (also Bonsai)
* later: C bindings (to Bonsai?)
* crypto-ledger (while ethan gone)
light-client provider:
* caching checkpoint on Verify
* cleanup (trim old node)

View File

@ -1,6 +1,7 @@
package app
import (
"bytes"
"fmt"
"strings"
@ -23,11 +24,12 @@ const (
// Basecoin - The ABCI application
type Basecoin struct {
info *sm.ChainState
info *sm.ChainState
state *Store
handler basecoin.Handler
pending []*abci.Validator
height uint64
logger log.Logger
}
@ -65,8 +67,9 @@ func (app *Basecoin) Info() abci.ResponseInfo {
}
}
// SetOption - ABCI
func (app *Basecoin) SetOption(key string, value string) string {
// InitState - used to setup state (was SetOption)
// to be used by InitChain later
func (app *Basecoin) InitState(key string, value string) string {
module, key := splitKey(key)
state := app.state.Append()
@ -79,13 +82,18 @@ func (app *Basecoin) SetOption(key string, value string) string {
return fmt.Sprintf("Error: unknown base option: %s", key)
}
log, err := app.handler.SetOption(app.logger, state, module, key, value)
log, err := app.handler.InitState(app.logger, state, module, key, value)
if err == nil {
return log
}
return "Error: " + err.Error()
}
// SetOption - ABCI
func (app *Basecoin) SetOption(key string, value string) string {
return "Not Implemented"
}
// DeliverTx - ABCI
func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result {
tx, err := basecoin.LoadTx(txBytes)
@ -103,7 +111,8 @@ func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result {
if err != nil {
return errors.Result(err)
}
return res.ToABCI()
app.addValChange(res.Diff)
return basecoin.ToABCI(res)
}
// CheckTx - ABCI
@ -123,7 +132,7 @@ func (app *Basecoin) CheckTx(txBytes []byte) abci.Result {
if err != nil {
return errors.Result(err)
}
return res.ToABCI()
return basecoin.ToABCI(res)
}
// Query - ABCI
@ -163,14 +172,35 @@ func (app *Basecoin) BeginBlock(hash []byte, header *abci.Header) {
}
// EndBlock - ABCI
// Returns a list of all validator changes made in this block
func (app *Basecoin) EndBlock(height uint64) (res abci.ResponseEndBlock) {
// for _, plugin := range app.plugins.GetList() {
// pluginRes := plugin.EndBlock(app.state, height)
// res.Diffs = append(res.Diffs, pluginRes.Diffs...)
// }
// TODO: cleanup in case a validator exists multiple times in the list
res.Diffs = app.pending
app.pending = nil
return
}
func (app *Basecoin) addValChange(diffs []*abci.Validator) {
for _, d := range diffs {
idx := pubKeyIndex(d, app.pending)
if idx >= 0 {
app.pending[idx] = d
} else {
app.pending = append(app.pending, d)
}
}
}
// return index of list with validator of same PubKey, or -1 if no match
func pubKeyIndex(val *abci.Validator, list []*abci.Validator) int {
for i, v := range list {
if bytes.Equal(val.PubKey, v.PubKey) {
return i
}
}
return -1
}
//TODO move split key to tmlibs?
// Splits the string at the first '/'.

View File

@ -98,9 +98,9 @@ func (at *appTest) feeTx(coins coin.Coins, toll coin.Coin, sequence uint32) base
return at.signTx(tx)
}
// set the account on the app through SetOption
// set the account on the app through InitState
func (at *appTest) initAccount(acct *coin.AccountWithKey) {
res := at.app.SetOption("coin/account", acct.MakeOption())
res := at.app.InitState("coin/account", acct.MakeOption())
require.EqualValues(at.t, res, "Success")
}
@ -121,7 +121,7 @@ func (at *appTest) reset() {
logger.With("module", "app"),
)
res := at.app.SetOption("base/chain_id", at.chainID)
res := at.app.InitState("base/chain_id", at.chainID)
require.EqualValues(at.t, res, "Success")
at.initAccount(at.acctIn)
@ -167,7 +167,7 @@ func (at *appTest) exec(t *testing.T, tx basecoin.Tx, checkTx bool) (res abci.Re
//--------------------------------------------------------
func TestSetOption(t *testing.T) {
func TestInitState(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
@ -183,14 +183,14 @@ func TestSetOption(t *testing.T) {
//testing ChainID
chainID := "testChain"
res := app.SetOption("base/chain_id", chainID)
res := app.InitState("base/chain_id", chainID)
assert.EqualValues(app.GetChainID(), chainID)
assert.EqualValues(res, "Success")
// make a nice account...
bal := coin.Coins{{"atom", 77}, {"eth", 12}}
acct := coin.NewAccountWithKey(bal)
res = app.SetOption("coin/account", acct.MakeOption())
res = app.InitState("coin/account", acct.MakeOption())
require.EqualValues(res, "Success")
// make sure it is set correctly, with some balance
@ -218,7 +218,7 @@ func TestSetOption(t *testing.T) {
}
]
}`
res = app.SetOption("coin/account", unsortAcc)
res = app.InitState("coin/account", unsortAcc)
require.EqualValues(res, "Success")
coins, err = getAddr(unsortAddr, app.GetState())
@ -226,13 +226,13 @@ func TestSetOption(t *testing.T) {
assert.True(coins.IsValid())
assert.Equal(unsortCoins, coins)
res = app.SetOption("base/dslfkgjdas", "")
res = app.InitState("base/dslfkgjdas", "")
assert.NotEqual(res, "Success")
res = app.SetOption("dslfkgjdas", "")
res = app.InitState("dslfkgjdas", "")
assert.NotEqual(res, "Success")
res = app.SetOption("dslfkgjdas/szfdjzs", "")
res = app.InitState("dslfkgjdas/szfdjzs", "")
assert.NotEqual(res, "Success")
}

88
app/app_val_test.go Normal file
View File

@ -0,0 +1,88 @@
package app
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/modules/base"
wire "github.com/tendermint/go-wire"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
)
//-----------------------------------
// Test cases start here
func randPower() uint64 {
return uint64(cmn.RandInt()%50 + 60)
}
func makeVal() *abci.Validator {
return &abci.Validator{
PubKey: cmn.RandBytes(10),
Power: randPower(),
}
}
// withNewPower returns a copy of the validator with a different power
func withNewPower(val *abci.Validator) *abci.Validator {
res := *val
res.Power = randPower()
if res.Power == val.Power {
panic("no no")
}
return &res
}
func TestEndBlock(t *testing.T) {
assert, require := assert.New(t), require.New(t)
logger := log.NewNopLogger()
store := MockStore()
handler := base.ValSetHandler{}
app := NewBasecoin(handler, store, logger)
val1 := makeVal()
val2 := makeVal()
val3 := makeVal()
val1a := withNewPower(val1)
val2a := withNewPower(val2)
cases := [...]struct {
changes [][]*abci.Validator
expected []*abci.Validator
}{
// Nothing in, nothing out, no crash
0: {},
// One in, one out, no problem
1: {
changes: [][]*abci.Validator{{val1}},
expected: []*abci.Validator{val1},
},
// Combine a few ones
2: {
changes: [][]*abci.Validator{{val1}, {val2, val3}},
expected: []*abci.Validator{val1, val2, val3},
},
// Make sure changes all to one validators are squished into one diff
3: {
changes: [][]*abci.Validator{{val1}, {val2, val1a}, {val2a, val3}},
expected: []*abci.Validator{val1a, val2a, val3},
},
}
for i, tc := range cases {
app.BeginBlock(nil, nil)
for _, c := range tc.changes {
tx := base.ValChangeTx{c}.Wrap()
txBytes := wire.BinaryBytes(tx)
res := app.DeliverTx(txBytes)
require.True(res.IsOK(), "%#v", res)
}
diff := app.EndBlock(app.height)
// TODO: don't care about order here...
assert.Equal(tc.expected, diff.Diffs, "%d", i)
}
}

View File

@ -16,16 +16,16 @@ func (app *Basecoin) LoadGenesis(path string) error {
}
// set chain_id
app.SetOption("base/chain_id", genDoc.ChainID)
app.InitState("base/chain_id", genDoc.ChainID)
// set accounts
for _, acct := range genDoc.AppOptions.Accounts {
_ = app.SetOption("coin/account", string(acct))
_ = app.InitState("coin/account", string(acct))
}
// set plugin options
for _, kv := range genDoc.AppOptions.pluginOptions {
_ = app.SetOption(kv.Key, kv.Value)
_ = app.InitState(kv.Key, kv.Value)
}
return nil

View File

@ -36,6 +36,16 @@ type ChainState struct {
Height uint64
}
// MockStore returns an in-memory store only intended for testing
func MockStore() *Store {
res, err := NewStore("", 0, log.NewNopLogger())
if err != nil {
// should never happen, abort test if it does
panic(err)
}
return res
}
// 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) {

View File

@ -72,7 +72,7 @@ func NewBenchApp(h basecoin.Handler, chainID string, n int,
store,
logger.With("module", "app"),
)
res := app.SetOption("base/chain_id", chainID)
res := app.InitState("base/chain_id", chainID)
if res != "Success" {
panic("cannot set chain")
}
@ -82,7 +82,7 @@ func NewBenchApp(h basecoin.Handler, chainID string, n int,
accts := make([]*coin.AccountWithKey, n)
for i := 0; i < n; i++ {
accts[i] = coin.NewAccountWithKey(money)
res := app.SetOption("coin/account", accts[i].MakeOption())
res := app.InitState("coin/account", accts[i].MakeOption())
if res != "Success" {
panic("can't set account")
}

View File

@ -114,7 +114,8 @@ func NewHandler(feeDenom string) basecoin.Handler {
// Handler the counter transaction processing handler
type Handler struct {
stack.NopOption
stack.PassInitState
stack.PassInitValidate
}
var _ stack.Dispatchable = Handler{}
@ -128,13 +129,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.SimpleDB, 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.CheckResult, err error) {
_, err = checkTx(ctx, tx)
return
}
// DeliverTx executes the tx if valid
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, 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.DeliverResult, err error) {
ctr, err := checkTx(ctx, tx)
if err != nil {
return res, err

View File

@ -36,12 +36,12 @@ func TestCounterPlugin(t *testing.T) {
store,
logger.With("module", "app"),
)
bcApp.SetOption("base/chain_id", chainID)
bcApp.InitState("base/chain_id", chainID)
// Account initialization
bal := coin.Coins{{"", 1000}, {"gold", 1000}}
acct := coin.NewAccountWithKey(bal)
log := bcApp.SetOption("coin/account", acct.MakeOption())
log := bcApp.InitState("coin/account", acct.MakeOption())
require.Equal("Success", log)
// Deliver a CounterTx

View File

@ -10,79 +10,154 @@ import (
// Handler is anything that processes a transaction
type Handler interface {
// Checker verifies there are valid fees and estimates work
Checker
// Deliver performs the tx once it makes it in the block
Deliver
SetOptioner
// InitStater sets state from the genesis file
InitStater
// InitValidater sets the initial validator set
InitValidater
// Named ensures there is a name for the item
Named
// TODO: flesh these out as well
// InitChain(store state.SimpleDB, vals []*abci.Validator)
// TODO????
// BeginBlock(store state.SimpleDB, hash []byte, header *abci.Header)
// EndBlock(store state.SimpleDB, height uint64) abci.ResponseEndBlock
}
// Named ensures there is a name for the item
type Named interface {
Name() string
}
// Checker verifies there are valid fees and estimates work
type Checker interface {
CheckTx(ctx Context, store state.SimpleDB, tx Tx) (Result, error)
CheckTx(ctx Context, store state.SimpleDB, tx Tx) (CheckResult, error)
}
// CheckerFunc (like http.HandlerFunc) is a shortcut for making wrapers
type CheckerFunc func(Context, state.SimpleDB, Tx) (Result, error)
// CheckerFunc (like http.HandlerFunc) is a shortcut for making wrappers
type CheckerFunc func(Context, state.SimpleDB, Tx) (CheckResult, error)
func (c CheckerFunc) CheckTx(ctx Context, store state.SimpleDB, tx Tx) (Result, error) {
func (c CheckerFunc) CheckTx(ctx Context, store state.SimpleDB, tx Tx) (CheckResult, error) {
return c(ctx, store, tx)
}
// Deliver performs the tx once it makes it in the block
type Deliver interface {
DeliverTx(ctx Context, store state.SimpleDB, tx Tx) (Result, error)
DeliverTx(ctx Context, store state.SimpleDB, tx Tx) (DeliverResult, error)
}
// DeliverFunc (like http.HandlerFunc) is a shortcut for making wrapers
type DeliverFunc func(Context, state.SimpleDB, Tx) (Result, error)
// DeliverFunc (like http.HandlerFunc) is a shortcut for making wrappers
type DeliverFunc func(Context, state.SimpleDB, Tx) (DeliverResult, error)
func (c DeliverFunc) DeliverTx(ctx Context, store state.SimpleDB, tx Tx) (Result, error) {
func (c DeliverFunc) DeliverTx(ctx Context, store state.SimpleDB, tx Tx) (DeliverResult, error) {
return c(ctx, store, tx)
}
type SetOptioner interface {
SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error)
// InitStater sets state from the genesis file
type InitStater interface {
InitState(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.SimpleDB, string, string, string) (string, error)
// InitStateFunc (like http.HandlerFunc) is a shortcut for making wrappers
type InitStateFunc func(log.Logger, state.SimpleDB, string, string, string) (string, error)
func (c SetOptionFunc) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
func (c InitStateFunc) InitState(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
return c(l, store, module, key, value)
}
// Result captures any non-error abci result
// to make sure people use error for error cases
type Result struct {
Data data.Bytes
Log string
// InitValidater sets the initial validator set
type InitValidater interface {
InitValidate(log log.Logger, store state.SimpleDB, vals []*abci.Validator)
}
func (r Result) ToABCI() abci.Result {
// InitValidateFunc (like http.HandlerFunc) is a shortcut for making wrappers
type InitValidateFunc func(log.Logger, state.SimpleDB, []*abci.Validator)
func (c InitValidateFunc) InitValidate(l log.Logger, store state.SimpleDB, vals []*abci.Validator) {
c(l, store, vals)
}
//---------- results and some wrappers --------
// Result is a common interface of CheckResult and GetResult
type Result interface {
GetData() data.Bytes
GetLog() string
}
func ToABCI(r Result) abci.Result {
return abci.Result{
Data: r.Data,
Log: r.Log,
Data: r.GetData(),
Log: r.GetLog(),
}
}
// CheckResult captures any non-error abci result
// to make sure people use error for error cases
type CheckResult struct {
Data data.Bytes
Log string
// GasAllocated is the maximum units of work we allow this tx to perform
GasAllocated uint64
// GasPayment is the total fees for this tx (or other source of payment)
GasPayment uint64
}
// NewCheck sets the gas used and the response data but no more info
// these are the most common info needed to be set by the Handler
func NewCheck(gasAllocated uint64, log string) CheckResult {
return CheckResult{
GasAllocated: gasAllocated,
Log: log,
}
}
var _ Result = CheckResult{}
func (r CheckResult) GetData() data.Bytes {
return r.Data
}
func (r CheckResult) GetLog() string {
return r.Log
}
// DeliverResult captures any non-error abci result
// to make sure people use error for error cases
type DeliverResult struct {
Data data.Bytes
Log string
Diff []*abci.Validator
GasUsed uint64
}
var _ Result = DeliverResult{}
func (r DeliverResult) GetData() data.Bytes {
return r.Data
}
func (r DeliverResult) GetLog() string {
return r.Log
}
// placeholders
// holders
type NopCheck struct{}
func (_ NopCheck) CheckTx(Context, state.SimpleDB, Tx) (r Result, e error) { return }
func (_ NopCheck) CheckTx(Context, state.SimpleDB, Tx) (r CheckResult, e error) { return }
type NopDeliver struct{}
func (_ NopDeliver) DeliverTx(Context, state.SimpleDB, Tx) (r Result, e error) { return }
func (_ NopDeliver) DeliverTx(Context, state.SimpleDB, Tx) (r DeliverResult, e error) { return }
type NopOption struct{}
type NopInitState struct{}
func (_ NopOption) SetOption(log.Logger, state.SimpleDB, string, string, string) (string, error) {
func (_ NopInitState) InitState(log.Logger, state.SimpleDB, string, string, string) (string, error) {
return "", nil
}
type NopInitValidate struct{}
func (_ NopInitValidate) InitValidate(log log.Logger, store state.SimpleDB, vals []*abci.Validator) {}

View File

@ -17,7 +17,8 @@ const (
// Signatures parses out go-crypto signatures and adds permissions to the
// context for use inside the application
type Signatures struct {
stack.PassOption
stack.PassInitState
stack.PassInitValidate
}
// Name of the module - fulfills Middleware interface
@ -39,7 +40,7 @@ type Signable interface {
}
// CheckTx verifies the signatures are correct - fulfills Middlware interface
func (Signatures) CheckTx(ctx basecoin.Context, store state.SimpleDB, 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.CheckResult, err error) {
sigs, tnext, err := getSigners(tx)
if err != nil {
return res, err
@ -49,7 +50,7 @@ func (Signatures) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoi
}
// DeliverTx verifies the signatures are correct - fulfills Middlware interface
func (Signatures) DeliverTx(ctx basecoin.Context, store state.SimpleDB, 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.DeliverResult, err error) {
sigs, tnext, err := getSigners(tx)
if err != nil {
return res, err

View File

@ -13,7 +13,8 @@ const (
// Chain enforces that this tx was bound to the named chain
type Chain struct {
stack.PassOption
stack.PassInitState
stack.PassInitValidate
}
// Name of the module - fulfills Middleware interface
@ -24,7 +25,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.SimpleDB, 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.CheckResult, err error) {
stx, err := c.checkChainTx(ctx.ChainID(), ctx.BlockHeight(), tx)
if err != nil {
return res, err
@ -33,7 +34,7 @@ func (c Chain) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.T
}
// DeliverTx makes sure we are on the proper chain - fulfills Middlware interface
func (c Chain) DeliverTx(ctx basecoin.Context, store state.SimpleDB, 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.DeliverResult, err error) {
stx, err := c.checkChainTx(ctx.ChainID(), ctx.BlockHeight(), tx)
if err != nil {
return res, err

View File

@ -82,10 +82,10 @@ func TestChain(t *testing.T) {
i := strconv.Itoa(idx)
// make sure check returns error, not a panic crash
res, err := app.CheckTx(ctx, store, tc.tx)
cres, err := app.CheckTx(ctx, store, tc.tx)
if tc.valid {
assert.Nil(err, "%d: %+v", idx, err)
assert.Equal(msg, res.Log, i)
assert.Equal(msg, cres.Log, i)
} else {
if assert.NotNil(err, i) {
assert.Contains(err.Error(), tc.errorMsg, i)
@ -93,10 +93,10 @@ func TestChain(t *testing.T) {
}
// make sure deliver returns error, not a panic crash
res, err = app.DeliverTx(ctx, store, tc.tx)
dres, err := app.DeliverTx(ctx, store, tc.tx)
if tc.valid {
assert.Nil(err, "%d: %+v", idx, err)
assert.Equal(msg, res.Log, i)
assert.Equal(msg, dres.Log, i)
} else {
if assert.NotNil(err, i) {
assert.Contains(err.Error(), tc.errorMsg, i)

118
modules/base/helpers.go Normal file
View File

@ -0,0 +1,118 @@
package base
import (
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/state"
)
//nolint
const (
NameVal = "val"
NamePrice = "price"
TypeValChange = NameVal + "/change"
ByteValChange = 0xfe
TypePriceShow = NamePrice + "/show"
BytePriceShow = 0xfd
)
func init() {
basecoin.TxMapper.
RegisterImplementation(ValChangeTx{}, TypeValChange, ByteValChange).
RegisterImplementation(PriceShowTx{}, TypePriceShow, BytePriceShow)
}
//--------------------------------
// Setup tx and handler for validation test cases
type ValSetHandler struct {
basecoin.NopCheck
basecoin.NopInitState
basecoin.NopInitValidate
}
var _ basecoin.Handler = ValSetHandler{}
func (ValSetHandler) Name() string {
return NameVal
}
func (ValSetHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx) (res basecoin.DeliverResult, err error) {
change, ok := tx.Unwrap().(ValChangeTx)
if !ok {
return res, errors.ErrUnknownTxType(tx)
}
res.Diff = change.Diff
return
}
type ValChangeTx struct {
Diff []*abci.Validator
}
func (v ValChangeTx) Wrap() basecoin.Tx {
return basecoin.Tx{v}
}
func (v ValChangeTx) ValidateBasic() error { return nil }
//--------------------------------
// Setup tx and handler for testing checktx fees/gas
// PriceData is the data we ping back
var PriceData = []byte{0xCA, 0xFE}
// PriceHandler returns checktx results based on the input
type PriceHandler struct {
basecoin.NopInitState
basecoin.NopInitValidate
}
var _ basecoin.Handler = PriceHandler{}
func (PriceHandler) Name() string {
return NamePrice
}
func (PriceHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx) (res basecoin.CheckResult, err error) {
price, ok := tx.Unwrap().(PriceShowTx)
if !ok {
return res, errors.ErrUnknownTxType(tx)
}
res.GasAllocated = price.GasAllocated
res.GasPayment = price.GasPayment
res.Data = PriceData
return
}
func (PriceHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx) (res basecoin.DeliverResult, err error) {
_, ok := tx.Unwrap().(PriceShowTx)
if !ok {
return res, errors.ErrUnknownTxType(tx)
}
res.Data = PriceData
return
}
// PriceShowTx lets us bounce back a given fee/gas on CheckTx
type PriceShowTx struct {
GasAllocated uint64
GasPayment uint64
}
func NewPriceShowTx(gasAllocated, gasPayment uint64) basecoin.Tx {
return PriceShowTx{GasAllocated: gasAllocated, GasPayment: gasPayment}.Wrap()
}
func (p PriceShowTx) Wrap() basecoin.Tx {
return basecoin.Tx{p}
}
func (v PriceShowTx) ValidateBasic() error { return nil }

View File

@ -3,6 +3,7 @@ package base
import (
"time"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
@ -26,7 +27,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.SimpleDB, 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.CheckResult, err error) {
start := time.Now()
res, err = next.CheckTx(ctx, store, tx)
delta := time.Now().Sub(start)
@ -41,7 +42,7 @@ func (Logger) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx
}
// DeliverTx logs time and result - fulfills Middlware interface
func (Logger) DeliverTx(ctx basecoin.Context, store state.SimpleDB, 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.DeliverResult, err error) {
start := time.Now()
res, err = next.DeliverTx(ctx, store, tx)
delta := time.Now().Sub(start)
@ -55,21 +56,30 @@ func (Logger) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.
return
}
// SetOption logs time and result - fulfills Middlware interface
func (Logger) SetOption(l log.Logger, store state.SimpleDB, module, key, value string, next basecoin.SetOptioner) (string, error) {
// InitState logs time and result - fulfills Middlware interface
func (Logger) InitState(l log.Logger, store state.SimpleDB, module, key, value string, next basecoin.InitStater) (string, error) {
start := time.Now()
res, err := next.SetOption(l, store, module, key, value)
res, err := next.InitState(l, store, module, key, value)
delta := time.Now().Sub(start)
// TODO: log the value being set also?
l = l.With("duration", micros(delta)).With("mod", module).With("key", key)
if err == nil {
l.Info("SetOption", "log", res)
l.Info("InitState", "log", res)
} else {
l.Error("SetOption", "err", err)
l.Error("InitState", "err", err)
}
return res, err
}
// InitValidate logs time and result - fulfills Middlware interface
func (Logger) InitValidate(l log.Logger, store state.SimpleDB, vals []*abci.Validator, next basecoin.InitValidater) {
start := time.Now()
next.InitValidate(l, store, vals)
delta := time.Now().Sub(start)
l = l.With("duration", micros(delta))
l.Info("InitValidate")
}
// micros returns how many microseconds passed in a call
func micros(d time.Duration) int {
return int(d.Seconds() * 1000000)

View File

@ -3,6 +3,7 @@ package base
import (
"strings"
abci "github.com/tendermint/abci/types"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data"
@ -18,7 +19,8 @@ const (
// Multiplexer grabs a MultiTx and sends them sequentially down the line
type Multiplexer struct {
stack.PassOption
stack.PassInitState
stack.PassInitValidate
}
// Name of the module - fulfills Middleware interface
@ -29,45 +31,86 @@ 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.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)
func (Multiplexer) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.CheckResult, err error) {
if mtx, ok := tx.Unwrap().(MultiTx); ok {
return runAllChecks(ctx, store, mtx.Txs, next)
}
return next.CheckTx(ctx, store, tx)
}
// DeliverTx splits the input tx and checks them all - fulfills Middlware interface
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)
func (Multiplexer) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.DeliverResult, err error) {
if mtx, ok := tx.Unwrap().(MultiTx); ok {
return runAllDelivers(ctx, store, mtx.Txs, next)
}
return next.DeliverTx(ctx, store, tx)
}
func runAll(ctx basecoin.Context, store state.SimpleDB, txs []basecoin.Tx, next basecoin.CheckerFunc) (res basecoin.Result, err error) {
func runAllChecks(ctx basecoin.Context, store state.SimpleDB, txs []basecoin.Tx, next basecoin.Checker) (res basecoin.CheckResult, err error) {
// store all results, unless anything errors
rs := make([]basecoin.Result, len(txs))
rs := make([]basecoin.CheckResult, len(txs))
for i, stx := range txs {
rs[i], err = next(ctx, store, stx)
rs[i], err = next.CheckTx(ctx, store, stx)
if err != nil {
return
}
}
// now combine the results into one...
return combine(rs), nil
return combineChecks(rs), nil
}
func runAllDelivers(ctx basecoin.Context, store state.SimpleDB, txs []basecoin.Tx, next basecoin.Deliver) (res basecoin.DeliverResult, err error) {
// store all results, unless anything errors
rs := make([]basecoin.DeliverResult, len(txs))
for i, stx := range txs {
rs[i], err = next.DeliverTx(ctx, store, stx)
if err != nil {
return
}
}
// now combine the results into one...
return combineDelivers(rs), nil
}
// combines all data bytes as a go-wire array.
// joins all log messages with \n
func combine(all []basecoin.Result) basecoin.Result {
func combineChecks(all []basecoin.CheckResult) basecoin.CheckResult {
datas := make([]data.Bytes, len(all))
logs := make([]string, len(all))
var allocated, payments uint64
for i, r := range all {
datas[i] = r.Data
logs[i] = r.Log
allocated += r.GasAllocated
payments += r.GasPayment
}
return basecoin.Result{
Data: wire.BinaryBytes(datas),
Log: strings.Join(logs, "\n"),
return basecoin.CheckResult{
Data: wire.BinaryBytes(datas),
Log: strings.Join(logs, "\n"),
GasAllocated: allocated,
GasPayment: payments,
}
}
// combines all data bytes as a go-wire array.
// joins all log messages with \n
func combineDelivers(all []basecoin.DeliverResult) basecoin.DeliverResult {
datas := make([]data.Bytes, len(all))
logs := make([]string, len(all))
var used uint64
var diffs []*abci.Validator
for i, r := range all {
datas[i] = r.Data
logs[i] = r.Log
used += r.GasUsed
if len(r.Diff) > 0 {
diffs = append(diffs, r.Diff...)
}
}
return basecoin.DeliverResult{
Data: wire.BinaryBytes(datas),
Log: strings.Join(logs, "\n"),
GasUsed: used,
Diff: diffs,
}
}

View File

@ -0,0 +1,87 @@
package base
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/state"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data"
"github.com/tendermint/tmlibs/log"
)
func TestMultiplexer(t *testing.T) {
assert := assert.New(t)
msg := "diddly"
chainID := "multi-verse"
height := uint64(100)
// Generic args here...
store := state.NewMemKVStore()
ctx := stack.NewContext(chainID, height, log.NewNopLogger())
// Build the stack
app := stack.
New(Multiplexer{}).
Dispatch(
stack.WrapHandler(stack.OKHandler{Log: msg}),
stack.WrapHandler(PriceHandler{}),
)
raw := stack.NewRawTx([]byte{1, 2, 3, 4})
fail := stack.NewFailTx()
price1 := NewPriceShowTx(123, 456)
price2 := NewPriceShowTx(1000, 2000)
price3 := NewPriceShowTx(11, 0)
join := func(data ...[]byte) []byte {
return wire.BinaryBytes(data)
}
cases := [...]struct {
tx basecoin.Tx
valid bool
gasAllocated uint64
gasPayment uint64
log string
data data.Bytes
}{
// test the components without multiplexer (no effect)
0: {raw, true, 0, 0, msg, nil},
1: {price1, true, 123, 456, "", PriceData},
2: {fail, false, 0, 0, "", nil},
// test multiplexer on error
3: {NewMultiTx(raw, fail, price1), false, 0, 0, "", nil},
// test combining info on multiplexer
4: {NewMultiTx(price1, raw), true, 123, 456, "\n" + msg, join(PriceData, nil)},
// add lots of prices
5: {NewMultiTx(price1, price2, price3), true, 1134, 2456, "\n\n", join(PriceData, PriceData, PriceData)},
// combine multiple logs
6: {NewMultiTx(raw, price3, raw), true, 11, 0, msg + "\n\n" + msg, join(nil, PriceData, nil)},
}
for i, tc := range cases {
cres, err := app.CheckTx(ctx, store, tc.tx)
if tc.valid {
assert.Nil(err, "%d: %+v", i, err)
assert.Equal(tc.log, cres.Log, "%d", i)
assert.Equal(tc.data, cres.Data, "%d", i)
assert.Equal(tc.gasAllocated, cres.GasAllocated, "%d", i)
assert.Equal(tc.gasPayment, cres.GasPayment, "%d", i)
} else {
assert.NotNil(err, "%d", i)
}
// make sure deliver returns error, not a panic crash
dres, err := app.DeliverTx(ctx, store, tc.tx)
if tc.valid {
assert.Nil(err, "%d: %+v", i, err)
assert.Equal(tc.log, dres.Log, "%d", i)
assert.Equal(tc.data, dres.Data, "%d", i)
} else {
assert.NotNil(err, "%d", i)
}
}
}

View File

@ -18,13 +18,13 @@ func TestEncoding(t *testing.T) {
require := require.New(t)
raw := stack.NewRawTx([]byte{0x34, 0xa7})
raw2 := stack.NewRawTx([]byte{0x73, 0x86, 0x22})
// raw2 := stack.NewRawTx([]byte{0x73, 0x86, 0x22})
cases := []struct {
Tx basecoin.Tx
}{
{raw},
{NewMultiTx(raw, raw2)},
// {NewMultiTx(raw, raw2)},
{NewChainTx("foobar", 0, raw)},
}

View File

@ -28,7 +28,7 @@ func BenchmarkSimpleTransfer(b *testing.B) {
// set the initial account
acct := NewAccountWithKey(Coins{{"mycoin", 1234567890}})
h.SetOption(logger, store, NameCoin, "account", acct.MakeOption(), nil)
h.InitState(logger, store, NameCoin, "account", acct.MakeOption(), nil)
sender := acct.Actor()
receiver := basecoin.Actor{App: "foo", Address: cmn.RandBytes(20)}

View File

@ -12,11 +12,19 @@ import (
"github.com/tendermint/basecoin/state"
)
//NameCoin - name space of the coin module
const NameCoin = "coin"
const (
//NameCoin - name space of the coin module
NameCoin = "coin"
// CostSend is GasAllocation per input/output
CostSend = uint64(10)
// CostCredit is GasAllocation of a credit allocation
CostCredit = uint64(20)
)
// Handler includes an accountant
type Handler struct{}
type Handler struct {
stack.PassInitValidate
}
var _ stack.Dispatchable = Handler{}
@ -35,7 +43,7 @@ func (Handler) AssertDispatcher() {}
// CheckTx checks if there is enough money in the account
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, _ basecoin.Checker) (res basecoin.Result, err error) {
tx basecoin.Tx, _ basecoin.Checker) (res basecoin.CheckResult, err error) {
err = tx.ValidateBasic()
if err != nil {
@ -44,16 +52,19 @@ func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB,
switch t := tx.Unwrap().(type) {
case SendTx:
return res, h.checkSendTx(ctx, store, t)
// price based on inputs and outputs
used := uint64(len(t.Inputs) + len(t.Outputs))
return basecoin.NewCheck(used*CostSend, ""), h.checkSendTx(ctx, store, t)
case CreditTx:
return h.creditTx(ctx, store, t)
// default price of 20, constant work
return basecoin.NewCheck(CostCredit, ""), h.creditTx(ctx, store, t)
}
return res, errors.ErrUnknownTxType(tx.Unwrap())
}
// DeliverTx moves the money
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, cb basecoin.Deliver) (res basecoin.Result, err error) {
tx basecoin.Tx, cb basecoin.Deliver) (res basecoin.DeliverResult, err error) {
err = tx.ValidateBasic()
if err != nil {
@ -62,16 +73,16 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
switch t := tx.Unwrap().(type) {
case SendTx:
return h.sendTx(ctx, store, t, cb)
return res, h.sendTx(ctx, store, t, cb)
case CreditTx:
return h.creditTx(ctx, store, t)
return res, h.creditTx(ctx, store, t)
}
return res, errors.ErrUnknownTxType(tx.Unwrap())
}
// SetOption - sets the genesis account balance
func (h Handler) SetOption(l log.Logger, store state.SimpleDB,
module, key, value string, cb basecoin.SetOptioner) (log string, err error) {
// InitState - sets the genesis account balance
func (h Handler) InitState(l log.Logger, store state.SimpleDB,
module, key, value string, cb basecoin.InitStater) (log string, err error) {
if module != NameCoin {
return "", errors.ErrUnknownModule(module)
}
@ -85,11 +96,11 @@ func (h Handler) SetOption(l log.Logger, store state.SimpleDB,
}
func (h Handler) sendTx(ctx basecoin.Context, store state.SimpleDB,
send SendTx, cb basecoin.Deliver) (res basecoin.Result, err error) {
send SendTx, cb basecoin.Deliver) error {
err = checkTx(ctx, send)
err := checkTx(ctx, send)
if err != nil {
return res, err
return err
}
// deduct from all input accounts
@ -97,7 +108,7 @@ func (h Handler) sendTx(ctx basecoin.Context, store state.SimpleDB,
for _, in := range send.Inputs {
_, err = ChangeCoins(store, in.Address, in.Coins.Negative())
if err != nil {
return res, err
return err
}
senders = append(senders, in.Address)
}
@ -112,7 +123,7 @@ func (h Handler) sendTx(ctx basecoin.Context, store state.SimpleDB,
_, err = ChangeCoins(store, out.Address, out.Coins)
if err != nil {
return res, err
return err
}
// now send ibc packet if needed...
if out.Address.ChainID != "" {
@ -133,46 +144,46 @@ func (h Handler) sendTx(ctx basecoin.Context, store state.SimpleDB,
ibcCtx := ctx.WithPermissions(ibc.AllowIBC(NameCoin))
_, err := cb.DeliverTx(ibcCtx, store, packet.Wrap())
if err != nil {
return res, err
return err
}
}
}
// a-ok!
return res, nil
return nil
}
func (h Handler) creditTx(ctx basecoin.Context, store state.SimpleDB,
credit CreditTx) (res basecoin.Result, err error) {
credit CreditTx) error {
// first check permissions!!
info, err := loadHandlerInfo(store)
if err != nil {
return res, err
return err
}
if info.Issuer.Empty() || !ctx.HasPermission(info.Issuer) {
return res, errors.ErrUnauthorized()
return errors.ErrUnauthorized()
}
// load up the account
addr := ChainAddr(credit.Debitor)
acct, err := GetAccount(store, addr)
if err != nil {
return res, err
return err
}
// make and check changes
acct.Coins = acct.Coins.Plus(credit.Credit)
if !acct.Coins.IsNonnegative() {
return res, ErrInsufficientFunds()
return ErrInsufficientFunds()
}
acct.Credit = acct.Credit.Plus(credit.Credit)
if !acct.Credit.IsNonnegative() {
return res, ErrInsufficientCredit()
return ErrInsufficientCredit()
}
err = storeAccount(store, addr.Bytes(), acct)
return res, err
return err
}
func checkTx(ctx basecoin.Context, send SendTx) error {

View File

@ -85,7 +85,7 @@ func TestHandlerValidation(t *testing.T) {
}
}
func TestDeliverSendTx(t *testing.T) {
func TestCheckDeliverSendTx(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
@ -110,6 +110,7 @@ func TestDeliverSendTx(t *testing.T) {
tx basecoin.Tx
perms []basecoin.Actor
final []money // nil for error
cost uint64 // gas allocated (if not error)
}{
{
[]money{{addr1, moreCoins}},
@ -118,6 +119,7 @@ func TestDeliverSendTx(t *testing.T) {
[]TxOutput{NewTxOutput(addr2, someCoins)}),
[]basecoin.Actor{addr1},
[]money{{addr1, diffCoins}, {addr2, someCoins}},
20,
},
// simple multi-sig 2 accounts to 1
{
@ -127,6 +129,7 @@ func TestDeliverSendTx(t *testing.T) {
[]TxOutput{NewTxOutput(addr3, mixedCoins)}),
[]basecoin.Actor{addr1, addr2},
[]money{{addr1, someCoins}, {addr2, diffCoins}, {addr3, mixedCoins}},
30,
},
// multi-sig with one account sending many times
{
@ -136,6 +139,17 @@ func TestDeliverSendTx(t *testing.T) {
[]TxOutput{NewTxOutput(addr2, mixedCoins)}),
[]basecoin.Actor{addr1},
[]money{{addr1, diffCoins}, {addr2, mixedCoins}},
30,
},
// invalid send (not enough money )
{
[]money{{addr1, moreCoins}, {addr2, someCoins}},
NewSendTx(
[]TxInput{NewTxInput(addr2, moreCoins)},
[]TxOutput{NewTxOutput(addr1, moreCoins)}),
[]basecoin.Actor{addr1, addr2},
nil,
0,
},
}
@ -150,9 +164,19 @@ func TestDeliverSendTx(t *testing.T) {
}
ctx := stack.MockContext("base-chain", 100).WithPermissions(tc.perms...)
_, err := h.DeliverTx(ctx, store, tc.tx, nil)
// throw-away state for checktx
cache := store.Checkpoint()
cres, err := h.CheckTx(ctx, cache, tc.tx, nil)
// real store for delivertx
_, err2 := h.DeliverTx(ctx, store, tc.tx, nil)
if len(tc.final) > 0 { // valid
assert.Nil(err, "%d: %+v", i, err)
assert.Nil(err2, "%d: %+v", i, err2)
// make sure proper gas is set
assert.Equal(uint64(0), cres.GasPayment, "%d", i)
assert.Equal(tc.cost, cres.GasAllocated, "%d", i)
// make sure the final balances are correct
for _, f := range tc.final {
acct, err := loadAccount(store, f.addr.Bytes())
@ -160,14 +184,15 @@ func TestDeliverSendTx(t *testing.T) {
assert.Equal(f.coins, acct.Coins)
}
} else {
// both check and deliver should fail
assert.NotNil(err, "%d", i)
// TODO: make sure balances unchanged!
assert.NotNil(err2, "%d", i)
}
}
}
func TestSetOption(t *testing.T) {
func TestInitState(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
@ -205,7 +230,7 @@ func TestSetOption(t *testing.T) {
for j, gen := range tc.init {
value, err := json.Marshal(gen)
require.Nil(err, "%d,%d: %+v", i, j, err)
_, err = h.SetOption(l, store, NameCoin, key, string(value), nil)
_, err = h.InitState(l, store, NameCoin, key, string(value), nil)
require.Nil(err)
}
@ -239,7 +264,7 @@ func TestSetIssuer(t *testing.T) {
value, err := json.Marshal(tc.issuer)
require.Nil(err, "%d,%d: %+v", i, err)
_, err = h.SetOption(l, store, NameCoin, key, string(value), nil)
_, err = h.InitState(l, store, NameCoin, key, string(value), nil)
require.Nil(err, "%+v", err)
// check state is proper
@ -274,11 +299,11 @@ func TestDeliverCreditTx(t *testing.T) {
// set the owner who can issue credit
js, err := json.Marshal(owner)
require.Nil(err, "%+v", err)
_, err = h.SetOption(log.NewNopLogger(), store, "coin", "issuer", string(js), nil)
_, err = h.InitState(log.NewNopLogger(), store, "coin", "issuer", string(js), nil)
require.Nil(err, "%+v", err)
// give addr2 some coins to start
_, err = h.SetOption(log.NewNopLogger(), store, "coin", "account", key.MakeOption(), nil)
_, err = h.InitState(log.NewNopLogger(), store, "coin", "account", key.MakeOption(), nil)
require.Nil(err, "%+v", err)
cases := []struct {

View File

@ -41,7 +41,7 @@ func (a *AccountWithKey) NextSequence() uint32 {
return a.Sequence
}
// MakeOption returns a string to use with SetOption to initialize this account
// MakeOption returns a string to use with InitState to initialize this account
//
// This is intended for use in test cases
func (a *AccountWithKey) MakeOption() string {

View File

@ -44,7 +44,7 @@ func TestIBCPostPacket(t *testing.T) {
// set up a rich guy on this chain
wealth := Coins{{"btc", 300}, {"eth", 2000}, {"ltc", 5000}}
rich := NewAccountWithKey(wealth)
_, err = ourChain.SetOption("coin", "account", rich.MakeOption())
_, err = ourChain.InitState("coin", "account", rich.MakeOption())
require.Nil(err, "%+v", err)
// sends money to another guy on a different chain, now other chain has credit

View File

@ -12,6 +12,7 @@ import (
var (
errInsufficientFees = fmt.Errorf("Insufficient fees")
errWrongFeeDenom = fmt.Errorf("Required fee denomination")
errSkipFees = fmt.Errorf("Skip fees")
invalidInput = abci.CodeType_BaseInvalidInput
)
@ -29,3 +30,10 @@ func ErrWrongFeeDenom(denom string) errors.TMError {
func IsWrongFeeDenomErr(err error) bool {
return errors.IsSameError(errWrongFeeDenom, err)
}
func ErrSkipFees() errors.TMError {
return errors.WithCode(errSkipFees, invalidInput)
}
func IsSkipFeesErr(err error) bool {
return errors.IsSameError(errSkipFees, err)
}

View File

@ -24,7 +24,8 @@ type SimpleFeeMiddleware struct {
// all fees go here, which could be a dump (Bank) or something reachable
// by other app logic
Collector basecoin.Actor
stack.PassOption
stack.PassInitState
stack.PassInitValidate
}
var _ stack.Middleware = SimpleFeeMiddleware{}
@ -45,40 +46,72 @@ func (SimpleFeeMiddleware) Name() string {
}
// CheckTx - check the transaction
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)
func (h SimpleFeeMiddleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.CheckResult, err error) {
fee, err := h.verifyFee(ctx, tx)
if err != nil {
if IsSkipFeesErr(err) {
return next.CheckTx(ctx, store, tx)
}
return res, err
}
var paid, used uint64
if !fee.Fee.IsZero() { // now, try to make a IPC call to coins...
send := coin.NewSendOneTx(fee.Payer, h.Collector, coin.Coins{fee.Fee})
sendRes, err := next.CheckTx(ctx, store, send)
if err != nil {
return res, err
}
paid = uint64(fee.Fee.Amount)
used = sendRes.GasAllocated
}
res, err = next.CheckTx(ctx, store, fee.Tx)
// add the given fee to the price for gas, plus one query
if err == nil {
res.GasPayment += paid
res.GasAllocated += used
}
return res, err
}
// DeliverTx - send the fee handler transaction
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) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.DeliverResult, err error) {
fee, err := h.verifyFee(ctx, tx)
if IsSkipFeesErr(err) {
return next.DeliverTx(ctx, store, tx)
}
if err != nil {
return res, err
}
if !fee.Fee.IsZero() { // now, try to make a IPC call to coins...
send := coin.NewSendOneTx(fee.Payer, h.Collector, coin.Coins{fee.Fee})
_, err = next.DeliverTx(ctx, store, send)
if err != nil {
return res, err
}
}
return next.DeliverTx(ctx, store, fee.Tx)
}
func (h SimpleFeeMiddleware) doTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.CheckerFunc) (res basecoin.Result, err error) {
func (h SimpleFeeMiddleware) verifyFee(ctx basecoin.Context, tx basecoin.Tx) (Fee, error) {
feeTx, ok := tx.Unwrap().(Fee)
if !ok {
// the fee wrapper is not required if there is no minimum
if h.MinFee.IsZero() {
return next(ctx, store, tx)
return feeTx, ErrSkipFees()
}
return res, errors.ErrInvalidFormat(TypeFees, tx)
return feeTx, errors.ErrInvalidFormat(TypeFees, tx)
}
// see if it is the proper denom and big enough
fee := feeTx.Fee
if fee.Denom != h.MinFee.Denom {
return res, ErrWrongFeeDenom(h.MinFee.Denom)
return feeTx, ErrWrongFeeDenom(h.MinFee.Denom)
}
if !fee.IsGTE(h.MinFee) {
return res, ErrInsufficientFees()
return feeTx, ErrInsufficientFees()
}
// now, try to make a IPC call to coins...
send := coin.NewSendOneTx(feeTx.Payer, h.Collector, coin.Coins{fee})
_, err = next(ctx, store, send)
if err != nil {
return res, err
}
return next(ctx, store, feeTx.Tx)
return feeTx, nil
}

View File

@ -50,11 +50,13 @@ func TestFeeChecks(t *testing.T) {
// set up the store and init the accounts
store := state.NewMemKVStore()
l := log.NewNopLogger()
_, err := app1.SetOption(l, store, "coin", "account", key1.MakeOption())
_, err := app1.InitState(l, store, "coin", "account", key1.MakeOption())
require.Nil(err, "%+v", err)
_, err = app2.SetOption(l, store, "coin", "account", key2.MakeOption())
_, err = app2.InitState(l, store, "coin", "account", key2.MakeOption())
require.Nil(err, "%+v", err)
// feeCost is what we expect if the fee is properly paid
feeCost := coin.CostSend * 2
cases := []struct {
valid bool
// this is the middleware stack to test
@ -68,30 +70,32 @@ func TestFeeChecks(t *testing.T) {
// expected balance after the tx
left coin.Coins
collected coin.Coins
// expected gas allocated
expectedCost uint64
}{
// make sure it works with no fee (control group)
{true, app1, act1, false, act1, zero, mixed, nil},
{true, app1, act2, false, act2, zero, pure, nil},
{true, app1, act1, false, act1, zero, mixed, nil, 0},
{true, app1, act2, false, act2, zero, pure, nil, 0},
// no fee or too low is rejected
{false, app2, act1, false, act1, zero, mixed, nil},
{false, app2, act2, false, act2, zero, pure, nil},
{false, app2, act1, true, act1, zero, mixed, nil},
{false, app2, act2, true, act2, atom(1), pure, nil},
{false, app2, act1, false, act1, zero, mixed, nil, 0},
{false, app2, act2, false, act2, zero, pure, nil, 0},
{false, app2, act1, true, act1, zero, mixed, nil, 0},
{false, app2, act2, true, act2, atom(1), pure, nil, 0},
// proper fees are transfered in both cases
{true, app1, act1, true, act1, atom(1), wallet(1199, 55), atoms(1)},
{true, app2, act2, true, act2, atom(57), atoms(46600), atoms(58)},
{true, app1, act1, true, act1, atom(1), wallet(1199, 55), atoms(1), feeCost},
{true, app2, act2, true, act2, atom(57), atoms(46600), atoms(58), feeCost},
// // fee must be the proper type...
{false, app1, act1, true, act1, eth(5), wallet(1199, 55), atoms(58)},
{false, app1, act1, true, act1, eth(5), wallet(1199, 55), atoms(58), 0},
// signature must match
{false, app2, act1, true, act2, atom(5), atoms(46600), atoms(58)},
{false, app2, act1, true, act2, atom(5), atoms(46600), atoms(58), 0},
// send only works within limits
{true, app2, act1, true, act1, atom(1100), wallet(99, 55), atoms(1158)},
{false, app2, act1, true, act1, atom(500), wallet(99, 55), atoms(1158)},
{true, app2, act1, true, act1, atom(1100), wallet(99, 55), atoms(1158), feeCost},
{false, app2, act1, true, act1, atom(500), wallet(99, 55), atoms(1158), 0},
}
for i, tc := range cases {
@ -105,9 +109,11 @@ func TestFeeChecks(t *testing.T) {
ctx := stack.MockContext("x", 1).WithPermissions(tc.signer)
// call checktx...
_, err := tc.app.CheckTx(ctx, store, tx)
cres, err := tc.app.CheckTx(ctx, store, tx)
if tc.valid {
assert.Nil(err, "%d: %+v", i, err)
assert.EqualValues(tc.fee.Amount, cres.GasPayment)
assert.EqualValues(tc.expectedCost, cres.GasAllocated)
} else {
assert.NotNil(err, "%d", i)
}

View File

@ -34,12 +34,14 @@ func AllowIBC(app string) basecoin.Actor {
}
// Handler updates the chain state or creates an ibc packet
type Handler struct{}
type Handler struct {
basecoin.NopInitValidate
}
var _ basecoin.Handler = Handler{}
// NewHandler returns a Handler that allows all chains to connect via IBC.
// Set a Registrar via SetOption to restrict it.
// Set a Registrar via InitState to restrict it.
func NewHandler() Handler {
return Handler{}
}
@ -49,8 +51,8 @@ func (Handler) Name() string {
return NameIBC
}
// SetOption sets the registrar for IBC
func (h Handler) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (log string, err error) {
// InitState sets the registrar for IBC
func (h Handler) InitState(l log.Logger, store state.SimpleDB, module, key, value string) (log string, err error) {
if module != NameIBC {
return "", errors.ErrUnknownModule(module)
}
@ -70,26 +72,27 @@ func (h Handler) SetOption(l log.Logger, store state.SimpleDB, module, key, valu
// CheckTx verifies the packet is formated correctly, and has the proper sequence
// for a registered chain
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.CheckResult, err error) {
err = tx.ValidateBasic()
if err != nil {
return res, err
}
switch t := tx.Unwrap().(type) {
// TODO: better fee calculation (don't do complex crypto)
switch tx.Unwrap().(type) {
case RegisterChainTx:
return h.initSeed(ctx, store, t)
return res, nil
case UpdateChainTx:
return h.updateSeed(ctx, store, t)
return res, nil
case CreatePacketTx:
return h.createPacket(ctx, store, t)
return res, nil
}
return res, errors.ErrUnknownTxType(tx.Unwrap())
}
// DeliverTx verifies all signatures on the tx and updates the chain state
// apropriately
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.DeliverResult, err error) {
err = tx.ValidateBasic()
if err != nil {
return res, err
@ -111,7 +114,7 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx baseco
//
// only the registrar, if set, is allowed to do this
func (h Handler) initSeed(ctx basecoin.Context, store state.SimpleDB,
t RegisterChainTx) (res basecoin.Result, err error) {
t RegisterChainTx) (res basecoin.DeliverResult, err error) {
info := LoadInfo(store)
if !info.Registrar.Empty() && !ctx.HasPermission(info.Registrar) {
@ -135,7 +138,7 @@ func (h Handler) initSeed(ctx basecoin.Context, store state.SimpleDB,
// updateSeed checks the seed against the existing chain data and rejects it if it
// doesn't fit (or no chain data)
func (h Handler) updateSeed(ctx basecoin.Context, store state.SimpleDB,
t UpdateChainTx) (res basecoin.Result, err error) {
t UpdateChainTx) (res basecoin.DeliverResult, err error) {
chainID := t.ChainID()
s := NewChainSet(store)
@ -165,7 +168,7 @@ func (h Handler) updateSeed(ctx basecoin.Context, store state.SimpleDB,
// createPacket makes sure all permissions are good and the destination
// chain is registed. If so, it appends it to the outgoing queue
func (h Handler) createPacket(ctx basecoin.Context, store state.SimpleDB,
t CreatePacketTx) (res basecoin.Result, err error) {
t CreatePacketTx) (res basecoin.DeliverResult, err error) {
// make sure the chain is registed
dest := t.DestChain
@ -203,6 +206,6 @@ func (h Handler) createPacket(ctx basecoin.Context, store state.SimpleDB,
packet.Sequence = q.Tail()
q.Push(packet.Bytes())
res = basecoin.Result{Log: fmt.Sprintf("Packet %s %d", dest, packet.Sequence)}
res = basecoin.DeliverResult{Log: fmt.Sprintf("Packet %s %d", dest, packet.Sequence)}
return
}

View File

@ -130,7 +130,7 @@ func TestIBCRegisterPermissions(t *testing.T) {
// set option specifies the registrar
msg, err := json.Marshal(tc.registrar)
require.Nil(err, "%+v", err)
_, err = app.SetOption(log.NewNopLogger(), store,
_, err = app.InitState(log.NewNopLogger(), store,
NameIBC, OptionRegistrar, string(msg))
require.Nil(err, "%+v", err)

View File

@ -9,7 +9,8 @@ import (
// Middleware allows us to verify the IBC proof on a packet and
// and if valid, attach this permission to the wrapped packet
type Middleware struct {
stack.PassOption
stack.PassInitState
stack.PassInitValidate
}
var _ stack.Middleware = Middleware{}
@ -26,7 +27,7 @@ func (Middleware) Name() string {
// CheckTx verifies the named chain and height is present, and verifies
// the merkle proof in the packet
func (m Middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, 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.CheckResult, err error) {
// if it is not a PostPacket, just let it go through
post, ok := tx.Unwrap().(PostPacketTx)
if !ok {
@ -43,7 +44,7 @@ func (m Middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basec
// DeliverTx verifies the named chain and height is present, and verifies
// the merkle proof in the packet
func (m Middleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, 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.DeliverResult, err error) {
// if it is not a PostPacket, just let it go through
post, ok := tx.Unwrap().(PostPacketTx)
if !ok {

View File

@ -100,7 +100,7 @@ func (a *AppChain) IncrementHeight(delta int) int {
// DeliverTx runs the tx and commits the new tree, incrementing height
// by one.
func (a *AppChain) DeliverTx(tx basecoin.Tx, perms ...basecoin.Actor) (basecoin.Result, error) {
func (a *AppChain) DeliverTx(tx basecoin.Tx, perms ...basecoin.Actor) (basecoin.DeliverResult, error) {
ctx := stack.MockContext(a.chainID, uint64(a.height)).WithPermissions(perms...)
store := a.store.Checkpoint()
res, err := a.app.DeliverTx(ctx, store, tx)
@ -118,9 +118,9 @@ func (a *AppChain) Update(tx UpdateChainTx) error {
return err
}
// SetOption sets the option on our app
func (a *AppChain) SetOption(mod, key, value string) (string, error) {
return a.app.SetOption(log.NewNopLogger(), a.store, mod, key, value)
// InitState sets the option on our app
func (a *AppChain) InitState(mod, key, value string) (string, error) {
return a.app.InitState(log.NewNopLogger(), a.store, mod, key, value)
}
// GetStore is used to get the app-specific sub-store

View File

@ -9,11 +9,13 @@ import (
//nolint
const (
NameNonce = "nonce"
CostNonce = 10
)
// ReplayCheck uses the sequence to check for replay attacks
type ReplayCheck struct {
stack.PassOption
stack.PassInitState
stack.PassInitValidate
}
// Name of the module - fulfills Middleware interface
@ -25,21 +27,23 @@ var _ stack.Middleware = ReplayCheck{}
// CheckTx verifies tx is not being replayed - fulfills Middlware interface
func (r ReplayCheck) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
tx basecoin.Tx, next basecoin.Checker) (res basecoin.CheckResult, err error) {
stx, err := r.checkIncrementNonceTx(ctx, store, tx)
if err != nil {
return res, err
}
return next.CheckTx(ctx, store, stx)
res, err = next.CheckTx(ctx, store, stx)
res.GasAllocated += CostNonce
return res, err
}
// 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.SimpleDB,
tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
tx basecoin.Tx, next basecoin.Deliver) (res basecoin.DeliverResult, err error) {
stx, err := r.checkIncrementNonceTx(ctx, store, tx)
if err != nil {

View File

@ -6,12 +6,19 @@ import (
"github.com/tendermint/basecoin/state"
)
//NameRole - name space of the roles module
const NameRole = "role"
const (
//NameRole - name space of the roles module
NameRole = "role"
// CostCreate is the cost to create a new role
CostCreate = uint64(40)
// CostAssume is the cost to assume a role as part of a tx
CostAssume = uint64(5)
)
// Handler allows us to create new roles
type Handler struct {
basecoin.NopOption
basecoin.NopInitState
basecoin.NopInitValidate
}
var _ basecoin.Handler = Handler{}
@ -27,12 +34,13 @@ func (Handler) Name() string {
}
// CheckTx verifies if the transaction is properly formated
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.CheckResult, err error) {
var cr CreateRoleTx
cr, err = checkTx(ctx, tx)
if err != nil {
return
}
res = basecoin.NewCheck(CostCreate, "")
err = checkNoRole(store, cr.Role)
return
}
@ -40,7 +48,7 @@ func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, 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.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.DeliverResult, err error) {
create, err := checkTx(ctx, tx)
if err != nil {
return res, err

View File

@ -38,11 +38,13 @@ func TestCreateRole(t *testing.T) {
store := state.NewMemKVStore()
for i, tc := range cases {
tx := roles.NewCreateRoleTx([]byte(tc.role), tc.min, tc.sigs)
_, err := h.CheckTx(ctx, store, tx)
cres, err := h.CheckTx(ctx, store, tx)
_, err2 := h.DeliverTx(ctx, store, tx)
if tc.valid {
assert.Nil(err, "%d/%s: %+v", i, tc.role, err)
assert.Nil(err2, "%d/%s: %+v", i, tc.role, err2)
assert.Equal(roles.CostCreate, cres.GasAllocated)
assert.Equal(uint64(0), cres.GasPayment)
} else {
assert.NotNil(err, "%d/%s", i, tc.role)
assert.NotNil(err2, "%d/%s", i, tc.role)

View File

@ -9,7 +9,8 @@ import (
// Middleware allows us to add a requested role as a permission
// if the tx requests it and has sufficient authority
type Middleware struct {
stack.PassOption
stack.PassInitState
stack.PassInitValidate
}
var _ stack.Middleware = Middleware{}
@ -27,7 +28,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.SimpleDB, 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.CheckResult, err error) {
// if this is not an AssumeRoleTx, then continue
assume, ok := tx.Unwrap().(AssumeRoleTx)
if !ok { // this also breaks the recursion below
@ -40,13 +41,16 @@ func (m Middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basec
}
// one could add multiple role statements, repeat as needed
return m.CheckTx(ctx, store, assume.Tx, next)
// charging for each level
res, err = m.CheckTx(ctx, store, assume.Tx, next)
res.GasAllocated += CostAssume
return
}
// 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.SimpleDB, 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.DeliverResult, err error) {
// if this is not an AssumeRoleTx, then continue
assume, ok := tx.Unwrap().(AssumeRoleTx)
if !ok { // this also breaks the recursion below

View File

@ -93,11 +93,14 @@ func TestAssumeRole(t *testing.T) {
}
// try CheckTx and DeliverTx and make sure they both assert permissions
_, err := app.CheckTx(myCtx, store, tx)
cres, err := app.CheckTx(myCtx, store, tx)
_, err2 := app.DeliverTx(myCtx, store, tx)
if tc.valid {
assert.Nil(err, "%d: %+v", i, err)
assert.Nil(err2, "%d: %+v", i, err2)
// make sure we charge for each role
assert.Equal(roles.CostAssume*uint64(len(tc.roles)), cres.GasAllocated)
assert.Equal(uint64(0), cres.GasPayment)
} else {
assert.NotNil(err, "%d", i)
assert.NotNil(err2, "%d", i)

View File

@ -14,7 +14,8 @@ const (
type Checkpoint struct {
OnCheck bool
OnDeliver bool
PassOption
PassInitState
PassInitValidate
}
// Name of the module - fulfills Middleware interface
@ -25,7 +26,7 @@ func (Checkpoint) Name() string {
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) {
func (c Checkpoint) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.CheckResult, err error) {
if !c.OnCheck {
return next.CheckTx(ctx, store, tx)
}
@ -38,7 +39,7 @@ func (c Checkpoint) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basec
}
// 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) {
func (c Checkpoint) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.DeliverResult, err error) {
if !c.OnDeliver {
return next.DeliverTx(ctx, store, tx)
}

View File

@ -32,12 +32,12 @@ func makeState() state.SimpleDB {
func TestCheckpointer(t *testing.T) {
assert, require := assert.New(t), require.New(t)
good := writerHand{"foo", []byte{1, 2}, []byte("bar")}
good := writerHand{name: "foo", key: []byte{1, 2}, value: []byte("bar")}
bad := FailHandler{Err: errors.New("no go")}
app := New(
Checkpoint{OnCheck: true},
writerMid{"bing", []byte{1, 2}, []byte("bang")},
writerMid{name: "bing", key: []byte{1, 2}, value: []byte("bang")},
Checkpoint{OnDeliver: true},
).Use(
NewDispatcher(

View File

@ -104,7 +104,7 @@ func withIBC(ctx basecoin.Context) basecoin.Context {
}
func secureCheck(h basecoin.Checker, parent basecoin.Context) basecoin.Checker {
next := func(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
next := func(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.CheckResult, err error) {
if !parent.IsParent(ctx) {
return res, errors.New("Passing in non-child Context")
}
@ -114,7 +114,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.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
next := func(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.DeliverResult, err error) {
if !parent.IsParent(ctx) {
return res, errors.New("Passing in non-child Context")
}

View File

@ -2,8 +2,10 @@ package stack
import (
"fmt"
"sort"
"strings"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
@ -64,7 +66,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.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (d *Dispatcher) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.CheckResult, err error) {
r, err := d.lookupTx(tx)
if err != nil {
return res, err
@ -85,7 +87,7 @@ func (d *Dispatcher) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx base
// 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.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (d *Dispatcher) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.DeliverResult, err error) {
r, err := d.lookupTx(tx)
if err != nil {
return res, err
@ -101,11 +103,11 @@ func (d *Dispatcher) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx ba
return r.DeliverTx(ctx, store, tx, cb)
}
// SetOption - implements Handler interface
// InitState - implements Handler interface
//
// 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.SimpleDB, module, key, value string) (string, error) {
// module name from InitState of the tx.
func (d *Dispatcher) InitState(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
r, err := d.lookupModule(module)
if err != nil {
return "", err
@ -116,7 +118,17 @@ func (d *Dispatcher) SetOption(l log.Logger, store state.SimpleDB, module, key,
// but isolate data space
store = stateSpace(store, r.Name())
return r.SetOption(l, store, module, key, value, cb)
return r.InitState(l, store, module, key, value, cb)
}
// InitValidate makes sure all modules are informed
func (d *Dispatcher) InitValidate(log log.Logger, store state.SimpleDB, vals []*abci.Validator) {
for _, mod := range d.sortedModules() {
// no ctx, so secureCheck not needed
cb := d
space := stateSpace(store, mod.Name())
mod.InitValidate(log, space, vals, cb)
}
}
func (d *Dispatcher) lookupTx(tx basecoin.Tx) (Dispatchable, error) {
@ -140,3 +152,19 @@ func (d *Dispatcher) lookupModule(name string) (Dispatchable, error) {
}
return r, nil
}
func (d *Dispatcher) sortedModules() []Dispatchable {
// order all routes names
size := len(d.routes)
names := make([]string, 0, size)
for k := range d.routes {
names = append(names, k)
}
sort.Strings(names)
res := make([]Dispatchable, size)
for i, k := range names {
res[i] = d.routes[k]
}
return res
}

View File

@ -94,7 +94,8 @@ func (r FailTx) ValidateBasic() error {
// OKHandler just used to return okay to everything
type OKHandler struct {
Log string
basecoin.NopOption
basecoin.NopInitState
basecoin.NopInitValidate
}
var _ basecoin.Handler = OKHandler{}
@ -105,18 +106,19 @@ func (OKHandler) Name() string {
}
// CheckTx always returns an empty success tx
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
func (ok OKHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.CheckResult, err error) {
return basecoin.CheckResult{Log: ok.Log}, nil
}
// DeliverTx always returns an empty success tx
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
func (ok OKHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.DeliverResult, err error) {
return basecoin.DeliverResult{Log: ok.Log}, nil
}
// EchoHandler returns success, echoing res.Data = tx bytes
type EchoHandler struct {
basecoin.NopOption
basecoin.NopInitState
basecoin.NopInitValidate
}
var _ basecoin.Handler = EchoHandler{}
@ -127,21 +129,22 @@ func (EchoHandler) Name() string {
}
// CheckTx always returns an empty success tx
func (EchoHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (EchoHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.CheckResult, err error) {
data, err := data.ToWire(tx)
return basecoin.Result{Data: data}, err
return basecoin.CheckResult{Data: data}, err
}
// DeliverTx always returns an empty success tx
func (EchoHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (EchoHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.DeliverResult, err error) {
data, err := data.ToWire(tx)
return basecoin.Result{Data: data}, err
return basecoin.DeliverResult{Data: data}, err
}
// FailHandler always returns an error
type FailHandler struct {
Err error
basecoin.NopOption
basecoin.NopInitState
basecoin.NopInitValidate
}
var _ basecoin.Handler = FailHandler{}
@ -152,12 +155,12 @@ func (FailHandler) Name() string {
}
// CheckTx always returns the given error
func (f FailHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (f FailHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.CheckResult, err error) {
return res, errors.Wrap(f.Err)
}
// DeliverTx always returns the given error
func (f FailHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (f FailHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.DeliverResult, err error) {
return res, errors.Wrap(f.Err)
}
@ -165,7 +168,8 @@ func (f FailHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx ba
type PanicHandler struct {
Msg string
Err error
basecoin.NopOption
basecoin.NopInitState
basecoin.NopInitValidate
}
var _ basecoin.Handler = PanicHandler{}
@ -176,7 +180,7 @@ func (PanicHandler) Name() string {
}
// CheckTx always panics
func (p PanicHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (p PanicHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.CheckResult, err error) {
if p.Err != nil {
panic(p.Err)
}
@ -184,7 +188,7 @@ func (p PanicHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx bas
}
// DeliverTx always panics
func (p PanicHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (p PanicHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.DeliverResult, err error) {
if p.Err != nil {
panic(p.Err)
}
@ -193,7 +197,8 @@ func (p PanicHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx b
// CheckHandler accepts CheckTx and verifies the permissions
type CheckHandler struct {
basecoin.NopOption
basecoin.NopInitState
basecoin.NopInitValidate
}
var _ basecoin.Handler = CheckHandler{}
@ -204,7 +209,7 @@ func (CheckHandler) Name() string {
}
// CheckTx verifies the permissions
func (c CheckHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (c CheckHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.CheckResult, err error) {
check, ok := tx.Unwrap().(CheckTx)
if !ok {
return res, errors.ErrUnknownTxType(tx)
@ -219,7 +224,16 @@ func (c CheckHandler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx bas
}
// DeliverTx verifies the permissions
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)
func (c CheckHandler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.DeliverResult, err error) {
check, ok := tx.Unwrap().(CheckTx)
if !ok {
return res, errors.ErrUnknownTxType(tx)
}
for _, perm := range check.Required {
if !ctx.HasPermission(perm) {
return res, errors.ErrUnauthorized()
}
}
return res, nil
}

View File

@ -25,9 +25,9 @@ func TestOK(t *testing.T) {
assert.Nil(err, "%+v", err)
assert.Equal(data, res.Log)
res, err = ok.DeliverTx(ctx, store, tx)
dres, err := ok.DeliverTx(ctx, store, tx)
assert.Nil(err, "%+v", err)
assert.Equal(data, res.Log)
assert.Equal(data, dres.Log)
}
func TestFail(t *testing.T) {

View File

@ -16,7 +16,8 @@ const (
// Required Actor, otherwise passes along the call untouched
type CheckMiddleware struct {
Required basecoin.Actor
PassOption
PassInitState
PassInitValidate
}
var _ Middleware = CheckMiddleware{}
@ -25,14 +26,14 @@ func (_ CheckMiddleware) Name() string {
return NameCheck
}
func (p CheckMiddleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, 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.CheckResult, 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.SimpleDB, 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.DeliverResult, err error) {
if !ctx.HasPermission(p.Required) {
return res, errors.ErrUnauthorized()
}
@ -42,7 +43,8 @@ func (p CheckMiddleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, t
// GrantMiddleware tries to set the permission to this Actor, which may be prohibited
type GrantMiddleware struct {
Auth basecoin.Actor
PassOption
PassInitState
PassInitValidate
}
var _ Middleware = GrantMiddleware{}
@ -51,12 +53,12 @@ func (_ GrantMiddleware) Name() string {
return NameGrant
}
func (g GrantMiddleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, 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.CheckResult, err error) {
ctx = ctx.WithPermissions(g.Auth)
return next.CheckTx(ctx, store, tx)
}
func (g GrantMiddleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, 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.DeliverResult, err error) {
ctx = ctx.WithPermissions(g.Auth)
return next.DeliverTx(ctx, store, tx)
}

View File

@ -2,6 +2,7 @@
package stack
import (
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
@ -15,76 +16,89 @@ import (
type Middleware interface {
CheckerMiddle
DeliverMiddle
SetOptionMiddle
InitStaterMiddle
InitValidaterMiddle
basecoin.Named
}
type CheckerMiddle interface {
CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Checker) (basecoin.Result, error)
tx basecoin.Tx, next basecoin.Checker) (basecoin.CheckResult, error)
}
type CheckerMiddleFunc func(basecoin.Context, state.SimpleDB,
basecoin.Tx, basecoin.Checker) (basecoin.Result, error)
basecoin.Tx, basecoin.Checker) (basecoin.CheckResult, error)
func (c CheckerMiddleFunc) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Checker) (basecoin.Result, error) {
tx basecoin.Tx, next basecoin.Checker) (basecoin.CheckResult, error) {
return c(ctx, store, tx, next)
}
type DeliverMiddle interface {
DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx,
next basecoin.Deliver) (basecoin.Result, error)
next basecoin.Deliver) (basecoin.DeliverResult, error)
}
type DeliverMiddleFunc func(basecoin.Context, state.SimpleDB,
basecoin.Tx, basecoin.Deliver) (basecoin.Result, error)
basecoin.Tx, basecoin.Deliver) (basecoin.DeliverResult, error)
func (d DeliverMiddleFunc) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Deliver) (basecoin.Result, error) {
tx basecoin.Tx, next basecoin.Deliver) (basecoin.DeliverResult, error) {
return d(ctx, store, tx, next)
}
type SetOptionMiddle interface {
SetOption(l log.Logger, store state.SimpleDB, module,
key, value string, next basecoin.SetOptioner) (string, error)
type InitStaterMiddle interface {
InitState(l log.Logger, store state.SimpleDB, module,
key, value string, next basecoin.InitStater) (string, error)
}
type SetOptionMiddleFunc func(log.Logger, state.SimpleDB,
string, string, string, basecoin.SetOptioner) (string, error)
type InitStaterMiddleFunc func(log.Logger, state.SimpleDB,
string, string, string, basecoin.InitStater) (string, error)
func (c SetOptionMiddleFunc) SetOption(l log.Logger, store state.SimpleDB,
module, key, value string, next basecoin.SetOptioner) (string, error) {
func (c InitStaterMiddleFunc) InitState(l log.Logger, store state.SimpleDB,
module, key, value string, next basecoin.InitStater) (string, error) {
return c(l, store, module, key, value, next)
}
type InitValidaterMiddle interface {
InitValidate(l log.Logger, store state.SimpleDB, vals []*abci.Validator, next basecoin.InitValidater)
}
type InitValidaterMiddleFunc func(log.Logger, state.SimpleDB,
[]*abci.Validator, basecoin.InitValidater)
func (c InitValidaterMiddleFunc) InitValidate(l log.Logger, store state.SimpleDB,
vals []*abci.Validator, next basecoin.InitValidater) {
c(l, store, vals, next)
}
// holders
type PassCheck struct{}
func (_ PassCheck) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Checker) (basecoin.Result, error) {
tx basecoin.Tx, next basecoin.Checker) (basecoin.CheckResult, error) {
return next.CheckTx(ctx, store, tx)
}
type PassDeliver struct{}
func (_ PassDeliver) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Deliver) (basecoin.Result, error) {
tx basecoin.Tx, next basecoin.Deliver) (basecoin.DeliverResult, error) {
return next.DeliverTx(ctx, store, tx)
}
type PassOption struct{}
type PassInitState struct{}
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)
func (_ PassInitState) InitState(l log.Logger, store state.SimpleDB, module,
key, value string, next basecoin.InitStater) (string, error) {
return next.InitState(l, store, module, key, value)
}
type NopOption struct{}
type PassInitValidate struct{}
func (_ NopOption) SetOption(l log.Logger, store state.SimpleDB, module,
key, value string, next basecoin.SetOptioner) (string, error) {
return "", nil
func (_ PassInitValidate) InitValidate(l log.Logger, store state.SimpleDB,
vals []*abci.Validator, next basecoin.InitValidater) {
next.InitValidate(l, store, vals)
}
// Dispatchable is like middleware, except the meaning of "next" is different.
@ -113,16 +127,21 @@ func (w wrapped) Name() string {
}
func (w wrapped) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, _ basecoin.Checker) (basecoin.Result, error) {
tx basecoin.Tx, _ basecoin.Checker) (basecoin.CheckResult, error) {
return w.h.CheckTx(ctx, store, tx)
}
func (w wrapped) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, _ basecoin.Deliver) (basecoin.Result, error) {
tx basecoin.Tx, _ basecoin.Deliver) (basecoin.DeliverResult, error) {
return w.h.DeliverTx(ctx, store, tx)
}
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)
func (w wrapped) InitState(l log.Logger, store state.SimpleDB,
module, key, value string, _ basecoin.InitStater) (string, error) {
return w.h.InitState(l, store, module, key, value)
}
func (w wrapped) InitValidate(l log.Logger, store state.SimpleDB,
vals []*abci.Validator, next basecoin.InitValidater) {
w.h.InitValidate(l, store, vals)
}

View File

@ -1,6 +1,7 @@
package stack
import (
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
@ -31,7 +32,7 @@ func (m *middleware) wrapCtx(ctx basecoin.Context) basecoin.Context {
}
// CheckTx always returns an empty success tx
func (m *middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (basecoin.Result, error) {
func (m *middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (basecoin.CheckResult, error) {
// make sure we pass in proper context to child
next := secureCheck(m.next, ctx)
// set the permissions for this app
@ -42,7 +43,7 @@ func (m *middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx base
}
// DeliverTx always returns an empty success tx
func (m *middleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) {
func (m *middleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.DeliverResult, err error) {
// make sure we pass in proper context to child
next := secureDeliver(m.next, ctx)
// set the permissions for this app
@ -52,11 +53,17 @@ func (m *middleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx ba
return m.middleware.DeliverTx(ctx, store, tx, next)
}
func (m *middleware) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
func (m *middleware) InitState(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
// set the namespace for the app
store = stateSpace(store, m.space)
return m.middleware.SetOption(l, store, module, key, value, m.next)
return m.middleware.InitState(l, store, module, key, value, m.next)
}
func (m *middleware) InitValidate(l log.Logger, store state.SimpleDB, vals []*abci.Validator) {
// set the namespace for the app
store = stateSpace(store, m.space)
m.middleware.InitValidate(l, store, vals, m.next)
}
// builder is used to associate info with the middleware, so we can build

View File

@ -72,11 +72,11 @@ func TestPermissionSandbox(t *testing.T) {
Apps(CheckMiddleware{Required: tc.require}).
Use(EchoHandler{})
res, err := app.CheckTx(ctx, store, raw)
checkPerm(t, i, tc.expectedRes, tc.expected, res, err)
cres, err := app.CheckTx(ctx, store, raw)
checkPerm(t, i, tc.expectedRes, tc.expected, cres, err)
res, err = app.DeliverTx(ctx, store, raw)
checkPerm(t, i, tc.expectedRes, tc.expected, res, err)
dres, err := app.DeliverTx(ctx, store, raw)
checkPerm(t, i, tc.expectedRes, tc.expected, dres, err)
}
}
@ -85,7 +85,7 @@ func checkPerm(t *testing.T, idx int, data []byte, check func(error) bool, res b
if len(data) > 0 {
assert.Nil(err, "%d: %+v", idx, err)
assert.EqualValues(data, res.Data)
assert.EqualValues(data, res.GetData())
} else {
assert.NotNil(err, "%d", idx)
// check error code!

View File

@ -3,6 +3,7 @@ package stack
import (
"fmt"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
@ -26,7 +27,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.SimpleDB, 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.CheckResult, err error) {
defer func() {
if r := recover(); r != nil {
err = normalizePanic(r)
@ -36,7 +37,7 @@ func (Recovery) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.
}
// DeliverTx catches any panic and converts to error - fulfills Middlware interface
func (Recovery) DeliverTx(ctx basecoin.Context, store state.SimpleDB, 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.DeliverResult, err error) {
defer func() {
if r := recover(); r != nil {
err = normalizePanic(r)
@ -45,14 +46,29 @@ func (Recovery) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoi
return next.DeliverTx(ctx, store, tx)
}
// SetOption catches any panic and converts to error - fulfills Middlware interface
func (Recovery) SetOption(l log.Logger, store state.SimpleDB, module, key, value string, next basecoin.SetOptioner) (log string, err error) {
// InitState catches any panic and converts to error - fulfills Middlware interface
func (Recovery) InitState(l log.Logger, store state.SimpleDB, module, key, value string, next basecoin.InitStater) (log string, err error) {
defer func() {
if r := recover(); r != nil {
err = normalizePanic(r)
}
}()
return next.SetOption(l, store, module, key, value)
return next.InitState(l, store, module, key, value)
}
// InitValidate catches any panic and logs it
// TODO: return an error???
func (Recovery) InitValidate(l log.Logger, store state.SimpleDB,
vals []*abci.Validator, next basecoin.InitValidater) {
defer func() {
if r := recover(); r != nil {
// TODO: return an error???
err := normalizePanic(r)
l.With("err", err).Error(err.Error())
}
}()
next.InitValidate(l, store, vals)
}
// normalizePanic makes sure we can get a nice TMError (with stack) out of it

View File

@ -17,6 +17,7 @@ import (
type writerMid struct {
name string
key, value []byte
PassInitValidate
}
var _ Middleware = writerMid{}
@ -24,27 +25,28 @@ var _ Middleware = writerMid{}
func (w writerMid) Name() string { return w.name }
func (w writerMid) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Checker) (basecoin.Result, error) {
tx basecoin.Tx, next basecoin.Checker) (basecoin.CheckResult, error) {
store.Set(w.key, w.value)
return next.CheckTx(ctx, store, tx)
}
func (w writerMid) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, next basecoin.Deliver) (basecoin.Result, error) {
tx basecoin.Tx, next basecoin.Deliver) (basecoin.DeliverResult, error) {
store.Set(w.key, w.value)
return next.DeliverTx(ctx, store, tx)
}
func (w writerMid) SetOption(l log.Logger, store state.SimpleDB, module,
key, value string, next basecoin.SetOptioner) (string, error) {
func (w writerMid) InitState(l log.Logger, store state.SimpleDB, module,
key, value string, next basecoin.InitStater) (string, error) {
store.Set([]byte(key), []byte(value))
return next.SetOption(l, store, module, key, value)
return next.InitState(l, store, module, key, value)
}
// writerHand is a middleware that writes the given bytes on CheckTx and DeliverTx
// writerHand is a handler that writes the given bytes on CheckTx and DeliverTx
type writerHand struct {
name string
key, value []byte
basecoin.NopInitValidate
}
var _ basecoin.Handler = writerHand{}
@ -52,18 +54,18 @@ var _ basecoin.Handler = writerHand{}
func (w writerHand) Name() string { return w.name }
func (w writerHand) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx) (basecoin.Result, error) {
tx basecoin.Tx) (basecoin.CheckResult, error) {
store.Set(w.key, w.value)
return basecoin.Result{}, nil
return basecoin.CheckResult{}, nil
}
func (w writerHand) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx) (basecoin.Result, error) {
tx basecoin.Tx) (basecoin.DeliverResult, error) {
store.Set(w.key, w.value)
return basecoin.Result{}, nil
return basecoin.DeliverResult{}, nil
}
func (w writerHand) SetOption(l log.Logger, store state.SimpleDB, module,
func (w writerHand) InitState(l log.Logger, store state.SimpleDB, module,
key, value string) (string, error) {
store.Set([]byte(key), []byte(value))
return "Success", nil
@ -76,9 +78,9 @@ func TestStateSpace(t *testing.T) {
expected []data.Bytes
}{
{
writerHand{"foo", []byte{1, 2}, []byte("bar")},
writerHand{name: "foo", key: []byte{1, 2}, value: []byte("bar")},
[]Middleware{
writerMid{"bing", []byte{1, 2}, []byte("bang")},
writerMid{name: "bing", key: []byte{1, 2}, value: []byte("bang")},
},
[]data.Bytes{
{'f', 'o', 'o', 0, 1, 2},