Connect coin handler to a store and test it

This commit is contained in:
Ethan Frey 2017-07-03 15:33:59 +02:00
parent 225904b010
commit c5a1b4883a
3 changed files with 146 additions and 30 deletions

View File

@ -29,12 +29,18 @@ func (_ Handler) Name() string {
// CheckTx checks if there is enough money in the account
func (h Handler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
_, err = checkTx(ctx, tx)
send, err := checkTx(ctx, tx)
if err != nil {
return res, err
}
// now make sure there is money
for _, in := range send.Inputs {
_, err = h.CheckCoins(store, in.Address, in.Coins, in.Sequence)
if err != nil {
return res, err
}
}
// otherwise, we are good
return res, nil
@ -42,12 +48,29 @@ func (h Handler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.
// DeliverTx moves the money
func (h Handler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
_, err = checkTx(ctx, tx)
send, err := checkTx(ctx, tx)
if err != nil {
return res, err
}
// now move the money
// deduct from all input accounts
for _, in := range send.Inputs {
_, err = h.ChangeCoins(store, in.Address, in.Coins.Negative(), in.Sequence)
if err != nil {
return res, err
}
}
// add to all output accounts
for _, out := range send.Outputs {
// note: sequence number is ignored when adding coins, only checked for subtracting
_, err = h.ChangeCoins(store, out.Address, out.Coins, 0)
if err != nil {
return res, err
}
}
// a-ok!
return basecoin.Result{}, nil
}

View File

@ -4,72 +4,159 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/types"
)
func TestHandlerPermissions(t *testing.T) {
// this makes sure that txs are rejected with invalid data or permissions
func TestHandlerValidation(t *testing.T) {
assert := assert.New(t)
// TODO: need to update this when we actually have token store
h := NewHandler()
// these are all valid, except for minusCoins
addr1 := basecoin.Actor{App: "coin", Address: []byte{1, 2}}
addr2 := basecoin.Actor{App: "role", Address: []byte{7, 8}}
someCoins := types.Coins{{"atom", 123}}
doubleCoins := types.Coins{{"atom", 246}}
minusCoins := types.Coins{{"eth", -34}}
cases := []struct {
valid bool
tx SendTx
tx basecoin.Tx
perms []basecoin.Actor
}{
// auth works with different apps
{true,
SendTx{
Inputs: []TxInput{NewTxInput(addr1, someCoins, 2)},
Outputs: []TxOutput{NewTxOutput(addr2, someCoins)}},
NewSendTx(
[]TxInput{NewTxInput(addr1, someCoins, 2)},
[]TxOutput{NewTxOutput(addr2, someCoins)}),
[]basecoin.Actor{addr1}},
{true,
SendTx{
Inputs: []TxInput{NewTxInput(addr2, someCoins, 2)},
Outputs: []TxOutput{NewTxOutput(addr1, someCoins)}},
NewSendTx(
[]TxInput{NewTxInput(addr2, someCoins, 2)},
[]TxOutput{NewTxOutput(addr1, someCoins)}),
[]basecoin.Actor{addr1, addr2}},
// check multi-input with both sigs
{true,
NewSendTx(
[]TxInput{NewTxInput(addr1, someCoins, 2), NewTxInput(addr2, someCoins, 3)},
[]TxOutput{NewTxOutput(addr1, doubleCoins)}),
[]basecoin.Actor{addr1, addr2}},
// wrong permissions fail
{false,
SendTx{
Inputs: []TxInput{NewTxInput(addr1, someCoins, 2)},
Outputs: []TxOutput{NewTxOutput(addr2, someCoins)}},
NewSendTx(
[]TxInput{NewTxInput(addr1, someCoins, 2)},
[]TxOutput{NewTxOutput(addr2, someCoins)}),
[]basecoin.Actor{}},
{false,
SendTx{
Inputs: []TxInput{NewTxInput(addr1, someCoins, 2)},
Outputs: []TxOutput{NewTxOutput(addr2, someCoins)}},
NewSendTx(
[]TxInput{NewTxInput(addr1, someCoins, 2)},
[]TxOutput{NewTxOutput(addr2, someCoins)}),
[]basecoin.Actor{addr2}},
{false,
NewSendTx(
[]TxInput{NewTxInput(addr1, someCoins, 2), NewTxInput(addr2, someCoins, 3)},
[]TxOutput{NewTxOutput(addr1, doubleCoins)}),
[]basecoin.Actor{addr1}},
// invalid input fails
{false,
SendTx{
Inputs: []TxInput{NewTxInput(addr1, minusCoins, 2)},
Outputs: []TxOutput{NewTxOutput(addr2, minusCoins)}},
NewSendTx(
[]TxInput{NewTxInput(addr1, minusCoins, 2)},
[]TxOutput{NewTxOutput(addr2, minusCoins)}),
[]basecoin.Actor{addr2}},
}
for i, tc := range cases {
ctx := stack.MockContext().WithPermissions(tc.perms...)
_, err := h.CheckTx(ctx, nil, tc.tx.Wrap())
_, err := checkTx(ctx, tc.tx)
if tc.valid {
assert.Nil(err, "%d: %+v", i, err)
} else {
assert.NotNil(err, "%d", i)
}
}
}
_, err = h.DeliverTx(ctx, nil, tc.tx.Wrap())
if tc.valid {
func TestDeliverTx(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
// some sample settings
addr1 := basecoin.Actor{App: "coin", Address: []byte{1, 2}}
addr2 := basecoin.Actor{App: "role", Address: []byte{7, 8}}
addr3 := basecoin.Actor{App: "coin", Address: []byte{6, 5, 4, 3}}
someCoins := types.Coins{{"atom", 123}}
moreCoins := types.Coins{{"atom", 6487}}
diffCoins := moreCoins.Minus(someCoins)
otherCoins := types.Coins{{"eth", 11}}
mixedCoins := someCoins.Plus(otherCoins)
type money struct {
addr basecoin.Actor
coins types.Coins
}
cases := []struct {
init []money
tx basecoin.Tx
perms []basecoin.Actor
final []money // nil for error
}{
{
[]money{{addr1, moreCoins}},
NewSendTx(
[]TxInput{NewTxInput(addr1, someCoins, 1)},
[]TxOutput{NewTxOutput(addr2, someCoins)}),
[]basecoin.Actor{addr1},
[]money{{addr1, diffCoins}, {addr2, someCoins}},
},
// simple multi-sig 2 accounts to 1
{
[]money{{addr1, mixedCoins}, {addr2, moreCoins}},
NewSendTx(
[]TxInput{NewTxInput(addr1, otherCoins, 1), NewTxInput(addr2, someCoins, 1)},
[]TxOutput{NewTxOutput(addr3, mixedCoins)}),
[]basecoin.Actor{addr1, addr2},
[]money{{addr1, someCoins}, {addr2, diffCoins}, {addr3, mixedCoins}},
},
// multi-sig with one account sending many times
{
[]money{{addr1, moreCoins.Plus(otherCoins)}},
NewSendTx(
[]TxInput{NewTxInput(addr1, otherCoins, 1), NewTxInput(addr1, someCoins, 2)},
[]TxOutput{NewTxOutput(addr2, mixedCoins)}),
[]basecoin.Actor{addr1},
[]money{{addr1, diffCoins}, {addr2, mixedCoins}},
},
}
h := NewHandler()
for i, tc := range cases {
// setup the cases....
store := types.NewMemKVStore()
for _, m := range tc.init {
acct := Account{Coins: m.coins}
err := storeAccount(store, h.makeKey(m.addr), acct)
require.Nil(err, "%d: %+v", i, err)
}
ctx := stack.MockContext().WithPermissions(tc.perms...)
_, err := h.DeliverTx(ctx, store, tc.tx)
if len(tc.final) > 0 { // valid
assert.Nil(err, "%d: %+v", i, err)
// make sure the final balances are correct
for _, f := range tc.final {
acct, err := loadAccount(store, h.makeKey(f.addr))
assert.Nil(err, "%d: %+v", i, err)
assert.Equal(f.coins, acct.Coins)
}
} else {
assert.NotNil(err, "%d", i)
// TODO: make sure balances unchanged!
}
}
}

View File

@ -45,18 +45,24 @@ func (a Accountant) ChangeCoins(store types.KVStore, addr basecoin.Actor, coins
// it doesn't save anything, that is up to you to decide (Check/Change Coins)
func (a Accountant) updateCoins(store types.KVStore, addr basecoin.Actor, coins types.Coins, seq int) (acct Account, err error) {
acct, err = loadAccount(store, a.makeKey(addr))
// we can increase an empty account...
if IsNoAccountErr(err) && coins.IsPositive() {
err = nil
}
if err != nil {
return acct, err
}
// check sequence
if seq != acct.Sequence+1 {
return acct, ErrInvalidSequence()
// check sequence if we are deducting... ugh, need a cleaner replay protection
if !coins.IsPositive() {
if seq != acct.Sequence+1 {
return acct, ErrInvalidSequence()
}
acct.Sequence += 1
}
acct.Sequence += 1
// check amount
final := acct.Coins.Minus(coins)
final := acct.Coins.Plus(coins)
if !final.IsNonnegative() {
return acct, ErrInsufficientFunds()
}