Counter uses dispatcher to deduct fees from account

This commit is contained in:
Ethan Frey 2017-07-04 14:47:46 +02:00
parent 8003034bbb
commit 670e7b48d1
9 changed files with 92 additions and 29 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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"`

View File

@ -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())
}

View File

@ -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)

View File

@ -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) {

View File

@ -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

View File

@ -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)

View File

@ -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