Add CreditTx and tests

This commit is contained in:
Ethan Frey 2017-07-21 23:28:44 +02:00
parent 6135345af8
commit 3027eeb3c3
5 changed files with 217 additions and 58 deletions

View File

@ -10,12 +10,13 @@ import (
)
var (
errNoAccount = fmt.Errorf("No such account")
errInsufficientFunds = fmt.Errorf("Insufficient funds")
errNoInputs = fmt.Errorf("No input coins")
errNoOutputs = fmt.Errorf("No output coins")
errInvalidAddress = fmt.Errorf("Invalid address")
errInvalidCoins = fmt.Errorf("Invalid coins")
errNoAccount = fmt.Errorf("No such account")
errInsufficientFunds = fmt.Errorf("Insufficient funds")
errInsufficientCredit = fmt.Errorf("Insufficient credit")
errNoInputs = fmt.Errorf("No input coins")
errNoOutputs = fmt.Errorf("No output coins")
errInvalidAddress = fmt.Errorf("Invalid address")
errInvalidCoins = fmt.Errorf("Invalid coins")
invalidInput = abci.CodeType_BaseInvalidInput
invalidOutput = abci.CodeType_BaseInvalidOutput
@ -66,6 +67,13 @@ func IsInsufficientFundsErr(err error) bool {
return errors.IsSameError(errInsufficientFunds, err)
}
func ErrInsufficientCredit() errors.TMError {
return errors.WithCode(errInsufficientCredit, invalidInput)
}
func IsInsufficientCreditErr(err error) bool {
return errors.IsSameError(errInsufficientCredit, err)
}
func ErrNoInputs() errors.TMError {
return errors.WithCode(errNoInputs, invalidInput)
}

View File

@ -37,28 +37,57 @@ func (Handler) AssertDispatcher() {}
func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB,
tx basecoin.Tx, _ basecoin.Checker) (res basecoin.Result, err error) {
send, err := checkTx(ctx, tx)
err = tx.ValidateBasic()
if err != nil {
return res, err
}
// now make sure there is money
for _, in := range send.Inputs {
_, err = CheckCoins(store, in.Address, in.Coins.Negative())
if err != nil {
return res, err
}
switch t := tx.Unwrap().(type) {
case SendTx:
return res, h.checkSendTx(ctx, store, t)
case CreditTx:
return h.creditTx(ctx, store, t)
}
// otherwise, we are good
return res, nil
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) {
send, err := checkTx(ctx, tx)
err = tx.ValidateBasic()
if err != nil {
return res, err
}
switch t := tx.Unwrap().(type) {
case SendTx:
return h.sendTx(ctx, store, t, cb)
case CreditTx:
return 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) {
if module != NameCoin {
return "", errors.ErrUnknownModule(module)
}
switch key {
case "account":
return setAccount(store, value)
case "issuer":
return setIssuer(store, value)
}
return "", errors.ErrUnknownKey(key)
}
func (h Handler) sendTx(ctx basecoin.Context, store state.SimpleDB,
send SendTx, cb basecoin.Deliver) (res basecoin.Result, err error) {
err = checkTx(ctx, send)
if err != nil {
return res, err
}
@ -107,43 +136,65 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB,
return res, nil
}
// SetOption - sets the genesis account balance
func (h Handler) SetOption(l log.Logger, store state.SimpleDB,
module, key, value string, _ basecoin.SetOptioner) (log string, err error) {
func (h Handler) creditTx(ctx basecoin.Context, store state.SimpleDB,
credit CreditTx) (res basecoin.Result, err error) {
if module != NameCoin {
return "", errors.ErrUnknownModule(module)
// first check permissions!!
info, err := loadHandlerInfo(store)
if err != nil {
return res, err
}
switch key {
case "account":
return setAccount(store, value)
case "issuer":
return setIssuer(store, value)
if info.Issuer.Empty() || !ctx.HasPermission(info.Issuer) {
return res, errors.ErrUnauthorized()
}
return "", errors.ErrUnknownKey(key)
// load up the account
addr := ChainAddr(credit.Debitor)
acct, err := GetAccount(store, addr)
if err != nil {
return res, err
}
// make and check changes
acct.Coins = acct.Coins.Plus(credit.Credit)
if !acct.Coins.IsNonnegative() {
return res, ErrInsufficientFunds()
}
acct.Credit = acct.Credit.Plus(credit.Credit)
if !acct.Credit.IsNonnegative() {
return res, ErrInsufficientCredit()
}
err = storeAccount(store, addr.Bytes(), acct)
return res, err
}
func checkTx(ctx basecoin.Context, tx basecoin.Tx) (send SendTx, err error) {
// check if the tx is proper type and valid
send, ok := tx.Unwrap().(SendTx)
if !ok {
return send, errors.ErrInvalidFormat(TypeSend, tx)
}
err = send.ValidateBasic()
if err != nil {
return send, err
}
func checkTx(ctx basecoin.Context, send SendTx) error {
// check if all inputs have permission
for _, in := range send.Inputs {
if !ctx.HasPermission(in.Address) {
return send, errors.ErrUnauthorized()
return errors.ErrUnauthorized()
}
}
return send, nil
return nil
}
func setAccount(store state.KVStore, value string) (log string, err error) {
func (Handler) checkSendTx(ctx basecoin.Context, store state.SimpleDB, send SendTx) error {
err := checkTx(ctx, send)
if err != nil {
return err
}
// now make sure there is money
for _, in := range send.Inputs {
_, err := CheckCoins(store, in.Address, in.Coins.Negative())
if err != nil {
return err
}
}
return nil
}
func setAccount(store state.SimpleDB, value string) (log string, err error) {
var acc GenesisAccount
err = data.FromJSON([]byte(value), &acc)
if err != nil {
@ -165,7 +216,7 @@ func setAccount(store state.KVStore, value string) (log string, err error) {
// setIssuer sets a permission for some super-powerful account to
// mint money
func setIssuer(store state.KVStore, value string) (log string, err error) {
func setIssuer(store state.SimpleDB, value string) (log string, err error) {
var issuer basecoin.Actor
err = data.FromJSON([]byte(value), &issuer)
if err != nil {

View File

@ -11,6 +11,7 @@ import (
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/modules/auth"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/state"
@ -75,7 +76,7 @@ func TestHandlerValidation(t *testing.T) {
for i, tc := range cases {
ctx := stack.MockContext("base-chain", 100).WithPermissions(tc.perms...)
_, err := checkTx(ctx, tc.tx)
err := checkTx(ctx, tc.tx.Unwrap().(SendTx))
if tc.valid {
assert.Nil(err, "%d: %+v", i, err)
} else {
@ -84,7 +85,7 @@ func TestHandlerValidation(t *testing.T) {
}
}
func TestDeliverTx(t *testing.T) {
func TestDeliverSendTx(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
@ -247,3 +248,109 @@ func TestSetIssuer(t *testing.T) {
assert.Equal(tc.issuer, info.Issuer)
}
}
func TestDeliverCreditTx(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
// sample coins
someCoins := Coins{{"atom", 6570}}
minusCoins := Coins{{"atom", -1234}}
lessCoins := someCoins.Plus(minusCoins)
otherCoins := Coins{{"eth", 11}}
mixedCoins := someCoins.Plus(otherCoins)
// some sample addresses
owner := basecoin.Actor{App: "foo", Address: []byte("rocks")}
addr1 := basecoin.Actor{App: "coin", Address: []byte{1, 2}}
key := NewAccountWithKey(someCoins)
addr2 := key.Actor()
addr3 := basecoin.Actor{ChainID: "other", App: "sigs", Address: []byte{3, 9}}
h := NewHandler()
store := state.NewMemKVStore()
ctx := stack.MockContext("secret", 77)
// 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)
require.Nil(err, "%+v", err)
// give addr2 some coins to start
_, err = h.SetOption(log.NewNopLogger(), store, "coin", "account", key.MakeOption(), nil)
require.Nil(err, "%+v", err)
cases := []struct {
tx basecoin.Tx
perm basecoin.Actor
check errors.CheckErr
addr basecoin.Actor
expected Account
}{
// require permission
{
tx: NewCreditTx(addr1, someCoins),
check: errors.IsUnauthorizedErr,
},
// add credit
{
tx: NewCreditTx(addr1, someCoins),
perm: owner,
check: errors.NoErr,
addr: addr1,
expected: Account{Coins: someCoins, Credit: someCoins},
},
// remove some
{
tx: NewCreditTx(addr1, minusCoins),
perm: owner,
check: errors.NoErr,
addr: addr1,
expected: Account{Coins: lessCoins, Credit: lessCoins},
},
// can't remove more cash than there is
{
tx: NewCreditTx(addr1, otherCoins.Negative()),
perm: owner,
check: IsInsufficientFundsErr,
},
// cumulative with initial state
{
tx: NewCreditTx(addr2, otherCoins),
perm: owner,
check: errors.NoErr,
addr: addr2,
expected: Account{Coins: mixedCoins, Credit: otherCoins},
},
// Even if there is cash, credit can't go negative
{
tx: NewCreditTx(addr2, minusCoins),
perm: owner,
check: IsInsufficientCreditErr,
},
// make sure it works for other chains
{
tx: NewCreditTx(addr3, mixedCoins),
perm: owner,
check: errors.NoErr,
addr: ChainAddr(addr3),
expected: Account{Coins: mixedCoins, Credit: mixedCoins},
},
}
for i, tc := range cases {
myStore := store.Checkpoint()
myCtx := ctx.WithPermissions(tc.perm)
_, err = h.DeliverTx(myCtx, myStore, tc.tx, nil)
assert.True(tc.check(err), "%d: %+v", i, err)
if err == nil {
store.Commit(myStore)
acct, err := GetAccount(store, tc.addr)
require.Nil(err, "%+v", err)
assert.Equal(tc.expected, acct, "%d", i)
}
}
}

View File

@ -117,7 +117,7 @@ func TestIBCPostPacket(t *testing.T) {
}
func assertPacket(t *testing.T, istore state.KVStore, destID string, amount Coins) {
func assertPacket(t *testing.T, istore state.SimpleDB, destID string, amount Coins) {
assert := assert.New(t)
require := require.New(t)

View File

@ -78,12 +78,6 @@ func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin
switch t := tx.Unwrap().(type) {
case RegisterChainTx:
// check permission to attach, do it here, so no permission check
// by SetOption
info := LoadInfo(store)
if !info.Registrar.Empty() && !ctx.HasPermission(info.Registrar) {
return res, errors.ErrUnauthorized()
}
return h.initSeed(ctx, store, t)
case UpdateChainTx:
return h.updateSeed(ctx, store, t)
@ -103,12 +97,6 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx baseco
switch t := tx.Unwrap().(type) {
case RegisterChainTx:
// check permission to attach, do it here, so no permission check
// by SetOption
info := LoadInfo(store)
if !info.Registrar.Empty() && !ctx.HasPermission(info.Registrar) {
return res, errors.ErrUnauthorized()
}
return h.initSeed(ctx, store, t)
case UpdateChainTx:
return h.updateSeed(ctx, store, t)
@ -125,6 +113,11 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx baseco
func (h Handler) initSeed(ctx basecoin.Context, store state.SimpleDB,
t RegisterChainTx) (res basecoin.Result, err error) {
info := LoadInfo(store)
if !info.Registrar.Empty() && !ctx.HasPermission(info.Registrar) {
return res, errors.ErrUnauthorized()
}
// verify that the header looks reasonable
chainID := t.ChainID()
s := NewChainSet(store)