diff --git a/app/app.go b/app/app.go index b29186a24a..69052a0ee3 100644 --- a/app/app.go +++ b/app/app.go @@ -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, "" -} diff --git a/app/app_test.go b/app/app_test.go index 1645853206..ab007ce374 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -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{ diff --git a/handler.go b/handler.go index b0089ed280..3e3728a09b 100644 --- a/handler.go +++ b/handler.go @@ -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 diff --git a/modules/coin/handler.go b/modules/coin/handler.go index a8919d835f..63cf3d693b 100644 --- a/modules/coin/handler.go +++ b/modules/coin/handler.go @@ -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 } diff --git a/modules/coin/handler_test.go b/modules/coin/handler_test.go index 7f1e0b12b5..534b5ee168 100644 --- a/modules/coin/handler_test.go +++ b/modules/coin/handler_test.go @@ -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) } diff --git a/modules/coin/store.go b/modules/coin/store.go index 15a280131d..46a1c93cd0 100644 --- a/modules/coin/store.go +++ b/modules/coin/store.go @@ -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...)