diff --git a/Makefile b/Makefile index 0d3eac4ceb..089ee47c4a 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ test_cli: tests/cli/shunit2 ./tests/cli/roles.sh ./tests/cli/counter.sh ./tests/cli/restart.sh - # @./tests/cli/ibc.sh + ./tests/cli/ibc.sh test_tutorial: docs/guide/shunit2 @shelldown ${TUTORIALS} diff --git a/app/app_test.go b/app/app_test.go index 5ea8cc59b2..4bef8443c8 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -25,7 +25,6 @@ import ( // DefaultHandler for the tests (coin, roles, ibc) func DefaultHandler(feeDenom string) basecoin.Handler { // use the default stack - c := coin.NewHandler() r := roles.NewHandler() i := ibc.NewHandler() @@ -44,7 +43,7 @@ func DefaultHandler(feeDenom string) basecoin.Handler { stack.Checkpoint{OnDeliver: true}, ). Dispatch( - stack.WrapHandler(c), + coin.NewHandler(), stack.WrapHandler(r), stack.WrapHandler(i), ) diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 5f98b085d9..b99482e1dd 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -19,11 +19,6 @@ import ( // BuildApp constructs the stack we want to use for this app func BuildApp(feeDenom string) basecoin.Handler { - // use the default stack - c := coin.NewHandler() - r := roles.NewHandler() - i := ibc.NewHandler() - return stack.New( base.Logger{}, stack.Recovery{}, @@ -39,9 +34,9 @@ func BuildApp(feeDenom string) basecoin.Handler { stack.Checkpoint{OnDeliver: true}, ). Dispatch( - stack.WrapHandler(c), - stack.WrapHandler(r), - stack.WrapHandler(i), + coin.NewHandler(), + stack.WrapHandler(roles.NewHandler()), + stack.WrapHandler(ibc.NewHandler()), ) } diff --git a/docs/guide/counter/plugins/counter/counter.go b/docs/guide/counter/plugins/counter/counter.go index 16a10650ca..e9c2b503d7 100644 --- a/docs/guide/counter/plugins/counter/counter.go +++ b/docs/guide/counter/plugins/counter/counter.go @@ -12,7 +12,9 @@ import ( "github.com/tendermint/basecoin/modules/base" "github.com/tendermint/basecoin/modules/coin" "github.com/tendermint/basecoin/modules/fee" + "github.com/tendermint/basecoin/modules/ibc" "github.com/tendermint/basecoin/modules/nonce" + "github.com/tendermint/basecoin/modules/roles" "github.com/tendermint/basecoin/stack" "github.com/tendermint/basecoin/state" ) @@ -90,13 +92,6 @@ func ErrDecoding() error { // NewHandler returns a new counter transaction processing handler func NewHandler(feeDenom string) basecoin.Handler { - // use the default stack - ch := coin.NewHandler() - counter := Handler{} - dispatcher := stack.NewDispatcher( - stack.WrapHandler(ch), - counter, - ) return stack.New( base.Logger{}, stack.Recovery{}, @@ -104,9 +99,17 @@ func NewHandler(feeDenom string) basecoin.Handler { base.Chain{}, stack.Checkpoint{OnCheck: true}, nonce.ReplayCheck{}, - fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank), - stack.Checkpoint{OnDeliver: true}, - ).Use(dispatcher) + ). + IBC(ibc.NewMiddleware()). + Apps( + roles.NewMiddleware(), + fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank), + stack.Checkpoint{OnDeliver: true}, + ). + Dispatch( + coin.NewHandler(), + Handler{}, + ) } // Handler the counter transaction processing handler diff --git a/modules/coin/bench_test.go b/modules/coin/bench_test.go index 34bf39bd1e..bc83ea90cc 100644 --- a/modules/coin/bench_test.go +++ b/modules/coin/bench_test.go @@ -11,7 +11,7 @@ import ( "github.com/tendermint/basecoin/state" ) -func makeHandler() basecoin.Handler { +func makeHandler() stack.Dispatchable { return NewHandler() } @@ -28,7 +28,7 @@ func BenchmarkSimpleTransfer(b *testing.B) { // set the initial account acct := NewAccountWithKey(Coins{{"mycoin", 1234567890}}) - h.SetOption(logger, store, NameCoin, "account", acct.MakeOption()) + h.SetOption(logger, store, NameCoin, "account", acct.MakeOption(), nil) sender := acct.Actor() receiver := basecoin.Actor{App: "foo", Address: cmn.RandBytes(20)} @@ -36,7 +36,7 @@ func BenchmarkSimpleTransfer(b *testing.B) { for i := 1; i <= b.N; i++ { ctx := stack.MockContext("foo", 100).WithPermissions(sender) tx := makeSimpleTx(sender, receiver, Coins{{"mycoin", 2}}) - _, err := h.DeliverTx(ctx, store, tx) + _, err := h.DeliverTx(ctx, store, tx, nil) // never should error if err != nil { panic(err) diff --git a/modules/coin/handler.go b/modules/coin/handler.go index ff1f040376..ffb75aa007 100644 --- a/modules/coin/handler.go +++ b/modules/coin/handler.go @@ -1,12 +1,16 @@ package coin import ( + "fmt" + "github.com/tendermint/go-wire/data" "github.com/tendermint/tmlibs/log" "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/errors" "github.com/tendermint/basecoin/modules/auth" + "github.com/tendermint/basecoin/modules/ibc" + "github.com/tendermint/basecoin/stack" "github.com/tendermint/basecoin/state" ) @@ -16,7 +20,7 @@ const NameCoin = "coin" // Handler includes an accountant type Handler struct{} -var _ basecoin.Handler = Handler{} +var _ stack.Dispatchable = Handler{} // NewHandler - new accountant handler for the coin module func NewHandler() Handler { @@ -28,8 +32,13 @@ func (Handler) Name() string { return NameCoin } +// AssertDispatcher - to fulfill Dispatchable interface +func (Handler) AssertDispatcher() {} + // CheckTx checks if there is enough money in the account -func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) { +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) if err != nil { return res, err @@ -48,34 +57,63 @@ func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin } // DeliverTx moves the money -func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) { +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) if err != nil { return res, err } // deduct from all input accounts + senders := basecoin.Actors{} for _, in := range send.Inputs { _, err = ChangeCoins(store, in.Address, in.Coins.Negative()) if err != nil { return res, err } + senders = append(senders, in.Address) } // add to all output accounts for _, out := range send.Outputs { + // TODO: cleaner way, this makes sure we don't consider + // incoming ibc packets with our chain to be remote packets + if out.Address.ChainID == ctx.ChainID() { + out.Address.ChainID = "" + } + + fmt.Printf("Giving %#v to %#v\n\n", out.Coins, out.Address) _, err = ChangeCoins(store, out.Address, out.Coins) if err != nil { return res, err } + // now send ibc packet if needed... + if out.Address.ChainID != "" { + // FIXME: if there are many outputs, we need to adjust inputs + // so the amounts in and out match. how? + outTx := NewSendTx(send.Inputs, []TxOutput{out}) + packet := ibc.CreatePacketTx{ + DestChain: out.Address.ChainID, + Permissions: senders, + Tx: outTx, + } + ibcCtx := ctx.WithPermissions(ibc.AllowIBC(NameCoin)) + _, err := cb.DeliverTx(ibcCtx, store, packet.Wrap()) + if err != nil { + return res, err + } + } } // a-ok! - return basecoin.Result{}, nil + return res, nil } // SetOption - sets the genesis account balance -func (h Handler) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (log string, err error) { +func (h Handler) SetOption(l log.Logger, store state.SimpleDB, + module, key, value string, _ basecoin.SetOptioner) (log string, err error) { + if module != NameCoin { return "", errors.ErrUnknownModule(module) } diff --git a/modules/coin/handler_test.go b/modules/coin/handler_test.go index 11959eccfc..1edd12d87e 100644 --- a/modules/coin/handler_test.go +++ b/modules/coin/handler_test.go @@ -149,7 +149,7 @@ func TestDeliverTx(t *testing.T) { } ctx := stack.MockContext("base-chain", 100).WithPermissions(tc.perms...) - _, err := h.DeliverTx(ctx, store, tc.tx) + _, err := h.DeliverTx(ctx, store, tc.tx, nil) if len(tc.final) > 0 { // valid assert.Nil(err, "%d: %+v", i, err) // make sure the final balances are correct @@ -204,7 +204,7 @@ func TestSetOption(t *testing.T) { for j, gen := range tc.init { value, err := json.Marshal(gen) require.Nil(err, "%d,%d: %+v", i, j, err) - _, err = h.SetOption(l, store, NameCoin, key, string(value)) + _, err = h.SetOption(l, store, NameCoin, key, string(value), nil) require.Nil(err) } @@ -215,5 +215,4 @@ func TestSetOption(t *testing.T) { assert.Equal(f.coins, acct.Coins) } } - } diff --git a/modules/coin/ibc_test.go b/modules/coin/ibc_test.go index 24856ef6f9..29c5462e55 100644 --- a/modules/coin/ibc_test.go +++ b/modules/coin/ibc_test.go @@ -10,6 +10,8 @@ import ( "github.com/tendermint/basecoin/modules/auth" "github.com/tendermint/basecoin/modules/ibc" "github.com/tendermint/basecoin/stack" + "github.com/tendermint/basecoin/state" + wire "github.com/tendermint/go-wire" ) // TODO: other test making sure tx is output on send, balance is updated @@ -28,7 +30,7 @@ func TestIBCPostPacket(t *testing.T) { app := stack.New(). IBC(ibc.NewMiddleware()). Dispatch( - stack.WrapHandler(NewHandler()), + NewHandler(), stack.WrapHandler(ibc.NewHandler()), ) ourChain := ibc.NewAppChain(app, ourID) @@ -57,14 +59,15 @@ func TestIBCPostPacket(t *testing.T) { require.Nil(err, "%+v", err) require.Equal(wealth, acct.Coins) + // make sure there is a proper packet for this.... + istore := ourChain.GetStore(ibc.NameIBC) + assertPacket(t, istore, otherID, wealth) + // these are the people for testing incoming ibc from the other chain recipient := basecoin.Actor{ChainID: ourID, App: auth.NameSigs, Address: []byte("bar")} sender := basecoin.Actor{ChainID: otherID, App: auth.NameSigs, Address: []byte("foo")} - coinTx := NewSendOneTx( - sender, - recipient, - Coins{{"eth", 100}, {"ltc", 300}}, - ) + payment := Coins{{"eth", 100}, {"ltc", 300}} + coinTx := NewSendOneTx(sender, recipient, payment) wrongCoin := NewSendOneTx(sender, recipient, Coins{{"missing", 20}}) p0 := ibc.NewPacket(coinTx, ourID, 0, sender) @@ -102,4 +105,36 @@ func TestIBCPostPacket(t *testing.T) { _, err := ourChain.DeliverTx(tc.packet.Wrap(), tc.permissions...) assert.True(tc.checker(err), "%d: %+v", i, err) } + + // now, make sure the recipient got credited for the 2 successful sendtx + cstore = ourChain.GetStore(NameCoin) + // FIXME: we need to strip off this when it is local chain-id... + // think this throw and handle this better + local := recipient.WithChain("") + acct, err = GetAccount(cstore, local) + require.Nil(err, "%+v", err) + assert.Equal(payment.Plus(payment), acct.Coins) + +} + +func assertPacket(t *testing.T, istore state.KVStore, destID string, amount Coins) { + assert := assert.New(t) + require := require.New(t) + + iq := ibc.InputQueue(istore, destID) + require.Equal(0, iq.Size()) + + q := ibc.OutputQueue(istore, destID) + require.Equal(1, q.Size()) + d := q.Item(0) + var res ibc.Packet + err := wire.ReadBinaryBytes(d, &res) + require.Nil(err, "%+v", err) + assert.Equal(destID, res.DestChain) + assert.EqualValues(0, res.Sequence) + stx, ok := res.Tx.Unwrap().(SendTx) + if assert.True(ok) { + assert.Equal(1, len(stx.Outputs)) + assert.Equal(amount, stx.Outputs[0].Coins) + } } diff --git a/modules/fee/handler_test.go b/modules/fee/handler_test.go index b775c2d565..000d99f0ba 100644 --- a/modules/fee/handler_test.go +++ b/modules/fee/handler_test.go @@ -40,7 +40,7 @@ func TestFeeChecks(t *testing.T) { // OKHandler will just return success to a RawTx stack.WrapHandler(stack.OKHandler{}), // coin is needed to handle the IPC call from Fee middleware - stack.WrapHandler(coin.NewHandler()), + coin.NewHandler(), ) // app1 requires no fees app1 := stack.New(fee.NewSimpleFeeMiddleware(atom(0), collector)).Use(disp) diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index 57c7d9638e..cba5579704 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -50,7 +50,7 @@ func (Handler) Name() string { } // SetOption sets the registrar for IBC -func (h Handler) SetOption(l log.Logger, store state.KVStore, module, key, value string) (log string, err error) { +func (h Handler) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (log string, err error) { if module != NameIBC { return "", errors.ErrUnknownModule(module) } @@ -70,7 +70,7 @@ func (h Handler) SetOption(l log.Logger, store state.KVStore, module, key, value // CheckTx verifies the packet is formated correctly, and has the proper sequence // for a registered chain -func (h Handler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { +func (h Handler) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) { err = tx.ValidateBasic() if err != nil { return res, err @@ -95,7 +95,7 @@ func (h Handler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin. // DeliverTx verifies all signatures on the tx and updates the chain state // apropriately -func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { +func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (res basecoin.Result, err error) { err = tx.ValidateBasic() if err != nil { return res, err @@ -122,7 +122,7 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoi // accepts it as the root of trust. // // only the registrar, if set, is allowed to do this -func (h Handler) initSeed(ctx basecoin.Context, store state.KVStore, +func (h Handler) initSeed(ctx basecoin.Context, store state.SimpleDB, t RegisterChainTx) (res basecoin.Result, err error) { // verify that the header looks reasonable @@ -141,7 +141,7 @@ func (h Handler) initSeed(ctx basecoin.Context, store state.KVStore, // updateSeed checks the seed against the existing chain data and rejects it if it // doesn't fit (or no chain data) -func (h Handler) updateSeed(ctx basecoin.Context, store state.KVStore, +func (h Handler) updateSeed(ctx basecoin.Context, store state.SimpleDB, t UpdateChainTx) (res basecoin.Result, err error) { chainID := t.ChainID() @@ -171,7 +171,7 @@ func (h Handler) updateSeed(ctx basecoin.Context, store state.KVStore, // createPacket makes sure all permissions are good and the destination // chain is registed. If so, it appends it to the outgoing queue -func (h Handler) createPacket(ctx basecoin.Context, store state.KVStore, +func (h Handler) createPacket(ctx basecoin.Context, store state.SimpleDB, t CreatePacketTx) (res basecoin.Result, err error) { // make sure the chain is registed diff --git a/modules/ibc/middleware.go b/modules/ibc/middleware.go index 0c02088f2d..7852bf1e57 100644 --- a/modules/ibc/middleware.go +++ b/modules/ibc/middleware.go @@ -26,7 +26,7 @@ func (Middleware) Name() string { // CheckTx verifies the named chain and height is present, and verifies // the merkle proof in the packet -func (m Middleware) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) { +func (m Middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) { // if it is not a PostPacket, just let it go through post, ok := tx.Unwrap().(PostPacketTx) if !ok { @@ -43,7 +43,7 @@ func (m Middleware) CheckTx(ctx basecoin.Context, store state.KVStore, tx baseco // DeliverTx verifies the named chain and height is present, and verifies // the merkle proof in the packet -func (m Middleware) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) { +func (m Middleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) { // if it is not a PostPacket, just let it go through post, ok := tx.Unwrap().(PostPacketTx) if !ok { @@ -60,7 +60,7 @@ func (m Middleware) DeliverTx(ctx basecoin.Context, store state.KVStore, tx base // verifyPost accepts a message bound for this chain... // TODO: think about relay -func (m Middleware) verifyPost(ctx basecoin.Context, store state.KVStore, +func (m Middleware) verifyPost(ctx basecoin.Context, store state.SimpleDB, tx PostPacketTx) (ictx basecoin.Context, itx basecoin.Tx, err error) { // make sure the chain is registered diff --git a/modules/ibc/provider.go b/modules/ibc/provider.go index 2c508836fa..7ff8858d8a 100644 --- a/modules/ibc/provider.go +++ b/modules/ibc/provider.go @@ -17,7 +17,7 @@ const ( // newCertifier loads up the current state of this chain to make a proper certifier // it will load the most recent height before block h if h is positive // if h < 0, it will load the latest height -func newCertifier(store state.KVStore, chainID string, h int) (*certifiers.InquiringCertifier, error) { +func newCertifier(store state.SimpleDB, chainID string, h int) (*certifiers.InquiringCertifier, error) { // each chain has their own prefixed subspace p := newDBProvider(store) @@ -42,11 +42,11 @@ func newCertifier(store state.KVStore, chainID string, h int) (*certifiers.Inqui // dbProvider wraps our kv store so it integrates with light-client verification type dbProvider struct { - byHash state.KVStore + byHash state.SimpleDB byHeight *state.Span } -func newDBProvider(store state.KVStore) *dbProvider { +func newDBProvider(store state.SimpleDB) *dbProvider { return &dbProvider{ byHash: stack.PrefixedStore(prefixHash, store), byHeight: state.NewSpan(stack.PrefixedStore(prefixHeight, store)), diff --git a/modules/ibc/store.go b/modules/ibc/store.go index af5654424d..e675455d7a 100644 --- a/modules/ibc/store.go +++ b/modules/ibc/store.go @@ -13,13 +13,13 @@ type HandlerInfo struct { } // Save the HandlerInfo to the store -func (h HandlerInfo) Save(store state.KVStore) { +func (h HandlerInfo) Save(store state.SimpleDB) { b := wire.BinaryBytes(h) store.Set(HandlerKey(), b) } // LoadInfo loads the HandlerInfo from the data store -func LoadInfo(store state.KVStore) (h HandlerInfo) { +func LoadInfo(store state.SimpleDB) (h HandlerInfo) { b := store.Get(HandlerKey()) if len(b) > 0 { wire.ReadBinaryBytes(b, &h) @@ -40,7 +40,7 @@ type ChainSet struct { } // NewChainSet loads or initialized the ChainSet -func NewChainSet(store state.KVStore) ChainSet { +func NewChainSet(store state.SimpleDB) ChainSet { space := stack.PrefixedStore(prefixChains, store) return ChainSet{ Set: state.NewSet(space), @@ -108,14 +108,14 @@ func (p Packet) Bytes() []byte { } // InputQueue returns the queue of input packets from this chain -func InputQueue(store state.KVStore, chainID string) *state.Queue { +func InputQueue(store state.SimpleDB, chainID string) *state.Queue { ch := stack.PrefixedStore(chainID, store) space := stack.PrefixedStore(prefixInput, ch) return state.NewQueue(space) } // OutputQueue returns the queue of output packets destined for this chain -func OutputQueue(store state.KVStore, chainID string) *state.Queue { +func OutputQueue(store state.SimpleDB, chainID string) *state.Queue { ch := stack.PrefixedStore(chainID, store) space := stack.PrefixedStore(prefixOutput, ch) return state.NewQueue(space) diff --git a/modules/ibc/test_helpers.go b/modules/ibc/test_helpers.go index 844a7c9ac5..9d118141e2 100644 --- a/modules/ibc/test_helpers.go +++ b/modules/ibc/test_helpers.go @@ -77,7 +77,7 @@ func makePostPacket(tree *iavl.IAVLTree, packet Packet, fromID string, fromHeigh type AppChain struct { chainID string app basecoin.Handler - store state.KVStore + store state.SimpleDB height int } @@ -102,11 +102,11 @@ func (a *AppChain) IncrementHeight(delta int) int { // by one. func (a *AppChain) DeliverTx(tx basecoin.Tx, perms ...basecoin.Actor) (basecoin.Result, error) { ctx := stack.MockContext(a.chainID, uint64(a.height)).WithPermissions(perms...) - store := state.NewKVCache(a.store) + store := a.store.Checkpoint() res, err := a.app.DeliverTx(ctx, store, tx) if err == nil { // commit data on success - store.Sync() + a.store.Commit(store) } return res, err } @@ -124,6 +124,6 @@ func (a *AppChain) SetOption(mod, key, value string) (string, error) { } // GetStore is used to get the app-specific sub-store -func (a *AppChain) GetStore(app string) state.KVStore { +func (a *AppChain) GetStore(app string) state.SimpleDB { return stack.PrefixedStore(app, a.store) } diff --git a/state/queue.go b/state/queue.go index c1b10440cc..756f90bb64 100644 --- a/state/queue.go +++ b/state/queue.go @@ -71,6 +71,14 @@ func (q *Queue) Pop() []byte { return value } +// Item looks at any element in the queue, without modifying anything +func (q *Queue) Item(seq uint64) []byte { + if seq >= q.tail || seq < q.head { + return nil + } + return q.store.Get(makeKey(seq)) +} + func (q *Queue) setCount(key []byte, val uint64) { b := make([]byte, 8) binary.BigEndian.PutUint64(b, val)