Fix up BasecoinApp and tests

This commit is contained in:
Ethan Frey 2017-07-03 21:34:08 +02:00
parent 7c4f408934
commit fc44de2141
6 changed files with 94 additions and 75 deletions

View File

@ -1,8 +1,6 @@
package app
import (
"strings"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin"
eyes "github.com/tendermint/merkleeyes/client"
@ -89,8 +87,10 @@ func (app *Basecoin) DeliverTx(txBytes []byte) abci.Result {
// TODO: can we abstract this setup and commit logic??
cache := app.state.CacheWrap()
ctx := stack.NewContext(app.state.GetChainID(),
app.logger.With("call", "delivertx"))
ctx := stack.NewContext(
app.state.GetChainID(),
app.logger.With("call", "delivertx"),
)
res, err := app.handler.DeliverTx(ctx, cache, tx)
if err != nil {
@ -110,8 +110,10 @@ func (app *Basecoin) CheckTx(txBytes []byte) abci.Result {
}
// TODO: can we abstract this setup and commit logic??
ctx := stack.NewContext(app.state.GetChainID(),
app.logger.With("call", "checktx"))
ctx := stack.NewContext(
app.state.GetChainID(),
app.logger.With("call", "checktx"),
)
// checktx generally shouldn't touch the state, but we don't care
// here on the framework level, since the cacheState is thrown away next block
res, err := app.handler.CheckTx(ctx, app.cacheState, tx)
@ -176,15 +178,3 @@ func (app *Basecoin) EndBlock(height uint64) (res abci.ResponseEndBlock) {
// }
return
}
//----------------------------------------
// Splits the string at the first '/'.
// if there are none, the second string is nil.
func splitKey(key string) (prefix string, suffix string) {
if strings.Contains(key, "/") {
keyParts := strings.SplitN(key, "/", 2)
return keyParts[0], keyParts[1]
}
return key, ""
}

View File

@ -9,7 +9,12 @@ import (
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/modules/coin"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/txs"
"github.com/tendermint/basecoin/types"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
@ -36,10 +41,17 @@ func newAppTest(t *testing.T) *appTest {
}
// make a tx sending 5mycoin from each accIn to accOut
func (at *appTest) getTx(seq int) *types.SendTx {
tx := types.MakeSendTx(seq, at.accOut, at.accIn)
types.SignTx(at.chainID, tx, at.accIn)
return tx
func (at *appTest) getTx(seq int, coins types.Coins) basecoin.Tx {
addrIn := at.accIn.Account.PubKey.Address()
addrOut := at.accOut.Account.PubKey.Address()
in := []coin.TxInput{{Address: stack.SigPerm(addrIn), Coins: coins, Sequence: seq}}
out := []coin.TxOutput{{Address: stack.SigPerm(addrOut), Coins: coins}}
tx := coin.NewSendTx(in, out)
tx = txs.NewChain(at.chainID, tx)
stx := txs.NewMulti(tx)
txs.Sign(stx, at.accIn.PrivKey)
return stx.Wrap()
}
// set the account on the app through SetOption
@ -69,45 +81,51 @@ func (at *appTest) reset() {
require.True(at.t, resabci.IsOK(), resabci)
}
func getBalance(pk crypto.PubKey, state types.KVStore) (types.Coins, error) {
return getAddr(pk.Address(), state)
}
func getAddr(addr []byte, state types.KVStore) (types.Coins, error) {
actor := stack.SigPerm(addr)
acct, err := coin.NewAccountant("").GetAccount(state, actor)
return acct.Coins, err
}
// returns the final balance and expected balance for input and output accounts
func (at *appTest) exec(tx *types.SendTx, checkTx bool) (res abci.Result, inputGot, inputExp, outputGot, outputExpected types.Coins) {
func (at *appTest) exec(t *testing.T, tx basecoin.Tx, checkTx bool) (res abci.Result, diffIn, diffOut types.Coins) {
require := require.New(t)
initBalIn := at.app.GetState().GetAccount(at.accIn.Account.PubKey.Address()).Balance
initBalOut := at.app.GetState().GetAccount(at.accOut.Account.PubKey.Address()).Balance
initBalIn, err := getBalance(at.accIn.Account.PubKey, at.app.GetState())
require.Nil(err, "%+v", err)
initBalOut, err := getBalance(at.accOut.Account.PubKey, at.app.GetState())
require.Nil(err, "%+v", err)
txBytes := []byte(wire.BinaryBytes(struct{ types.Tx }{tx}))
txBytes := wire.BinaryBytes(tx)
if checkTx {
res = at.app.CheckTx(txBytes)
} else {
res = at.app.DeliverTx(txBytes)
}
endBalIn := at.app.GetState().GetAccount(at.accIn.Account.PubKey.Address()).Balance
endBalOut := at.app.GetState().GetAccount(at.accOut.Account.PubKey.Address()).Balance
decrBalInExp := tx.Outputs[0].Coins.Plus(types.Coins{tx.Fee})
return res, endBalIn, initBalIn.Minus(decrBalInExp), endBalOut, initBalOut.Plus(tx.Outputs[0].Coins)
endBalIn, err := getBalance(at.accIn.Account.PubKey, at.app.GetState())
require.Nil(err, "%+v", err)
endBalOut, err := getBalance(at.accOut.Account.PubKey, at.app.GetState())
require.Nil(err, "%+v", err)
return res, endBalIn.Minus(initBalIn), endBalOut.Minus(initBalOut)
}
//--------------------------------------------------------
func TestSplitKey(t *testing.T) {
assert := assert.New(t)
prefix, suffix := splitKey("foo/bar")
assert.EqualValues("foo", prefix)
assert.EqualValues("bar", suffix)
prefix, suffix = splitKey("foobar")
assert.EqualValues("foobar", prefix)
assert.EqualValues("", suffix)
}
func TestSetOption(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
eyesCli := eyes.NewLocalClient("", 0)
app := NewBasecoin(DefaultHandler(), eyesCli,
log.TestingLogger().With("module", "app"))
app := NewBasecoin(
DefaultHandler(),
eyesCli,
log.TestingLogger().With("module", "app"),
)
//testing ChainID
chainID := "testChain"
@ -116,15 +134,16 @@ func TestSetOption(t *testing.T) {
assert.EqualValues(res, "Success")
// make a nice account...
accIn := types.MakeAcc("input0")
accsInBytes, err := json.Marshal(accIn.Account)
accIn := types.MakeAcc("input0").Account
accsInBytes, err := json.Marshal(accIn)
assert.Nil(err)
res = app.SetOption("base/account", string(accsInBytes))
require.EqualValues(res, "Success")
// make sure it is set correctly, with some balance
acct := types.GetAccount(app.GetState(), accIn.PubKey.Address())
require.NotNil(acct)
assert.Equal(accIn.Balance, acct.Balance)
coins, err := getBalance(accIn.PubKey, app.state)
require.Nil(err)
assert.Equal(accIn.Balance, coins)
// let's parse an account with badly sorted coins...
unsortAddr, err := hex.DecodeString("C471FB670E44D219EE6DF2FC284BE38793ACBCE1")
@ -148,10 +167,11 @@ func TestSetOption(t *testing.T) {
}`
res = app.SetOption("base/account", unsortAcc)
require.EqualValues(res, "Success")
acct = types.GetAccount(app.GetState(), unsortAddr)
require.NotNil(acct)
assert.True(acct.Balance.IsValid())
assert.Equal(unsortCoins, acct.Balance)
coins, err = getAddr(unsortAddr, app.state)
require.Nil(err)
assert.True(coins.IsValid())
assert.Equal(unsortCoins, coins)
res = app.SetOption("base/dslfkgjdas", "")
assert.NotEqual(res, "Success")
@ -172,33 +192,32 @@ func TestTx(t *testing.T) {
//Bad Balance
at.accIn.Balance = types.Coins{{"mycoin", 2}}
at.acc2app(at.accIn.Account)
res, _, _, _, _ := at.exec(at.getTx(1), true)
res, _, _ := at.exec(t, at.getTx(1, types.Coins{{"mycoin", 5}}), true)
assert.True(res.IsErr(), "ExecTx/Bad CheckTx: Expected error return from ExecTx, returned: %v", res)
res, inGot, inExp, outGot, outExp := at.exec(at.getTx(1), false)
res, diffIn, diffOut := at.exec(t, at.getTx(1, types.Coins{{"mycoin", 5}}), false)
assert.True(res.IsErr(), "ExecTx/Bad DeliverTx: Expected error return from ExecTx, returned: %v", res)
assert.False(inGot.IsEqual(inExp), "ExecTx/Bad DeliverTx: shouldn't be equal, inGot: %v, inExp: %v", inGot, inExp)
assert.False(outGot.IsEqual(outExp), "ExecTx/Bad DeliverTx: shouldn't be equal, outGot: %v, outExp: %v", outGot, outExp)
assert.True(diffIn.IsZero())
assert.True(diffOut.IsZero())
//Regular CheckTx
at.reset()
res, _, _, _, _ = at.exec(at.getTx(1), true)
res, _, _ = at.exec(t, at.getTx(1, types.Coins{{"mycoin", 5}}), true)
assert.True(res.IsOK(), "ExecTx/Good CheckTx: Expected OK return from ExecTx, Error: %v", res)
//Regular DeliverTx
at.reset()
res, inGot, inExp, outGot, outExp = at.exec(at.getTx(1), false)
amt := types.Coins{{"mycoin", 3}}
res, diffIn, diffOut = at.exec(t, at.getTx(1, amt), false)
assert.True(res.IsOK(), "ExecTx/Good DeliverTx: Expected OK return from ExecTx, Error: %v", res)
assert.True(inGot.IsEqual(inExp),
"ExecTx/good DeliverTx: unexpected change in input coins, inGot: %v, inExp: %v", inGot, inExp)
assert.True(outGot.IsEqual(outExp),
"ExecTx/good DeliverTx: unexpected change in output coins, outGot: %v, outExp: %v", outGot, outExp)
assert.Equal(amt.Negative(), diffIn)
assert.Equal(amt, diffOut)
}
func TestQuery(t *testing.T) {
assert := assert.New(t)
at := newAppTest(t)
res, _, _, _, _ := at.exec(at.getTx(1), false)
res, _, _ := at.exec(t, at.getTx(1, types.Coins{{"mycoin", 5}}), false)
assert.True(res.IsOK(), "Commit, DeliverTx: Expected OK return from DeliverTx, Error: %v", res)
resQueryPreCommit := at.app.Query(abci.RequestQuery{

View File

@ -15,7 +15,6 @@ type Handler interface {
SetOptioner
Named
// TODO: flesh these out as well
// SetOption(store types.KVStore, key, value string) (log string)
// InitChain(store types.KVStore, vals []*abci.Validator)
// BeginBlock(store types.KVStore, hash []byte, header *abci.Header)
// EndBlock(store types.KVStore, height uint64) abci.ResponseEndBlock

View File

@ -8,6 +8,7 @@ import (
"github.com/tendermint/basecoin"
"github.com/tendermint/basecoin/errors"
"github.com/tendermint/basecoin/stack"
"github.com/tendermint/basecoin/types"
)
@ -24,7 +25,7 @@ var _ basecoin.Handler = Handler{}
func NewHandler() Handler {
return Handler{
Accountant: Accountant{Prefix: []byte(NameCoin + "/")},
Accountant: NewAccountant(""),
}
}
@ -41,7 +42,7 @@ func (h Handler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.
// now make sure there is money
for _, in := range send.Inputs {
_, err = h.CheckCoins(store, in.Address, in.Coins, in.Sequence)
_, err = h.CheckCoins(store, in.Address, in.Coins.Negative(), in.Sequence)
if err != nil {
return res, err
}
@ -91,8 +92,9 @@ func (h Handler) SetOption(l log.Logger, store types.KVStore, key, value string)
if err != nil {
return "", ErrInvalidAddress()
}
actor := basecoin.Actor{App: NameCoin, Address: addr}
err = storeAccount(store, h.makeKey(actor), acc.ToAccount())
// this sets the permission for a public key signature, use that app
actor := stack.SigPerm(addr)
err = storeAccount(store, h.MakeKey(actor), acc.ToAccount())
if err != nil {
return "", err
}

View File

@ -143,7 +143,7 @@ func TestDeliverTx(t *testing.T) {
store := types.NewMemKVStore()
for _, m := range tc.init {
acct := Account{Coins: m.coins}
err := storeAccount(store, h.makeKey(m.addr), acct)
err := storeAccount(store, h.MakeKey(m.addr), acct)
require.Nil(err, "%d: %+v", i, err)
}
@ -153,7 +153,7 @@ func TestDeliverTx(t *testing.T) {
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))
acct, err := loadAccount(store, h.MakeKey(f.addr))
assert.Nil(err, "%d: %+v", i, err)
assert.Equal(f.coins, acct.Coins)
}
@ -210,7 +210,7 @@ func TestSetOption(t *testing.T) {
// check state is proper
for _, f := range tc.expected {
acct, err := loadAccount(store, h.makeKey(f.addr))
acct, err := loadAccount(store, h.MakeKey(f.addr))
assert.Nil(err, "%d: %+v", i, err)
assert.Equal(f.coins, acct.Coins)
}

View File

@ -14,8 +14,17 @@ type Accountant struct {
Prefix []byte
}
func NewAccountant(prefix string) Accountant {
if prefix == "" {
prefix = NameCoin
}
return Accountant{
Prefix: []byte(prefix + "/"),
}
}
func (a Accountant) GetAccount(store types.KVStore, addr basecoin.Actor) (Account, error) {
acct, err := loadAccount(store, a.makeKey(addr))
acct, err := loadAccount(store, a.MakeKey(addr))
// for empty accounts, don't return an error, but rather an empty account
if IsNoAccountErr(err) {
err = nil
@ -36,7 +45,7 @@ func (a Accountant) ChangeCoins(store types.KVStore, addr basecoin.Actor, coins
return acct.Coins, err
}
err = storeAccount(store, a.makeKey(addr), acct)
err = storeAccount(store, a.MakeKey(addr), acct)
return acct.Coins, err
}
@ -44,7 +53,7 @@ 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))
acct, err = loadAccount(store, a.MakeKey(addr))
// we can increase an empty account...
if IsNoAccountErr(err) && coins.IsPositive() {
err = nil
@ -71,7 +80,7 @@ func (a Accountant) updateCoins(store types.KVStore, addr basecoin.Actor, coins
return acct, nil
}
func (a Accountant) makeKey(addr basecoin.Actor) []byte {
func (a Accountant) MakeKey(addr basecoin.Actor) []byte {
key := addr.Bytes()
if len(a.Prefix) > 0 {
key = append(a.Prefix, key...)