From 670e7b48d1a0b1dbe7e163302a5c7e8a18b345e7 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 4 Jul 2017 14:47:46 +0200 Subject: [PATCH] Counter uses dispatcher to deduct fees from account --- context.go | 1 + .../cmd/countercli/commands/counter.go | 2 +- docs/guide/counter/plugins/counter/counter.go | 46 ++++++++++++++----- .../counter/plugins/counter/counter_test.go | 25 ++++++---- stack/context.go | 11 +++++ stack/dispatcher.go | 15 +++--- stack/interface.go | 6 +++ stack/mock.go | 11 +++++ tests/cli/counter.sh | 4 ++ 9 files changed, 92 insertions(+), 29 deletions(-) diff --git a/context.go b/context.go index dbbe7b6d92..66ba9c5d3e 100644 --- a/context.go +++ b/context.go @@ -32,6 +32,7 @@ type Context interface { log.Logger WithPermissions(perms ...Actor) Context HasPermission(perm Actor) bool + GetPermissions(chain, app string) []Actor IsParent(ctx Context) bool Reset() Context ChainID() string diff --git a/docs/guide/counter/cmd/countercli/commands/counter.go b/docs/guide/counter/cmd/countercli/commands/counter.go index 3c388c8975..eab32acf1b 100644 --- a/docs/guide/counter/cmd/countercli/commands/counter.go +++ b/docs/guide/counter/cmd/countercli/commands/counter.go @@ -74,6 +74,6 @@ func readCounterTxFlags() (tx basecoin.Tx, err error) { return tx, err } - tx = counter.NewCounterTx(viper.GetBool(FlagValid), feeCoins) + tx = counter.NewCounterTx(viper.GetBool(FlagValid), feeCoins, viper.GetInt(FlagSequence)) return tx, nil } diff --git a/docs/guide/counter/plugins/counter/counter.go b/docs/guide/counter/plugins/counter/counter.go index 1cdc31a9bc..96d409c754 100644 --- a/docs/guide/counter/plugins/counter/counter.go +++ b/docs/guide/counter/plugins/counter/counter.go @@ -30,14 +30,16 @@ func init() { } type CounterTx struct { - Valid bool `json:"valid"` - Fee types.Coins `json:"fee"` + Valid bool `json:"valid"` + Fee types.Coins `json:"fee"` + Sequence int `json:"sequence"` } -func NewCounterTx(valid bool, fee types.Coins) basecoin.Tx { +func NewCounterTx(valid bool, fee types.Coins, sequence int) basecoin.Tx { return CounterTx{ - Valid: valid, - Fee: fee, + Valid: valid, + Fee: fee, + Sequence: sequence, }.Wrap() } @@ -85,29 +87,31 @@ func NewCounterHandler() basecoin.Handler { counter := CounterHandler{} dispatcher := stack.NewDispatcher( stack.WrapHandler(coin), - stack.WrapHandler(counter), + counter, ) return stack.NewDefault().Use(dispatcher) } type CounterHandler struct { - basecoin.NopOption + stack.NopOption } -var _ basecoin.Handler = CounterHandler{} +var _ stack.Dispatchable = CounterHandler{} func (_ CounterHandler) Name() string { return NameCounter } +func (_ CounterHandler) AssertDispatcher() {} + // CheckTx checks if the tx is properly structured -func (h CounterHandler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { +func (h CounterHandler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, _ basecoin.Checker) (res basecoin.Result, err error) { _, err = checkTx(ctx, tx) return } // DeliverTx executes the tx if valid -func (h CounterHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { +func (h CounterHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, dispatch basecoin.Deliver) (res basecoin.Result, err error) { ctr, err := checkTx(ctx, tx) if err != nil { return res, err @@ -118,8 +122,22 @@ func (h CounterHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx return res, ErrInvalidCounter() } - // TODO: handle coin movement.... ugh, need sequence to do this, right? - // like, actually decrement the other account + // handle coin movement.... like, actually decrement the other account + if !ctr.Fee.IsZero() { + // take the coins and put them in out account! + senders := ctx.GetPermissions("", stack.NameSigs) + if len(senders) == 0 { + return res, errors.ErrMissingSignature() + } + in := []coin.TxInput{{Address: senders[0], Coins: ctr.Fee, Sequence: ctr.Sequence}} + out := []coin.TxOutput{{Address: CounterAcct(), Coins: ctr.Fee}} + send := coin.NewSendTx(in, out) + // if the deduction fails (too high), abort the command + _, err = dispatch.DeliverTx(ctx, store, send) + if err != nil { + return res, err + } + } // update the counter state, err := LoadState(store) @@ -148,6 +166,10 @@ func checkTx(ctx basecoin.Context, tx basecoin.Tx) (ctr CounterTx, err error) { // CounterStore //-------------------------------------------------------------------------------- +func CounterAcct() basecoin.Actor { + return basecoin.Actor{App: NameCounter, Address: []byte{0x04, 0x20}} +} + type CounterState struct { Counter int `json:"counter"` TotalFees types.Coins `json:"total_fees"` diff --git a/docs/guide/counter/plugins/counter/counter_test.go b/docs/guide/counter/plugins/counter/counter_test.go index e3e8793da7..8b41fcaec4 100644 --- a/docs/guide/counter/plugins/counter/counter_test.go +++ b/docs/guide/counter/plugins/counter/counter_test.go @@ -2,6 +2,7 @@ package counter import ( "encoding/json" + "os" "testing" "github.com/stretchr/testify/assert" @@ -21,10 +22,14 @@ func TestCounterPlugin(t *testing.T) { // Basecoin initialization eyesCli := eyescli.NewLocalClient("", 0) chainID := "test_chain_id" + + // l := log.TestingLogger().With("module", "app"), + l := log.NewTMLogger(os.Stdout).With("module", "app") + // l = log.NewTracingLogger(l) bcApp := app.NewBasecoin( NewCounterHandler(), eyesCli, - log.TestingLogger().With("module", "app"), + l, ) bcApp.SetOption("base/chain_id", chainID) // t.Log(bcApp.Info()) @@ -42,7 +47,7 @@ func TestCounterPlugin(t *testing.T) { // Deliver a CounterTx DeliverCounterTx := func(valid bool, counterFee types.Coins, inputSequence int) abci.Result { - tx := NewCounterTx(valid, counterFee) + tx := NewCounterTx(valid, counterFee, inputSequence) tx = txs.NewChain(chainID, tx) stx := txs.NewSig(tx) txs.Sign(stx, test1PrivAcc.PrivKey) @@ -50,19 +55,19 @@ func TestCounterPlugin(t *testing.T) { return bcApp.DeliverTx(txBytes) } - // Test a basic send, no fee - res := DeliverCounterTx(true, types.Coins{}, 1) + // Test a basic send, no fee (doesn't update sequence as no money spent) + res := DeliverCounterTx(true, nil, 1) assert.True(res.IsOK(), res.String()) // Test an invalid send, no fee - res = DeliverCounterTx(false, types.Coins{}, 1) + res = DeliverCounterTx(false, nil, 1) assert.True(res.IsErr(), res.String()) - // Test the fee - res = DeliverCounterTx(true, types.Coins{{"gold", 100}}, 2) + // Test the fee (increments sequence) + res = DeliverCounterTx(true, types.Coins{{"gold", 100}}, 1) assert.True(res.IsOK(), res.String()) - // TODO: Test unsupported fee - // res = DeliverCounterTx(true, types.Coins{{"silver", 100}}, 3) - // assert.True(res.IsErr(), res.String()) + // Test unsupported fee + res = DeliverCounterTx(true, types.Coins{{"silver", 100}}, 2) + assert.True(res.IsErr(), res.String()) } diff --git a/stack/context.go b/stack/context.go index 6124a96780..80ad743901 100644 --- a/stack/context.go +++ b/stack/context.go @@ -66,6 +66,17 @@ func (c secureContext) HasPermission(perm basecoin.Actor) bool { return false } +func (c secureContext) GetPermissions(chain, app string) (res []basecoin.Actor) { + for _, p := range c.perms { + if chain == p.ChainID { + if app == "" || app == p.App { + res = append(res, p) + } + } + } + return res +} + // IsParent ensures that this is derived from the given secureClient func (c secureContext) IsParent(other basecoin.Context) bool { so, ok := other.(secureContext) diff --git a/stack/dispatcher.go b/stack/dispatcher.go index 6e177ca098..5da79f7a04 100644 --- a/stack/dispatcher.go +++ b/stack/dispatcher.go @@ -56,8 +56,9 @@ func (d *Dispatcher) CheckTx(ctx basecoin.Context, store types.KVStore, tx basec if err != nil { return res, err } - // TODO: callback - return r.CheckTx(ctx, store, tx, nil) + // TODO: check on callback + cb := d + return r.CheckTx(ctx, store, tx, cb) } func (d *Dispatcher) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { @@ -65,8 +66,9 @@ func (d *Dispatcher) DeliverTx(ctx basecoin.Context, store types.KVStore, tx bas if err != nil { return res, err } - // TODO: callback - return r.DeliverTx(ctx, store, tx, nil) + // TODO: check on callback + cb := d + return r.DeliverTx(ctx, store, tx, cb) } func (d *Dispatcher) SetOption(l log.Logger, store types.KVStore, module, key, value string) (string, error) { @@ -74,8 +76,9 @@ func (d *Dispatcher) SetOption(l log.Logger, store types.KVStore, module, key, v if err != nil { return "", err } - // TODO: callback - return r.SetOption(l, store, module, key, value, nil) + // TODO: check on callback + cb := d + return r.SetOption(l, store, module, key, value, cb) } func (d *Dispatcher) lookupTx(tx basecoin.Tx) (Dispatchable, error) { diff --git a/stack/interface.go b/stack/interface.go index 09e1ad4d16..3b5b5d4e61 100644 --- a/stack/interface.go +++ b/stack/interface.go @@ -67,6 +67,12 @@ func (_ PassOption) SetOption(l log.Logger, store types.KVStore, module, key, va return next.SetOption(l, store, module, key, value) } +type NopOption struct{} + +func (_ NopOption) SetOption(l log.Logger, store types.KVStore, module, key, value string, next basecoin.SetOptioner) (string, error) { + return "", nil +} + // Dispatchable is like middleware, except the meaning of "next" is different. // Whereas in the middleware, it is the next handler that we should pass the same tx into, // for dispatchers, it is a dispatcher, which it can use to diff --git a/stack/mock.go b/stack/mock.go index 443f83dfd8..781f13d4fb 100644 --- a/stack/mock.go +++ b/stack/mock.go @@ -44,6 +44,17 @@ func (c mockContext) HasPermission(perm basecoin.Actor) bool { return false } +func (c mockContext) GetPermissions(chain, app string) (res []basecoin.Actor) { + for _, p := range c.perms { + if chain == p.ChainID { + if app == "" || app == p.App { + res = append(res, p) + } + } + } + return res +} + // IsParent ensures that this is derived from the given secureClient func (c mockContext) IsParent(other basecoin.Context) bool { _, ok := other.(mockContext) diff --git a/tests/cli/counter.sh b/tests/cli/counter.sh index 72ebbc9df7..e4d7352f32 100755 --- a/tests/cli/counter.sh +++ b/tests/cli/counter.sh @@ -70,8 +70,12 @@ test03AddCount() { HASH=$(echo $TX | jq .hash | tr -d \") TX_HEIGHT=$(echo $TX | jq .height) + # make sure the counter was updated checkCounter "1" "10" + # make sure the account was debited + checkAccount $SENDER "2" "9007199254739990" + # make sure tx is indexed TX=$(${CLIENT_EXE} query tx $HASH --trace) if assertTrue "found tx" $?; then