From 88781593bb7f17c780b74c1d25df468728b86a0b Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 13 Jul 2017 13:38:23 +0200 Subject: [PATCH 01/41] Prepare stack middleware to handle IBC middleware --- stack/context.go | 32 ++++++++++++++-- stack/middleware.go | 79 +++++++++++++++++++++++++++++++++++----- stack/middleware_test.go | 41 ++++++++++++++++----- 3 files changed, 129 insertions(+), 23 deletions(-) diff --git a/stack/context.go b/stack/context.go index e688a5a08..9e3c247e8 100644 --- a/stack/context.go +++ b/stack/context.go @@ -14,6 +14,7 @@ type nonce int64 type secureContext struct { app string + ibc bool // this exposes the log.Logger and all other methods we don't override naiveContext } @@ -31,24 +32,35 @@ var _ basecoin.Context = secureContext{} func (c secureContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context { // the guard makes sure you only set permissions for the app you are inside for _, p := range perms { - // TODO: also check chainID, limit only certain middleware can set IBC? - if p.App != c.app { - err := errors.Errorf("Cannot set permission for %s from %s", c.app, p.App) + if !c.validPermisison(p) { + err := errors.Errorf("Cannot set permission for %s/%s on (app=%s, ibc=%b)", + p.ChainID, p.App, c.app, c.ibc) panic(err) } } return secureContext{ app: c.app, + ibc: c.ibc, naiveContext: c.naiveContext.WithPermissions(perms...).(naiveContext), } } +func (c secureContext) validPermisison(p basecoin.Actor) bool { + // if app is set, then it must match + if c.app != "" && c.app != p.App { + return false + } + // if ibc, chain must be set, otherwise it must not + return c.ibc == (p.ChainID != "") +} + // Reset should clear out all permissions, // but carry on knowledge that this is a child func (c secureContext) Reset() basecoin.Context { return secureContext{ app: c.app, + ibc: c.ibc, naiveContext: c.naiveContext.Reset().(naiveContext), } } @@ -71,6 +83,20 @@ func withApp(ctx basecoin.Context, app string) basecoin.Context { } return secureContext{ app: app, + ibc: false, + naiveContext: sc.naiveContext, + } +} + +// withIBC is a private method so we can securely allow IBC permissioning +func withIBC(ctx basecoin.Context) basecoin.Context { + sc, ok := ctx.(secureContext) + if !ok { + return ctx + } + return secureContext{ + app: "", + ibc: true, naiveContext: sc.naiveContext, } } diff --git a/stack/middleware.go b/stack/middleware.go index e3400ff58..c5f6a9b71 100644 --- a/stack/middleware.go +++ b/stack/middleware.go @@ -12,6 +12,8 @@ import ( // heavily inspired by negroni's design type middleware struct { middleware Middleware + space string + allowIBC bool next basecoin.Handler } @@ -21,13 +23,20 @@ func (m *middleware) Name() string { return m.middleware.Name() } +func (m *middleware) wrapCtx(ctx basecoin.Context) basecoin.Context { + if m.allowIBC { + return withIBC(ctx) + } + return withApp(ctx, m.space) +} + // CheckTx always returns an empty success tx func (m *middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (basecoin.Result, error) { // make sure we pass in proper context to child next := secureCheck(m.next, ctx) // set the permissions for this app - ctx = withApp(ctx, m.Name()) - store = stateSpace(store, m.Name()) + ctx = m.wrapCtx(ctx) + store = stateSpace(store, m.space) return m.middleware.CheckTx(ctx, store, tx, next) } @@ -37,22 +46,48 @@ func (m *middleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx ba // make sure we pass in proper context to child next := secureDeliver(m.next, ctx) // set the permissions for this app - ctx = withApp(ctx, m.Name()) - store = stateSpace(store, m.Name()) + ctx = m.wrapCtx(ctx) + store = stateSpace(store, m.space) return m.middleware.DeliverTx(ctx, store, tx, next) } func (m *middleware) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) { // set the namespace for the app - store = stateSpace(store, m.Name()) + store = stateSpace(store, m.space) return m.middleware.SetOption(l, store, module, key, value, m.next) } +// builder is used to associate info with the middleware, so we can build +// it properly +type builder struct { + middleware Middleware + stateSpace string + allowIBC bool +} + +func prep(m Middleware, ibc bool) builder { + return builder{ + middleware: m, + stateSpace: m.Name(), + allowIBC: ibc, + } +} + +// wrap sets up the middleware with the proper options +func (b builder) wrap(next basecoin.Handler) basecoin.Handler { + return &middleware{ + middleware: b.middleware, + space: b.stateSpace, + allowIBC: b.allowIBC, + next: next, + } +} + // Stack is the entire application stack type Stack struct { - middles []Middleware + middles []builder handler basecoin.Handler basecoin.Handler // the compiled version, which we expose } @@ -62,9 +97,26 @@ var _ basecoin.Handler = &Stack{} // New prepares a middleware stack, you must `.Use()` a Handler // before you can execute it. func New(middlewares ...Middleware) *Stack { - return &Stack{ - middles: middlewares, + stack := new(Stack) + return stack.Apps(middlewares...) +} + +// Apps adds the following Middlewares as typical application +// middleware to the stack (limit permission to one app) +func (s *Stack) Apps(middlewares ...Middleware) *Stack { + // TODO: some wrapper... + for _, m := range middlewares { + s.middles = append(s.middles, prep(m, false)) } + return s +} + +// IBC add the following middleware with permission to add cross-chain +// permissions +func (s *Stack) IBC(m Middleware) *Stack { + // TODO: some wrapper... + s.middles = append(s.middles, prep(m, true)) + return s } // Use sets the final handler for the stack and prepares it for use @@ -77,10 +129,17 @@ func (s *Stack) Use(handler basecoin.Handler) *Stack { return s } -func build(mid []Middleware, end basecoin.Handler) basecoin.Handler { +// Dispatch is like Use, but a convenience method to construct a +// dispatcher with a set of modules to route. +func (s *Stack) Dispatch(routes ...Dispatchable) *Stack { + d := NewDispatcher(routes...) + return s.Use(d) +} + +func build(mid []builder, end basecoin.Handler) basecoin.Handler { if len(mid) == 0 { return end } next := build(mid[1:], end) - return &middleware{mid[0], next} + return mid[0].wrap(next) } diff --git a/stack/middleware_test.go b/stack/middleware_test.go index 1b4ed8df3..bb33a9a62 100644 --- a/stack/middleware_test.go +++ b/stack/middleware_test.go @@ -31,25 +31,46 @@ func TestPermissionSandbox(t *testing.T) { // test cases to make sure permissioning is solid grantee := basecoin.Actor{App: NameGrant, Address: []byte{1}} grantee2 := basecoin.Actor{App: NameGrant, Address: []byte{2}} - signer := basecoin.Actor{App: nameSigner, Address: []byte{1}} + // ibc and grantee are the same, just different chains + ibc := basecoin.Actor{ChainID: "other", App: NameGrant, Address: []byte{1}} + ibc2 := basecoin.Actor{ChainID: "other", App: nameSigner, Address: []byte{21}} + signer := basecoin.Actor{App: nameSigner, Address: []byte{21}} cases := []struct { + asIBC bool grant basecoin.Actor require basecoin.Actor expectedRes data.Bytes expected func(error) bool }{ - {grantee, grantee, rawBytes, nil}, - {grantee, grantee2, nil, errors.IsUnauthorizedErr}, - {grantee, signer, nil, errors.IsUnauthorizedErr}, - {signer, signer, nil, errors.IsInternalErr}, + // grant as normal app middleware + {false, grantee, grantee, rawBytes, nil}, + {false, grantee, grantee2, nil, errors.IsUnauthorizedErr}, + {false, grantee2, grantee2, rawBytes, nil}, + {false, ibc, grantee, nil, errors.IsInternalErr}, + {false, grantee, ibc, nil, errors.IsUnauthorizedErr}, + {false, grantee, signer, nil, errors.IsUnauthorizedErr}, + {false, signer, signer, nil, errors.IsInternalErr}, + + // grant as ibc middleware + {true, ibc, ibc, rawBytes, nil}, // ibc can set permissions + {true, ibc2, ibc2, rawBytes, nil}, // for any app + // the must match, both app and chain + {true, ibc, ibc2, nil, errors.IsUnauthorizedErr}, + {true, ibc, grantee, nil, errors.IsUnauthorizedErr}, + // cannot set local apps from ibc middleware + {true, grantee, grantee, nil, errors.IsInternalErr}, } for i, tc := range cases { - app := New( - Recovery{}, // we need this so panics turn to errors - GrantMiddleware{Auth: tc.grant}, - CheckMiddleware{Required: tc.require}, - ).Use(EchoHandler{}) + app := New(Recovery{}) + if tc.asIBC { + app = app.IBC(GrantMiddleware{Auth: tc.grant}) + } else { + app = app.Apps(GrantMiddleware{Auth: tc.grant}) + } + app = app. + Apps(CheckMiddleware{Required: tc.require}). + Use(EchoHandler{}) res, err := app.CheckTx(ctx, store, raw) checkPerm(t, i, tc.expectedRes, tc.expected, res, err) From 697c2f1e049e83630f83b7491bb95a5217a5bfd3 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 13 Jul 2017 19:22:05 +0200 Subject: [PATCH 02/41] Started on ibc module tx, types --- modules/ibc/errors.go | 1 + modules/ibc/handler.go | 40 ++++++++++ modules/ibc/middleware.go | 37 ++++++++++ modules/ibc/provider.go | 44 +++++++++++ modules/ibc/store.go | 1 + modules/ibc/tx.go | 152 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 275 insertions(+) create mode 100644 modules/ibc/errors.go create mode 100644 modules/ibc/handler.go create mode 100644 modules/ibc/middleware.go create mode 100644 modules/ibc/provider.go create mode 100644 modules/ibc/store.go create mode 100644 modules/ibc/tx.go diff --git a/modules/ibc/errors.go b/modules/ibc/errors.go new file mode 100644 index 000000000..a81836a9c --- /dev/null +++ b/modules/ibc/errors.go @@ -0,0 +1 @@ +package ibc diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go new file mode 100644 index 000000000..8de36d42d --- /dev/null +++ b/modules/ibc/handler.go @@ -0,0 +1,40 @@ +package ibc + +import ( + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/state" +) + +// nolint +const ( + NameIBC = "ibc" +) + +// Handler allows us to update the chain state or create a packet +type Handler struct { + basecoin.NopOption +} + +var _ basecoin.Handler = Handler{} + +// NewHandler makes a role handler to create roles +func NewHandler() Handler { + return Handler{} +} + +// Name - return name space +func (Handler) Name() string { + return NameIBC +} + +// 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) { + return res, nil +} + +// DeliverTx verifies all signatures on the tx and updated the chain state +// apropriately +func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { + return res, nil +} diff --git a/modules/ibc/middleware.go b/modules/ibc/middleware.go new file mode 100644 index 000000000..cb32ea603 --- /dev/null +++ b/modules/ibc/middleware.go @@ -0,0 +1,37 @@ +package ibc + +import ( + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/stack" + "github.com/tendermint/basecoin/state" +) + +// Middleware allows us to verify the IBC proof on a packet and +// and if valid, attach this permission to the wrapped packet +type Middleware struct { + stack.PassOption +} + +var _ stack.Middleware = Middleware{} + +// NewMiddleware creates a role-checking middleware +func NewMiddleware() Middleware { + return Middleware{} +} + +// Name - return name space +func (Middleware) Name() string { + return NameIBC +} + +// 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) { + return res, nil +} + +// 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) { + return res, nil +} diff --git a/modules/ibc/provider.go b/modules/ibc/provider.go new file mode 100644 index 000000000..a28e6af95 --- /dev/null +++ b/modules/ibc/provider.go @@ -0,0 +1,44 @@ +package ibc + +import ( + "github.com/tendermint/light-client/certifiers" + + "github.com/tendermint/basecoin/stack" + "github.com/tendermint/basecoin/state" +) + +// newCertifier loads up the current state of this chain to make a proper +func newCertifier(chainID string, store state.KVStore) (*certifiers.InquiringCertifier, error) { + // each chain has their own prefixed subspace + space := stack.PrefixedStore(chainID, store) + p := dbProvider{space} + + // this gets the most recent verified seed + seed, err := certifiers.LatestSeed(p) + if err != nil { + return nil, err + } + + // we have no source for untrusted keys, but use the db to load trusted history + cert := certifiers.NewInquiring(chainID, seed.Validators, p, + certifiers.MissingProvider{}) + return cert, nil +} + +// dbProvider wraps our kv store so it integrates with light-client verification +type dbProvider struct { + store state.KVStore +} + +var _ certifiers.Provider = dbProvider{} + +func (d dbProvider) StoreSeed(seed certifiers.Seed) error { + return nil +} + +func (d dbProvider) GetByHeight(h int) (certifiers.Seed, error) { + return certifiers.Seed{}, certifiers.ErrSeedNotFound() +} +func (d dbProvider) GetByHash(hash []byte) (certifiers.Seed, error) { + return certifiers.Seed{}, certifiers.ErrSeedNotFound() +} diff --git a/modules/ibc/store.go b/modules/ibc/store.go new file mode 100644 index 000000000..a81836a9c --- /dev/null +++ b/modules/ibc/store.go @@ -0,0 +1 @@ +package ibc diff --git a/modules/ibc/tx.go b/modules/ibc/tx.go new file mode 100644 index 000000000..682758663 --- /dev/null +++ b/modules/ibc/tx.go @@ -0,0 +1,152 @@ +package ibc + +import ( + abci "github.com/tendermint/abci/types" + "github.com/tendermint/light-client/certifiers" + merkle "github.com/tendermint/merkleeyes/iavl" + + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/errors" +) + +// nolint +const ( + // 0x3? series for ibc + ByteRegisterChain = byte(0x30) + ByteUpdateChain = byte(0x31) + BytePacketCreate = byte(0x32) + BytePacketPost = byte(0x33) + + TypeRegisterChain = NameIBC + "/register" + TypeUpdateChain = NameIBC + "/update" + TypePacketCreate = NameIBC + "/create" + TypePacketPost = NameIBC + "/post" + + IBCCodeEncodingError = abci.CodeType(1001) + IBCCodeChainAlreadyExists = abci.CodeType(1002) + IBCCodePacketAlreadyExists = abci.CodeType(1003) + IBCCodeUnknownHeight = abci.CodeType(1004) + IBCCodeInvalidCommit = abci.CodeType(1005) + IBCCodeInvalidProof = abci.CodeType(1006) +) + +func init() { + basecoin.TxMapper. + RegisterImplementation(RegisterChainTx{}, TypeRegisterChain, ByteRegisterChain). + RegisterImplementation(UpdateChainTx{}, TypeUpdateChain, ByteUpdateChain). + RegisterImplementation(PacketCreateTx{}, TypePacketCreate, BytePacketCreate). + RegisterImplementation(PacketPostTx{}, TypePacketPost, BytePacketPost) +} + +// RegisterChainTx allows you to register a new chain on this blockchain +type RegisterChainTx struct { + Seed certifiers.Seed `json:"seed"` +} + +// ChainID helps get the chain this tx refers to +func (r RegisterChainTx) ChainID() string { + return r.Seed.Header.ChainID +} + +// ValidateBasic makes sure this is consistent, without checking the sigs +func (r RegisterChainTx) ValidateBasic() error { + return r.Seed.ValidateBasic(r.ChainID()) +} + +// Wrap - used to satisfy TxInner +func (r RegisterChainTx) Wrap() basecoin.Tx { + return basecoin.Tx{r} +} + +// UpdateChainTx updates the state of this chain +type UpdateChainTx struct { + Seed certifiers.Seed `json:"seed"` +} + +// ChainID helps get the chain this tx refers to +func (u UpdateChainTx) ChainID() string { + return u.Seed.Header.ChainID +} + +// ValidateBasic makes sure this is consistent, without checking the sigs +func (u UpdateChainTx) ValidateBasic() error { + return u.Seed.ValidateBasic(u.ChainID()) +} + +// PacketCreateTx is meant to be called by IPC, another module... +// +// this is the tx that will be sent to another app and the permissions it +// comes with (which must be a subset of the permissions on the current tx) +// +// TODO: how to control who can create packets (can I just signed create packet?) +type PacketCreateTx struct { + DestChain string `json:"dest_chain"` + Permissions []basecoin.Actor `json:"permissions"` + Tx basecoin.Tx `json:"tx"` +} + +// ValidateBasic makes sure this is consistent - used to satisfy TxInner +func (p PacketCreateTx) ValidateBasic() error { + if p.DestChain == "" { + return errors.ErrNoChain() + } + // if len(p.Permissions) == 0 { + // return ErrNoPermissions() + // } + return nil +} + +// Wrap - used to satisfy TxInner +func (p PacketCreateTx) Wrap() basecoin.Tx { + return basecoin.Tx{p} +} + +// PacketPostTx takes a wrapped packet from another chain and +// TODO!!! +type PacketPostTx struct { + FromChainID string // The immediate source of the packet, not always Packet.SrcChainID + FromChainHeight uint64 // The block height in which Packet was committed, to check Proof + Proof *merkle.IAVLProof + // Packet +} + +// ValidateBasic makes sure this is consistent - used to satisfy TxInner +func (p PacketPostTx) ValidateBasic() error { + // TODO + return nil +} + +// Wrap - used to satisfy TxInner +func (p PacketPostTx) Wrap() basecoin.Tx { + return basecoin.Tx{p} +} + +// proof := tx.Proof +// if proof == nil { +// sm.res.Code = IBCCodeInvalidProof +// sm.res.Log = "Proof is nil" +// return +// } +// packetBytes := wire.BinaryBytes(packet) + +// // Make sure packet's proof matches given (packet, key, blockhash) +// ok := proof.Verify(packetKeyEgress, packetBytes, header.AppHash) +// if !ok { +// sm.res.Code = IBCCodeInvalidProof +// sm.res.Log = fmt.Sprintf("Proof is invalid. key: %s; packetByes %X; header %v; proof %v", packetKeyEgress, packetBytes, header, proof) +// return +// } + +// // Execute payload +// switch payload := packet.Payload.(type) { +// case DataPayload: +// // do nothing +// case CoinsPayload: +// // Add coins to destination account +// acc := types.GetAccount(sm.store, payload.Address) +// if acc == nil { +// acc = &types.Account{} +// } +// acc.Balance = acc.Balance.Plus(payload.Coins) +// types.SetAccount(sm.store, payload.Address, acc) +// } From eaae12101eac0594afc3323b56a12e52744bdda6 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 13 Jul 2017 20:09:25 +0200 Subject: [PATCH 03/41] Add queue implementaiton to state --- state/queue.go | 79 +++++++++++++++++++++++++++++++++++++++++++++ state/queue_test.go | 60 ++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 state/queue.go create mode 100644 state/queue_test.go diff --git a/state/queue.go b/state/queue.go new file mode 100644 index 000000000..db4869172 --- /dev/null +++ b/state/queue.go @@ -0,0 +1,79 @@ +package state + +import "encoding/binary" + +var ( + headKey = []byte("h") + tailKey = []byte("t") + dataKey = []byte("d") +) + +// Queue allows us to fill up a range of the db, and grab from either end +type Queue struct { + store KVStore + head uint64 // if Size() > 0, the first element is here + tail uint64 // this is the first empty slot to Push() to +} + +// NewQueue will load or initialize a queue in this state-space +// +// Generally, you will want to stack.PrefixStore() the space first +func NewQueue(store KVStore) *Queue { + q := &Queue{store: store} + q.head = q.getCount(headKey) + q.tail = q.getCount(tailKey) + return q +} + +// Tail returns the next slot that Push() will use +func (q *Queue) Tail() uint64 { + return q.tail +} + +// Size returns how many elements are in the queue +func (q *Queue) Size() uint64 { + return q.tail - q.head +} + +// Push adds an element to the tail of the queue and returns it's location +func (q *Queue) Push(value []byte) uint64 { + key := makeKey(q.tail) + q.store.Set(key, value) + q.tail++ + q.setCount(tailKey, q.tail) + return q.tail - 1 +} + +// Pop gets an element from the end of the queue +func (q *Queue) Pop() []byte { + if q.Size() <= 0 { + return nil + } + key := makeKey(q.head) + value := q.store.Get(key) + q.head++ + q.setCount(headKey, q.head) + return value +} + +func (q *Queue) setCount(key []byte, val uint64) { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, val) + q.store.Set(key, b) +} + +func (q *Queue) getCount(key []byte) (val uint64) { + b := q.store.Get(key) + if b != nil { + val = binary.BigEndian.Uint64(b) + } + return val +} + +// makeKey returns the key for a data point +func makeKey(val uint64) []byte { + b := make([]byte, 8+len(dataKey)) + copy(b, dataKey) + binary.BigEndian.PutUint64(b[len(dataKey):], val) + return b +} diff --git a/state/queue_test.go b/state/queue_test.go new file mode 100644 index 000000000..df367e5bb --- /dev/null +++ b/state/queue_test.go @@ -0,0 +1,60 @@ +package state + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestQueue(t *testing.T) { + assert := assert.New(t) + + cases := []struct { + pushes [][]byte + pops [][]byte + }{ + // fill it up and empty it all + { + [][]byte{{1, 2, 3}, {44}, {3, 0}}, + [][]byte{{1, 2, 3}, {44}, {3, 0}}, + }, + // don't empty everything - size is 1 at the end + { + [][]byte{{77, 22}, {11, 9}, {121}}, + [][]byte{{77, 22}, {11, 9}}, + }, + // empty too much, just get nil, no negative size + { + [][]byte{{1}, {2}, {4}}, + [][]byte{{1}, {2}, {4}, nil, nil, nil}, + }, + } + + for i, tc := range cases { + store := NewMemKVStore() + + // initialize a queue and add items + q := NewQueue(store) + for j, in := range tc.pushes { + cnt := q.Push(in) + assert.Equal(uint64(j), cnt, "%d", i) + } + assert.EqualValues(len(tc.pushes), q.Size()) + + // load from disk and pop them + r := NewQueue(store) + for _, out := range tc.pops { + val := r.Pop() + assert.Equal(out, val, "%d", i) + } + + // it's empty in memory and on disk + expected := len(tc.pushes) - len(tc.pops) + if expected < 0 { + expected = 0 + } + assert.EqualValues(expected, r.Size()) + s := NewQueue(store) + assert.EqualValues(expected, s.Size()) + } +} From 1b75d9431b6e635033b0f4ec7c15274dfcc268a2 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 13 Jul 2017 21:41:10 +0200 Subject: [PATCH 04/41] Add span to hold sparse, ordered sets --- state/queue_test.go | 7 +++ state/span.go | 123 ++++++++++++++++++++++++++++++++++++++++++++ state/span_test.go | 122 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 state/span.go create mode 100644 state/span_test.go diff --git a/state/queue_test.go b/state/queue_test.go index df367e5bb..d6212be90 100644 --- a/state/queue_test.go +++ b/state/queue_test.go @@ -9,6 +9,11 @@ import ( func TestQueue(t *testing.T) { assert := assert.New(t) + lots := make([][]byte, 500) + for i := range lots { + lots[i] = []byte{1, 8, 7} + } + cases := []struct { pushes [][]byte pops [][]byte @@ -28,6 +33,8 @@ func TestQueue(t *testing.T) { [][]byte{{1}, {2}, {4}}, [][]byte{{1}, {2}, {4}, nil, nil, nil}, }, + // let's play with lots.... + {lots, append(lots, nil)}, } for i, tc := range cases { diff --git a/state/span.go b/state/span.go new file mode 100644 index 000000000..5b9708417 --- /dev/null +++ b/state/span.go @@ -0,0 +1,123 @@ +package state + +import wire "github.com/tendermint/go-wire" + +var ( + keys = []byte("keys") + // uses dataKey from queue.go to prefix data +) + +// Span holds a number of different keys in a large range and allows +// use to make some basic range queries, like highest between, lowest between... +// All items are added with an index +// +// This becomes horribly inefficent as len(keys) => 1000+, but by then +// hopefully we have access to the iavl tree to do this well +// +// TODO: doesn't handle deleting.... +type Span struct { + store KVStore + // keys is sorted ascending and cannot contain duplicates + keys []uint64 +} + +// NewSpan loads or initializes a span of keys +func NewSpan(store KVStore) *Span { + s := &Span{store: store} + s.loadKeys() + return s +} + +// Set puts a value at a given height +func (s *Span) Set(h uint64, value []byte) { + key := makeKey(h) + s.store.Set(key, value) + s.addKey(h) + s.storeKeys() +} + +// Get returns the element at h if it exists +func (s *Span) Get(h uint64) []byte { + key := makeKey(h) + return s.store.Get(key) +} + +// Bottom returns the lowest element in the Span, along with its index +func (s *Span) Bottom() ([]byte, uint64) { + if len(s.keys) == 0 { + return nil, 0 + } + h := s.keys[0] + return s.Get(h), h +} + +// Top returns the highest element in the Span, along with its index +func (s *Span) Top() ([]byte, uint64) { + l := len(s.keys) + if l == 0 { + return nil, 0 + } + h := s.keys[l-1] + return s.Get(h), h +} + +// GTE returns the lowest element in the Span that is >= h, along with its index +func (s *Span) GTE(h uint64) ([]byte, uint64) { + for _, k := range s.keys { + if k >= h { + return s.Get(k), k + } + } + return nil, 0 +} + +// LTE returns the highest element in the Span that is <= h, +// along with its index +func (s *Span) LTE(h uint64) ([]byte, uint64) { + var k uint64 + // start from the highest and go down for the first match + for i := len(s.keys) - 1; i >= 0; i-- { + k = s.keys[i] + if k <= h { + return s.Get(k), k + } + } + return nil, 0 +} + +// addKey inserts this key, maintaining sorted order, no duplicates +func (s *Span) addKey(h uint64) { + for i, k := range s.keys { + // don't add duplicates + if h == k { + return + } + // insert before this key + if h < k { + // https://github.com/golang/go/wiki/SliceTricks + s.keys = append(s.keys, 0) + copy(s.keys[i+1:], s.keys[i:]) + s.keys[i] = h + return + } + } + // if it is higher than all (or empty keys), append + s.keys = append(s.keys, h) +} + +func (s *Span) loadKeys() { + b := s.store.Get(keys) + if b == nil { + return + } + err := wire.ReadBinaryBytes(b, &s.keys) + // hahaha... just like i love to hate :) + if err != nil { + panic(err) + } +} + +func (s *Span) storeKeys() { + b := wire.BinaryBytes(s.keys) + s.store.Set(keys, b) +} diff --git a/state/span_test.go b/state/span_test.go new file mode 100644 index 000000000..8362f3d90 --- /dev/null +++ b/state/span_test.go @@ -0,0 +1,122 @@ +package state + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" +) + +type kv struct { + k uint64 + v []byte +} + +type bscase struct { + data []kv + // these are the tests to try out + top kv + bottom kv + gets []kv // for each item check the query matches + lte []kv // value for lte queires... + gte []kv // value for gte +} + +func TestBasicSpan(t *testing.T) { + + a, b, c := []byte{0xaa}, []byte{0xbb}, []byte{0xcc} + + lots := make([]kv, 1000) + for i := range lots { + lots[i] = kv{uint64(3 * i), []byte{byte(i / 100), byte(i % 100)}} + } + + cases := []bscase{ + // simplest queries + { + []kv{{1, a}, {3, b}, {5, c}}, + kv{5, c}, + kv{1, a}, + []kv{{1, a}, {3, b}, {5, c}}, + []kv{{2, a}, {77, c}, {3, b}, {0, nil}}, // lte + []kv{{6, nil}, {2, b}, {1, a}}, // gte + }, + // add out of order + { + []kv{{7, a}, {2, b}, {6, c}}, + kv{7, a}, + kv{2, b}, + []kv{{2, b}, {6, c}, {7, a}}, + []kv{{4, b}, {7, a}, {1, nil}}, // lte + []kv{{4, c}, {7, a}, {1, b}}, // gte + }, + // add out of order and with duplicates + { + []kv{{7, a}, {2, b}, {6, c}, {7, c}, {6, b}, {2, a}}, + kv{7, c}, + kv{2, a}, + []kv{{2, a}, {6, b}, {7, c}}, + []kv{{5, a}, {6, b}, {123, c}}, // lte + []kv{{0, a}, {3, b}, {7, c}, {8, nil}}, // gte + }, + // try lots... + { + lots, + lots[len(lots)-1], + lots[0], + lots, + nil, + nil, + }, + } + + for i, tc := range cases { + store := NewMemKVStore() + + // initialize a queue and add items + s := NewSpan(store) + for _, x := range tc.data { + s.Set(x.k, x.v) + } + + testSpan(t, i, s, tc) + // reload and try the queries again + s2 := NewSpan(store) + testSpan(t, i+10, s2, tc) + } +} + +func testSpan(t *testing.T, idx int, s *Span, tc bscase) { + assert := assert.New(t) + i := strconv.Itoa(idx) + + v, k := s.Top() + assert.Equal(tc.top.k, k, i) + assert.Equal(tc.top.v, v, i) + + v, k = s.Bottom() + assert.Equal(tc.bottom.k, k, i) + assert.Equal(tc.bottom.v, v, i) + + for _, g := range tc.gets { + v = s.Get(g.k) + assert.Equal(g.v, v, i) + } + + for _, l := range tc.lte { + v, k = s.LTE(l.k) + assert.Equal(l.v, v, i) + if l.v != nil { + assert.True(k <= l.k, i) + } + } + + for _, t := range tc.gte { + v, k = s.GTE(t.k) + assert.Equal(t.v, v, i) + if t.v != nil { + assert.True(k >= t.k, i) + } + } + +} From b150c865f95a41a7672de95be9d7541dfc6fe8c2 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 13 Jul 2017 22:11:15 +0200 Subject: [PATCH 05/41] Add a light-client provider in the kvstore --- modules/ibc/provider.go | 46 +++++++++--- modules/ibc/provider_test.go | 136 +++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 8 deletions(-) create mode 100644 modules/ibc/provider_test.go diff --git a/modules/ibc/provider.go b/modules/ibc/provider.go index a28e6af95..91aebb77a 100644 --- a/modules/ibc/provider.go +++ b/modules/ibc/provider.go @@ -1,17 +1,24 @@ package ibc import ( + wire "github.com/tendermint/go-wire" "github.com/tendermint/light-client/certifiers" "github.com/tendermint/basecoin/stack" "github.com/tendermint/basecoin/state" ) +const ( + prefixHash = "v" + prefixHeight = "h" + prefixPacket = "p" +) + // newCertifier loads up the current state of this chain to make a proper func newCertifier(chainID string, store state.KVStore) (*certifiers.InquiringCertifier, error) { // each chain has their own prefixed subspace space := stack.PrefixedStore(chainID, store) - p := dbProvider{space} + p := newDBProvider(space) // this gets the most recent verified seed seed, err := certifiers.LatestSeed(p) @@ -27,18 +34,41 @@ func newCertifier(chainID string, store state.KVStore) (*certifiers.InquiringCer // dbProvider wraps our kv store so it integrates with light-client verification type dbProvider struct { - store state.KVStore + byHash state.KVStore + byHeight *state.Span } -var _ certifiers.Provider = dbProvider{} +func newDBProvider(store state.KVStore) *dbProvider { + return &dbProvider{ + byHash: stack.PrefixedStore(prefixHash, store), + byHeight: state.NewSpan(stack.PrefixedStore(prefixHeight, store)), + } +} -func (d dbProvider) StoreSeed(seed certifiers.Seed) error { +var _ certifiers.Provider = &dbProvider{} + +func (d *dbProvider) StoreSeed(seed certifiers.Seed) error { + // TODO: don't duplicate data.... + b := wire.BinaryBytes(seed) + d.byHash.Set(seed.Hash(), b) + d.byHeight.Set(uint64(seed.Height()), b) return nil } -func (d dbProvider) GetByHeight(h int) (certifiers.Seed, error) { - return certifiers.Seed{}, certifiers.ErrSeedNotFound() +func (d *dbProvider) GetByHeight(h int) (seed certifiers.Seed, err error) { + b, _ := d.byHeight.LTE(uint64(h)) + if b == nil { + return seed, certifiers.ErrSeedNotFound() + } + err = wire.ReadBinaryBytes(b, &seed) + return } -func (d dbProvider) GetByHash(hash []byte) (certifiers.Seed, error) { - return certifiers.Seed{}, certifiers.ErrSeedNotFound() + +func (d *dbProvider) GetByHash(hash []byte) (seed certifiers.Seed, err error) { + b := d.byHash.Get(hash) + if b == nil { + return seed, certifiers.ErrSeedNotFound() + } + err = wire.ReadBinaryBytes(b, &seed) + return } diff --git a/modules/ibc/provider_test.go b/modules/ibc/provider_test.go new file mode 100644 index 000000000..6028570ea --- /dev/null +++ b/modules/ibc/provider_test.go @@ -0,0 +1,136 @@ +package ibc + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/tendermint/basecoin/state" + "github.com/tendermint/light-client/certifiers" +) + +func assertSeedEqual(t *testing.T, s, s2 certifiers.Seed) { + assert := assert.New(t) + assert.Equal(s.Height(), s2.Height()) + assert.Equal(s.Hash(), s2.Hash()) + // TODO: more +} + +func TestProviderStore(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + // make a few seeds + keys := certifiers.GenValKeys(2) + seeds := makeSeeds(keys, 4, "some-chain", "demo-store") + + // make a provider + store := state.NewMemKVStore() + p := newDBProvider(store) + + // check it... + _, err := p.GetByHeight(20) + require.NotNil(err) + assert.True(certifiers.IsSeedNotFoundErr(err)) + + // add a seed + for _, s := range seeds { + err = p.StoreSeed(s) + require.Nil(err) + } + + // make sure we get it... + s := seeds[0] + val, err := p.GetByHeight(s.Height()) + if assert.Nil(err) { + assertSeedEqual(t, s, val) + } + + // make sure we get higher + val, err = p.GetByHeight(s.Height() + 2) + if assert.Nil(err) { + assertSeedEqual(t, s, val) + } + + // below is nothing + _, err = p.GetByHeight(s.Height() - 2) + assert.True(certifiers.IsSeedNotFoundErr(err)) + + // make sure we get highest + val, err = certifiers.LatestSeed(p) + if assert.Nil(err) { + assertSeedEqual(t, seeds[3], val) + } + + // make sure by hash also (note all have same hash, so overwritten) + val, err = p.GetByHash(seeds[1].Hash()) + if assert.Nil(err) { + assertSeedEqual(t, seeds[3], val) + } +} + +func TestDBProvider(t *testing.T) { + store := state.NewMemKVStore() + p := newDBProvider(store) + checkProvider(t, p, "test-db", "bling") +} + +func makeSeeds(keys certifiers.ValKeys, count int, chainID, app string) []certifiers.Seed { + appHash := []byte(app) + seeds := make([]certifiers.Seed, count) + for i := 0; i < count; i++ { + // two seeds for each validator, to check how we handle dups + // (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ... + vals := keys.ToValidators(10, int64(count/2)) + h := 20 + 10*i + check := keys.GenCheckpoint(chainID, h, nil, vals, appHash, 0, len(keys)) + seeds[i] = certifiers.Seed{check, vals} + } + return seeds +} + +func checkProvider(t *testing.T, p certifiers.Provider, chainID, app string) { + assert, require := assert.New(t), require.New(t) + keys := certifiers.GenValKeys(5) + count := 10 + + // make a bunch of seeds... + seeds := makeSeeds(keys, count, chainID, app) + + // check provider is empty + seed, err := p.GetByHeight(20) + require.NotNil(err) + assert.True(certifiers.IsSeedNotFoundErr(err)) + + seed, err = p.GetByHash(seeds[3].Hash()) + require.NotNil(err) + assert.True(certifiers.IsSeedNotFoundErr(err)) + + // now add them all to the provider + for _, s := range seeds { + err = p.StoreSeed(s) + require.Nil(err) + // and make sure we can get it back + s2, err := p.GetByHash(s.Hash()) + assert.Nil(err) + assertSeedEqual(t, s, s2) + // by height as well + s2, err = p.GetByHeight(s.Height()) + assert.Nil(err) + assertSeedEqual(t, s, s2) + } + + // make sure we get the last hash if we overstep + seed, err = p.GetByHeight(5000) + if assert.Nil(err) { + assertSeedEqual(t, seeds[count-1], seed) + } + + // and middle ones as well + seed, err = p.GetByHeight(47) + if assert.Nil(err) { + // we only step by 10, so 40 must be the one below this + assert.Equal(40, seed.Height()) + } + +} From 8747bd5a8be64f6a676cc22d05cb795801503c92 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 14 Jul 2017 12:17:08 +0200 Subject: [PATCH 06/41] Add set struct to the store --- state/queue.go | 4 +- state/set.go | 152 ++++++++++++++++++++++++++++++++++++++++++++++ state/set_test.go | 77 +++++++++++++++++++++++ 3 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 state/set.go create mode 100644 state/set_test.go diff --git a/state/queue.go b/state/queue.go index db4869172..9460cf1d1 100644 --- a/state/queue.go +++ b/state/queue.go @@ -31,8 +31,8 @@ func (q *Queue) Tail() uint64 { } // Size returns how many elements are in the queue -func (q *Queue) Size() uint64 { - return q.tail - q.head +func (q *Queue) Size() int { + return int(q.tail - q.head) } // Push adds an element to the tail of the queue and returns it's location diff --git a/state/set.go b/state/set.go new file mode 100644 index 000000000..98d6a4095 --- /dev/null +++ b/state/set.go @@ -0,0 +1,152 @@ +package state + +import ( + "bytes" + "sort" + + wire "github.com/tendermint/go-wire" +) + +// Set allows us to add arbitrary k-v pairs, check existence, +// as well as iterate through the set (always in key order) +// +// If we had full access to the IAVL tree, this would be completely +// trivial and redundant +type Set struct { + store KVStore + keys KeyList +} + +var _ KVStore = &Set{} + +// NewSet loads or initializes a span of keys +func NewSet(store KVStore) *Set { + s := &Set{store: store} + s.loadKeys() + return s +} + +// Set puts a value at a given height. +// If the value is nil, or an empty slice, remove the key from the list +func (s *Set) Set(key []byte, value []byte) { + s.store.Set(makeBKey(key), value) + if len(value) > 0 { + s.addKey(key) + } else { + s.removeKey(key) + } + s.storeKeys() +} + +// Get returns the element with a key if it exists +func (s *Set) Get(key []byte) []byte { + return s.store.Get(makeBKey(key)) +} + +// Remove deletes this key from the set (same as setting value = nil) +func (s *Set) Remove(key []byte) { + s.store.Set(key, nil) +} + +// Exists checks for the existence of the key in the set +func (s *Set) Exists(key []byte) bool { + return len(s.Get(key)) > 0 +} + +// Size returns how many elements are in the set +func (s *Set) Size() int { + return len(s.keys) +} + +// List returns all keys in the set +// It makes a copy, so we don't modify this in place +func (s *Set) List() (keys KeyList) { + out := make([][]byte, len(s.keys)) + for i := range s.keys { + out[i] = append([]byte(nil), s.keys[i]...) + } + return out +} + +// addKey inserts this key, maintaining sorted order, no duplicates +func (s *Set) addKey(key []byte) { + for i, k := range s.keys { + cmp := bytes.Compare(k, key) + // don't add duplicates + if cmp == 0 { + return + } + // insert before the first key greater than input + if cmp > 0 { + // https://github.com/golang/go/wiki/SliceTricks + s.keys = append(s.keys, nil) + copy(s.keys[i+1:], s.keys[i:]) + s.keys[i] = key + return + } + } + // if it is higher than all (or empty keys), append + s.keys = append(s.keys, key) +} + +// removeKey removes this key if it is present, maintaining sorted order +func (s *Set) removeKey(key []byte) { + for i, k := range s.keys { + cmp := bytes.Compare(k, key) + // if there is a match, remove + if cmp == 0 { + s.keys = append(s.keys[:i], s.keys[i+1:]...) + return + } + // if we has the proper location, without finding it, abort + if cmp > 0 { + return + } + } +} + +func (s *Set) loadKeys() { + b := s.store.Get(keys) + if b == nil { + return + } + err := wire.ReadBinaryBytes(b, &s.keys) + // hahaha... just like i love to hate :) + if err != nil { + panic(err) + } +} + +func (s *Set) storeKeys() { + b := wire.BinaryBytes(s.keys) + s.store.Set(keys, b) +} + +// makeBKey prefixes the byte slice for the storage key +func makeBKey(key []byte) []byte { + return append(dataKey, key...) +} + +// KeyList is a sortable list of byte slices +type KeyList [][]byte + +//nolint +func (kl KeyList) Len() int { return len(kl) } +func (kl KeyList) Less(i, j int) bool { return bytes.Compare(kl[i], kl[j]) < 0 } +func (kl KeyList) Swap(i, j int) { kl[i], kl[j] = kl[j], kl[i] } + +var _ sort.Interface = KeyList{} + +// Equals checks for if the two lists have the same content... +// needed as == doesn't work for slices of slices +func (kl KeyList) Equals(kl2 KeyList) bool { + if len(kl) != len(kl2) { + return false + } + for i := range kl { + if !bytes.Equal(kl[i], kl2[i]) { + return false + } + } + return true +} diff --git a/state/set_test.go b/state/set_test.go new file mode 100644 index 000000000..2c2f3a5d2 --- /dev/null +++ b/state/set_test.go @@ -0,0 +1,77 @@ +package state + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" +) + +type pair struct { + k []byte + v []byte +} + +type setCase struct { + data []pair + // these are the tests to try out + gets []pair // for each item check the query matches + list KeyList // make sure the set returns the proper list +} + +func TestSet(t *testing.T) { + + a, b, c, d := []byte{0xaa}, []byte{0xbb}, []byte{0xcc}, []byte{0xdd} + + cases := []setCase{ + + // simplest queries + { + []pair{{a, a}, {b, b}, {c, c}}, + []pair{{c, c}, {d, nil}, {b, b}}, + KeyList{a, b, c}, + }, + // out of order + { + []pair{{c, a}, {a, b}, {d, c}, {b, d}}, + []pair{{a, b}, {b, d}}, + KeyList{a, b, c, d}, + }, + // duplicate and removing + { + []pair{{c, a}, {c, c}, {a, d}, {d, d}, {b, b}, {d, nil}, {a, nil}, {a, a}, {b, nil}}, + []pair{{a, a}, {c, c}, {b, nil}}, + KeyList{a, c}, + }, + } + + for i, tc := range cases { + store := NewMemKVStore() + + // initialize a queue and add items + s := NewSet(store) + for _, x := range tc.data { + s.Set(x.k, x.v) + } + + testSet(t, i, s, tc) + // reload and try the queries again + s2 := NewSet(store) + testSet(t, i+10, s2, tc) + } +} + +func testSet(t *testing.T, idx int, s *Set, tc setCase) { + assert := assert.New(t) + i := strconv.Itoa(idx) + + for _, g := range tc.gets { + v := s.Get(g.k) + assert.Equal(g.v, v, i) + e := s.Exists(g.k) + assert.Equal(e, (g.v != nil), i) + } + + l := s.List() + assert.True(tc.list.Equals(l), "%s: %v / %v", i, tc.list, l) +} From 9c1e695d46e7b017a56313d7215113e6285db156 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 14 Jul 2017 16:29:29 +0200 Subject: [PATCH 07/41] Implement register and update headers as handler --- modules/ibc/errors.go | 39 +++++++++++++++++++++++ modules/ibc/handler.go | 68 +++++++++++++++++++++++++++++++++++++++-- modules/ibc/provider.go | 20 ++++++++---- modules/ibc/store.go | 48 +++++++++++++++++++++++++++++ modules/ibc/tx.go | 13 +++----- 5 files changed, 172 insertions(+), 16 deletions(-) diff --git a/modules/ibc/errors.go b/modules/ibc/errors.go index a81836a9c..ee4e78672 100644 --- a/modules/ibc/errors.go +++ b/modules/ibc/errors.go @@ -1 +1,40 @@ package ibc + +import ( + "fmt" + + abci "github.com/tendermint/abci/types" + "github.com/tendermint/basecoin/errors" +) + +// nolint +var ( + errChainNotRegistered = fmt.Errorf("Chain not registered") + errChainAlreadyExists = fmt.Errorf("Chain already exists") + // errNotMember = fmt.Errorf("Not a member") + // errInsufficientSigs = fmt.Errorf("Not enough signatures") + // errNoMembers = fmt.Errorf("No members specified") + // errTooManyMembers = fmt.Errorf("Too many members specified") + // errNotEnoughMembers = fmt.Errorf("Not enough members specified") + + IBCCodeChainNotRegistered = abci.CodeType(1001) + IBCCodeChainAlreadyExists = abci.CodeType(1002) + IBCCodePacketAlreadyExists = abci.CodeType(1003) + IBCCodeUnknownHeight = abci.CodeType(1004) + IBCCodeInvalidCommit = abci.CodeType(1005) + IBCCodeInvalidProof = abci.CodeType(1006) +) + +func ErrNotRegistered(chainID string) error { + return errors.WithMessage(chainID, errChainNotRegistered, IBCCodeChainNotRegistered) +} +func IsNotRegistetedErr(err error) bool { + return errors.IsSameError(errChainNotRegistered, err) +} + +func ErrAlreadyRegistered(chainID string) error { + return errors.WithMessage(chainID, errChainAlreadyExists, IBCCodeChainAlreadyExists) +} +func IsAlreadyRegistetedErr(err error) bool { + return errors.IsSameError(errChainAlreadyExists, err) +} diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index 8de36d42d..cce8cb596 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -2,6 +2,8 @@ package ibc import ( "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/errors" + "github.com/tendermint/basecoin/stack" "github.com/tendermint/basecoin/state" ) @@ -30,11 +32,73 @@ func (Handler) Name() string { // 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) { - return res, nil + err = tx.ValidateBasic() + if err != nil { + return res, err + } + + switch t := tx.Unwrap().(type) { + case RegisterChainTx: + return h.initSeed(ctx, store, t) + case UpdateChainTx: + return h.updateSeed(ctx, store, t) + } + return res, errors.ErrUnknownTxType(tx.Unwrap()) } // DeliverTx verifies all signatures on the tx and updated the chain state // apropriately func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { - return res, nil + err = tx.ValidateBasic() + if err != nil { + return res, err + } + + switch t := tx.Unwrap().(type) { + case RegisterChainTx: + return h.initSeed(ctx, store, t) + case UpdateChainTx: + return h.updateSeed(ctx, store, t) + } + return res, errors.ErrUnknownTxType(tx.Unwrap()) +} + +// initSeed imports the first seed for this chain and accepts it as the root of trust +func (h Handler) initSeed(ctx basecoin.Context, store state.KVStore, + t RegisterChainTx) (res basecoin.Result, err error) { + + chainID := t.ChainID() + s := NewChainSet(store) + err = s.Register(chainID, ctx.BlockHeight(), t.Seed.Height()) + if err != nil { + return res, err + } + + space := stack.PrefixedStore(chainID, store) + provider := newDBProvider(space) + err = provider.StoreSeed(t.Seed) + return res, err +} + +// 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, + t UpdateChainTx) (res basecoin.Result, err error) { + + chainID := t.ChainID() + if !NewChainSet(store).Exists([]byte(chainID)) { + return res, ErrNotRegistered(chainID) + } + + // load the certifier for this chain + seed := t.Seed + space := stack.PrefixedStore(chainID, store) + cert, err := newCertifier(space, chainID, seed.Height()) + if err != nil { + return res, err + } + + // this will import the seed if it is valid in the current context + err = cert.Update(seed.Checkpoint, seed.Validators) + return res, err } diff --git a/modules/ibc/provider.go b/modules/ibc/provider.go index 91aebb77a..28cbd309a 100644 --- a/modules/ibc/provider.go +++ b/modules/ibc/provider.go @@ -14,14 +14,22 @@ const ( prefixPacket = "p" ) -// newCertifier loads up the current state of this chain to make a proper -func newCertifier(chainID string, store state.KVStore) (*certifiers.InquiringCertifier, error) { +// 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) { // each chain has their own prefixed subspace - space := stack.PrefixedStore(chainID, store) - p := newDBProvider(space) + p := newDBProvider(store) - // this gets the most recent verified seed - seed, err := certifiers.LatestSeed(p) + var seed certifiers.Seed + var err error + if h > 0 { + // this gets the most recent verified seed below the specified height + seed, err = p.GetByHeight(h) + } else { + // 0 or negative means start at latest seed + seed, err = certifiers.LatestSeed(p) + } if err != nil { return nil, err } diff --git a/modules/ibc/store.go b/modules/ibc/store.go index a81836a9c..31bbace0b 100644 --- a/modules/ibc/store.go +++ b/modules/ibc/store.go @@ -1 +1,49 @@ package ibc + +import ( + "github.com/tendermint/basecoin/stack" + "github.com/tendermint/basecoin/state" + wire "github.com/tendermint/go-wire" +) + +const ( + // this is the prefix for the list of chains + // we otherwise use the chainid as prefix, so this must not be an + // alpha-numeric byte + prefixChains = "**" +) + +// ChainInfo is the global info we store for each registered chain, +// besides the headers, proofs, and packets +type ChainInfo struct { + RegisteredAt uint64 `json:"registered_at"` + RemoteBlock int `json:"remote_block"` +} + +// ChainSet is the set of all registered chains +type ChainSet struct { + *state.Set +} + +// NewChainSet loads or initialized the ChainSet +func NewChainSet(store state.KVStore) ChainSet { + space := stack.PrefixedStore(prefixChains, store) + return ChainSet{ + Set: state.NewSet(space), + } +} + +// Register adds the named chain with some info +// returns error if already present +func (c ChainSet) Register(chainID string, ourHeight uint64, theirHeight int) error { + if c.Exists([]byte(chainID)) { + return ErrAlreadyRegistered(chainID) + } + info := ChainInfo{ + RegisteredAt: ourHeight, + RemoteBlock: theirHeight, + } + data := wire.BinaryBytes(info) + c.Set.Set([]byte(chainID), data) + return nil +} diff --git a/modules/ibc/tx.go b/modules/ibc/tx.go index 682758663..0131456ed 100644 --- a/modules/ibc/tx.go +++ b/modules/ibc/tx.go @@ -1,7 +1,6 @@ package ibc import ( - abci "github.com/tendermint/abci/types" "github.com/tendermint/light-client/certifiers" merkle "github.com/tendermint/merkleeyes/iavl" @@ -21,13 +20,6 @@ const ( TypeUpdateChain = NameIBC + "/update" TypePacketCreate = NameIBC + "/create" TypePacketPost = NameIBC + "/post" - - IBCCodeEncodingError = abci.CodeType(1001) - IBCCodeChainAlreadyExists = abci.CodeType(1002) - IBCCodePacketAlreadyExists = abci.CodeType(1003) - IBCCodeUnknownHeight = abci.CodeType(1004) - IBCCodeInvalidCommit = abci.CodeType(1005) - IBCCodeInvalidProof = abci.CodeType(1006) ) func init() { @@ -73,6 +65,11 @@ func (u UpdateChainTx) ValidateBasic() error { return u.Seed.ValidateBasic(u.ChainID()) } +// Wrap - used to satisfy TxInner +func (u UpdateChainTx) Wrap() basecoin.Tx { + return basecoin.Tx{u} +} + // PacketCreateTx is meant to be called by IPC, another module... // // this is the tx that will be sent to another app and the permissions it From 715d573e1adb77c197645b28215f067f86485a6f Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 14 Jul 2017 16:34:52 +0200 Subject: [PATCH 08/41] Thoughts on permissioning --- modules/ibc/handler.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index cce8cb596..faff8f7d2 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -13,6 +13,9 @@ const ( ) // Handler allows us to update the chain state or create a packet +// +// TODO: require auth for registration, the authorized actor (or role) +// should be defined in the handler, and set via SetOption type Handler struct { basecoin.NopOption } @@ -56,6 +59,7 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoi switch t := tx.Unwrap().(type) { case RegisterChainTx: + // TODO: do we want some permissioning for this??? return h.initSeed(ctx, store, t) case UpdateChainTx: return h.updateSeed(ctx, store, t) From 91eb91b803398b925691984ce8833fe6ea871feb Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Sun, 16 Jul 2017 22:46:21 +0200 Subject: [PATCH 09/41] Start defining CreatePacket --- modules/ibc/errors.go | 23 +++++++++++++--- modules/ibc/handler.go | 59 +++++++++++++++++++++++++++++++++++++++++- modules/ibc/store.go | 15 +++++++++++ modules/ibc/tx.go | 40 ++++++++++++++++------------ 4 files changed, 116 insertions(+), 21 deletions(-) diff --git a/modules/ibc/errors.go b/modules/ibc/errors.go index ee4e78672..df9d593ec 100644 --- a/modules/ibc/errors.go +++ b/modules/ibc/errors.go @@ -9,8 +9,10 @@ import ( // nolint var ( - errChainNotRegistered = fmt.Errorf("Chain not registered") - errChainAlreadyExists = fmt.Errorf("Chain already exists") + errChainNotRegistered = fmt.Errorf("Chain not registered") + errChainAlreadyExists = fmt.Errorf("Chain already exists") + errNeedsIBCPermission = fmt.Errorf("Needs app-permission to send IBC") + errCannotSetPermission = fmt.Errorf("Requesting invalid permission on IBC") // errNotMember = fmt.Errorf("Not a member") // errInsufficientSigs = fmt.Errorf("Not enough signatures") // errNoMembers = fmt.Errorf("No members specified") @@ -23,12 +25,13 @@ var ( IBCCodeUnknownHeight = abci.CodeType(1004) IBCCodeInvalidCommit = abci.CodeType(1005) IBCCodeInvalidProof = abci.CodeType(1006) + IBCCodeInvalidCall = abci.CodeType(1007) ) func ErrNotRegistered(chainID string) error { return errors.WithMessage(chainID, errChainNotRegistered, IBCCodeChainNotRegistered) } -func IsNotRegistetedErr(err error) bool { +func IsNotRegisteredErr(err error) bool { return errors.IsSameError(errChainNotRegistered, err) } @@ -38,3 +41,17 @@ func ErrAlreadyRegistered(chainID string) error { func IsAlreadyRegistetedErr(err error) bool { return errors.IsSameError(errChainAlreadyExists, err) } + +func ErrNeedsIBCPermission() error { + return errors.WithCode(errNeedsIBCPermission, IBCCodeInvalidCall) +} +func IsNeedsIBCPermissionErr(err error) bool { + return errors.IsSameError(errNeedsIBCPermission, err) +} + +func ErrCannotSetPermission() error { + return errors.WithCode(errCannotSetPermission, IBCCodeInvalidCall) +} +func IsCannotSetPermissionErr(err error) bool { + return errors.IsSameError(errCannotSetPermission, err) +} diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index faff8f7d2..1a5e191df 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -7,16 +7,27 @@ import ( "github.com/tendermint/basecoin/state" ) -// nolint const ( + // NameIBC is the name of this module NameIBC = "ibc" ) +var ( + allowIBC = []byte{0x42, 0xbe, 0xef, 0x1} +) + +// AllowIBC is the special code that an app must set to +// enable sending IBC packets for this app-type +func AllowIBC(app string) basecoin.Actor { + return basecoin.Actor{App: app, Address: allowIBC} +} + // Handler allows us to update the chain state or create a packet // // TODO: require auth for registration, the authorized actor (or role) // should be defined in the handler, and set via SetOption type Handler struct { + // TODO: add option to set who can permit registration and store it basecoin.NopOption } @@ -45,6 +56,8 @@ func (h Handler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin. return h.initSeed(ctx, store, t) case UpdateChainTx: return h.updateSeed(ctx, store, t) + case CreatePacketTx: + return h.createPacket(ctx, store, t) } return res, errors.ErrUnknownTxType(tx.Unwrap()) } @@ -63,6 +76,8 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoi return h.initSeed(ctx, store, t) case UpdateChainTx: return h.updateSeed(ctx, store, t) + case CreatePacketTx: + return h.createPacket(ctx, store, t) } return res, errors.ErrUnknownTxType(tx.Unwrap()) } @@ -106,3 +121,45 @@ func (h Handler) updateSeed(ctx basecoin.Context, store state.KVStore, err = cert.Update(seed.Checkpoint, seed.Validators) return res, err } + +// 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, + t CreatePacketTx) (res basecoin.Result, err error) { + + // make sure the chain is registed + dest := t.DestChain + if !NewChainSet(store).Exists([]byte(dest)) { + return res, ErrNotRegistered(dest) + } + + // make sure we have the special IBC permission + mod, err := t.Tx.GetKind() + if err != nil { + return res, err + } + if !ctx.HasPermission(AllowIBC(mod)) { + return res, ErrNeedsIBCPermission() + } + + // start making the packet to send + packet := Packet{ + DestChain: t.DestChain, + Tx: t.Tx, + Permissions: make([]basecoin.Actor, len(t.Permissions)), + } + + // make sure we have all the permissions we want to send + for i, p := range t.Permissions { + if !ctx.HasPermission(p) { + return res, ErrCannotSetPermission() + } + // add the permission with the current ChainID + packet.Permissions[i] = p + packet.Permissions[i].ChainID = ctx.ChainID() + } + + // now add it to the output queue.... + // TODO: where to store, also set the sequence.... + return res, nil +} diff --git a/modules/ibc/store.go b/modules/ibc/store.go index 31bbace0b..4208b363c 100644 --- a/modules/ibc/store.go +++ b/modules/ibc/store.go @@ -1,6 +1,7 @@ package ibc import ( + "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/stack" "github.com/tendermint/basecoin/state" wire "github.com/tendermint/go-wire" @@ -47,3 +48,17 @@ func (c ChainSet) Register(chainID string, ourHeight uint64, theirHeight int) er c.Set.Set([]byte(chainID), data) return nil } + +// Packet is a wrapped transaction and permission that we want to +// send off to another chain. +type Packet struct { + DestChain string `json:"dest_chain"` + Sequence int `json:"sequence"` + Permissions []basecoin.Actor `json:"permissions"` + Tx basecoin.Tx `json:"tx"` +} + +// Bytes returns a serialization of the Packet +func (p Packet) Bytes() []byte { + return wire.BinaryBytes(p) +} diff --git a/modules/ibc/tx.go b/modules/ibc/tx.go index 0131456ed..5f4bfe6cc 100644 --- a/modules/ibc/tx.go +++ b/modules/ibc/tx.go @@ -13,21 +13,21 @@ const ( // 0x3? series for ibc ByteRegisterChain = byte(0x30) ByteUpdateChain = byte(0x31) - BytePacketCreate = byte(0x32) - BytePacketPost = byte(0x33) + ByteCreatePacket = byte(0x32) + BytePostPacket = byte(0x33) TypeRegisterChain = NameIBC + "/register" TypeUpdateChain = NameIBC + "/update" - TypePacketCreate = NameIBC + "/create" - TypePacketPost = NameIBC + "/post" + TypeCreatePacket = NameIBC + "/create" + TypePostPacket = NameIBC + "/post" ) func init() { basecoin.TxMapper. RegisterImplementation(RegisterChainTx{}, TypeRegisterChain, ByteRegisterChain). RegisterImplementation(UpdateChainTx{}, TypeUpdateChain, ByteUpdateChain). - RegisterImplementation(PacketCreateTx{}, TypePacketCreate, BytePacketCreate). - RegisterImplementation(PacketPostTx{}, TypePacketPost, BytePacketPost) + RegisterImplementation(CreatePacketTx{}, TypeCreatePacket, ByteCreatePacket). + RegisterImplementation(PostPacketTx{}, TypePostPacket, BytePostPacket) } // RegisterChainTx allows you to register a new chain on this blockchain @@ -70,20 +70,21 @@ func (u UpdateChainTx) Wrap() basecoin.Tx { return basecoin.Tx{u} } -// PacketCreateTx is meant to be called by IPC, another module... +// CreatePacketTx is meant to be called by IPC, another module... // // this is the tx that will be sent to another app and the permissions it // comes with (which must be a subset of the permissions on the current tx) // -// TODO: how to control who can create packets (can I just signed create packet?) -type PacketCreateTx struct { +// If must have the special `AllowIBC` permission from the app +// that can send this packet (so only coins can request SendTx packet) +type CreatePacketTx struct { DestChain string `json:"dest_chain"` Permissions []basecoin.Actor `json:"permissions"` Tx basecoin.Tx `json:"tx"` } // ValidateBasic makes sure this is consistent - used to satisfy TxInner -func (p PacketCreateTx) ValidateBasic() error { +func (p CreatePacketTx) ValidateBasic() error { if p.DestChain == "" { return errors.ErrNoChain() } @@ -94,27 +95,32 @@ func (p PacketCreateTx) ValidateBasic() error { } // Wrap - used to satisfy TxInner -func (p PacketCreateTx) Wrap() basecoin.Tx { +func (p CreatePacketTx) Wrap() basecoin.Tx { return basecoin.Tx{p} } -// PacketPostTx takes a wrapped packet from another chain and +// PostPacketTx takes a wrapped packet from another chain and // TODO!!! -type PacketPostTx struct { +// also think... which chains can relay packets??? +// right now, enforce that these packets are only sent directly, +// not routed over the hub. add routing later. +type PostPacketTx struct { + // make sure we have this header... FromChainID string // The immediate source of the packet, not always Packet.SrcChainID FromChainHeight uint64 // The block height in which Packet was committed, to check Proof - Proof *merkle.IAVLProof - // Packet + // this proof must match the header and the packet.Bytes() + Proof *merkle.IAVLProof + Packet Packet } // ValidateBasic makes sure this is consistent - used to satisfy TxInner -func (p PacketPostTx) ValidateBasic() error { +func (p PostPacketTx) ValidateBasic() error { // TODO return nil } // Wrap - used to satisfy TxInner -func (p PacketPostTx) Wrap() basecoin.Tx { +func (p PostPacketTx) Wrap() basecoin.Tx { return basecoin.Tx{p} } From 485ec80cca5482bbfdd4971da779c532e63c4b73 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Sun, 16 Jul 2017 22:56:28 +0200 Subject: [PATCH 10/41] Add Registrar permission to attaching chains --- context.go | 7 +++++ modules/ibc/handler.go | 68 +++++++++++++++++++++++++++++------------- tx.go | 15 ++++++++++ 3 files changed, 70 insertions(+), 20 deletions(-) diff --git a/context.go b/context.go index 4da626ed3..24535dc0a 100644 --- a/context.go +++ b/context.go @@ -43,6 +43,13 @@ func (a Actor) Empty() bool { return a.ChainID == "" && a.App == "" && len(a.Address) == 0 } +// WithChain creates a copy of the actor with a different chainID +func (a Actor) WithChain(chainID string) (b Actor) { + b = a + b.ChainID = chainID + return +} + // Context is an interface, so we can implement "secure" variants that // rely on private fields to control the actions type Context interface { diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index 1a5e191df..114a58039 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -1,6 +1,9 @@ package ibc import ( + "github.com/tendermint/go-wire/data" + "github.com/tendermint/tmlibs/log" + "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/errors" "github.com/tendermint/basecoin/stack" @@ -10,6 +13,9 @@ import ( const ( // NameIBC is the name of this module NameIBC = "ibc" + // OptionRegistrar is the option name to set the actor + // to handle ibc chain registration + OptionRegistrar = "registrar" ) var ( @@ -23,29 +29,44 @@ func AllowIBC(app string) basecoin.Actor { } // Handler allows us to update the chain state or create a packet -// -// TODO: require auth for registration, the authorized actor (or role) -// should be defined in the handler, and set via SetOption type Handler struct { - // TODO: add option to set who can permit registration and store it - basecoin.NopOption + Registrar basecoin.Actor } -var _ basecoin.Handler = Handler{} +var _ basecoin.Handler = &Handler{} -// NewHandler makes a role handler to create roles -func NewHandler() Handler { - return Handler{} +// NewHandler makes a Handler that allows all chains to connect via IBC. +// Set a Registrar via SetOption to restrict it. +func NewHandler() *Handler { + return new(Handler) } // Name - return name space -func (Handler) Name() string { +func (*Handler) Name() string { return NameIBC } +// SetOption - sets the registrar for IBC +func (h *Handler) SetOption(l log.Logger, store state.KVStore, module, key, value string) (log string, err error) { + if module != NameIBC { + return "", errors.ErrUnknownModule(module) + } + if key == OptionRegistrar { + var act basecoin.Actor + err = data.FromJSON([]byte(value), &act) + if err != nil { + return "", err + } + h.Registrar = act + // TODO: save/load from disk! + return "Success", nil + } + return "", errors.ErrUnknownKey(key) +} + // 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.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { err = tx.ValidateBasic() if err != nil { return res, err @@ -64,7 +85,7 @@ func (h Handler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin. // DeliverTx verifies all signatures on the tx and updated 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.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { err = tx.ValidateBasic() if err != nil { return res, err @@ -72,7 +93,6 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoi switch t := tx.Unwrap().(type) { case RegisterChainTx: - // TODO: do we want some permissioning for this??? return h.initSeed(ctx, store, t) case UpdateChainTx: return h.updateSeed(ctx, store, t) @@ -82,10 +102,19 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoi return res, errors.ErrUnknownTxType(tx.Unwrap()) } -// initSeed imports the first seed for this chain and accepts it as the root of trust -func (h Handler) initSeed(ctx basecoin.Context, store state.KVStore, +// initSeed imports the first seed for this chain and +// 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, t RegisterChainTx) (res basecoin.Result, err error) { + // check permission to attach + // nothing set, means anyone can connect + if !h.Registrar.Empty() && !ctx.HasPermission(h.Registrar) { + return res, errors.ErrUnauthorized() + } + chainID := t.ChainID() s := NewChainSet(store) err = s.Register(chainID, ctx.BlockHeight(), t.Seed.Height()) @@ -101,7 +130,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.KVStore, t UpdateChainTx) (res basecoin.Result, err error) { chainID := t.ChainID() @@ -124,7 +153,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.KVStore, t CreatePacketTx) (res basecoin.Result, err error) { // make sure the chain is registed @@ -134,7 +163,7 @@ func (h Handler) createPacket(ctx basecoin.Context, store state.KVStore, } // make sure we have the special IBC permission - mod, err := t.Tx.GetKind() + mod, err := t.Tx.GetMod() if err != nil { return res, err } @@ -155,8 +184,7 @@ func (h Handler) createPacket(ctx basecoin.Context, store state.KVStore, return res, ErrCannotSetPermission() } // add the permission with the current ChainID - packet.Permissions[i] = p - packet.Permissions[i].ChainID = ctx.ChainID() + packet.Permissions[i] = p.WithChain(ctx.ChainID()) } // now add it to the output queue.... diff --git a/tx.go b/tx.go index cc143ffc4..3a5626a7f 100644 --- a/tx.go +++ b/tx.go @@ -1,6 +1,8 @@ package basecoin import ( + "strings" + "github.com/tendermint/go-wire/data" "github.com/tendermint/basecoin/errors" @@ -75,3 +77,16 @@ func (t Tx) GetKind() (string, error) { // grab the type we used in json return text.Kind, nil } + +func (t Tx) GetMod() (string, error) { + kind, err := t.GetKind() + if err != nil { + return "", err + } + parts := strings.SplitN(kind, "/", 2) + if len(parts) != 2 { + // TODO: return "base"? + return "", errors.ErrUnknownTxType(t) + } + return parts[0], nil +} From 7d3c0cd3e75c9da811add6536af86432c7cbc0a5 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 17 Jul 2017 21:19:35 +0200 Subject: [PATCH 11/41] Store registrar for ibc permissions in db --- modules/ibc/handler.go | 32 ++++++++++++++++---------------- modules/ibc/store.go | 24 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index 114a58039..380fd88dd 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -29,25 +29,23 @@ func AllowIBC(app string) basecoin.Actor { } // Handler allows us to update the chain state or create a packet -type Handler struct { - Registrar basecoin.Actor -} +type Handler struct{} -var _ basecoin.Handler = &Handler{} +var _ basecoin.Handler = Handler{} // NewHandler makes a Handler that allows all chains to connect via IBC. // Set a Registrar via SetOption to restrict it. -func NewHandler() *Handler { - return new(Handler) +func NewHandler() Handler { + return Handler{} } // Name - return name space -func (*Handler) Name() string { +func (Handler) Name() string { return NameIBC } // 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.KVStore, module, key, value string) (log string, err error) { if module != NameIBC { return "", errors.ErrUnknownModule(module) } @@ -57,8 +55,9 @@ func (h *Handler) SetOption(l log.Logger, store state.KVStore, module, key, valu if err != nil { return "", err } - h.Registrar = act - // TODO: save/load from disk! + // Save the data + info := HandlerInfo{act} + info.Save(store) return "Success", nil } return "", errors.ErrUnknownKey(key) @@ -66,7 +65,7 @@ func (h *Handler) SetOption(l log.Logger, store state.KVStore, module, key, valu // 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.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { err = tx.ValidateBasic() if err != nil { return res, err @@ -85,7 +84,7 @@ func (h *Handler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin // DeliverTx verifies all signatures on the tx and updated 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.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { err = tx.ValidateBasic() if err != nil { return res, err @@ -106,12 +105,13 @@ func (h *Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx baseco // 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.KVStore, t RegisterChainTx) (res basecoin.Result, err error) { // check permission to attach // nothing set, means anyone can connect - if !h.Registrar.Empty() && !ctx.HasPermission(h.Registrar) { + info := LoadInfo(store) + if !info.Registrar.Empty() && !ctx.HasPermission(info.Registrar) { return res, errors.ErrUnauthorized() } @@ -130,7 +130,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.KVStore, t UpdateChainTx) (res basecoin.Result, err error) { chainID := t.ChainID() @@ -153,7 +153,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.KVStore, t CreatePacketTx) (res basecoin.Result, err error) { // make sure the chain is registed diff --git a/modules/ibc/store.go b/modules/ibc/store.go index 4208b363c..2aa34c492 100644 --- a/modules/ibc/store.go +++ b/modules/ibc/store.go @@ -14,6 +14,30 @@ const ( prefixChains = "**" ) +var ( + handlerKey = []byte{0x2} +) + +// HandlerInfo is the global state of the ibc.Handler +type HandlerInfo struct { + Registrar basecoin.Actor `json:"registrar"` +} + +// Save the HandlerInfo to the store +func (h HandlerInfo) Save(store state.KVStore) { + b := wire.BinaryBytes(h) + store.Set(handlerKey, b) +} + +// LoadInfo loads the HandlerInfo from the data store +func LoadInfo(store state.KVStore) (h HandlerInfo) { + b := store.Get(handlerKey) + if len(b) > 0 { + wire.ReadBinaryBytes(b, &h) + } + return +} + // ChainInfo is the global info we store for each registered chain, // besides the headers, proofs, and packets type ChainInfo struct { From 1fc222e449aa98d3647bdad3c0f68f0817b3b31f Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 17 Jul 2017 21:28:36 +0200 Subject: [PATCH 12/41] Complete output queue for create packet --- modules/ibc/handler.go | 7 +++++-- modules/ibc/store.go | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index 380fd88dd..91eb3478a 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -173,7 +173,7 @@ func (h Handler) createPacket(ctx basecoin.Context, store state.KVStore, // start making the packet to send packet := Packet{ - DestChain: t.DestChain, + DestChain: dest, Tx: t.Tx, Permissions: make([]basecoin.Actor, len(t.Permissions)), } @@ -188,6 +188,9 @@ func (h Handler) createPacket(ctx basecoin.Context, store state.KVStore, } // now add it to the output queue.... - // TODO: where to store, also set the sequence.... + q := OutputQueue(store, dest) + packet.Sequence = q.Tail() + q.Push(packet.Bytes()) + return res, nil } diff --git a/modules/ibc/store.go b/modules/ibc/store.go index 2aa34c492..810377d89 100644 --- a/modules/ibc/store.go +++ b/modules/ibc/store.go @@ -12,8 +12,12 @@ const ( // we otherwise use the chainid as prefix, so this must not be an // alpha-numeric byte prefixChains = "**" + + prefixInput = "i" + prefixOutput = "o" ) +// this is used for the global handler info var ( handlerKey = []byte{0x2} ) @@ -77,7 +81,7 @@ func (c ChainSet) Register(chainID string, ourHeight uint64, theirHeight int) er // send off to another chain. type Packet struct { DestChain string `json:"dest_chain"` - Sequence int `json:"sequence"` + Sequence uint64 `json:"sequence"` Permissions []basecoin.Actor `json:"permissions"` Tx basecoin.Tx `json:"tx"` } @@ -86,3 +90,17 @@ type Packet struct { func (p Packet) Bytes() []byte { return wire.BinaryBytes(p) } + +// InputQueue returns the queue of input packets from this chain +func InputQueue(store state.KVStore, 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 { + ch := stack.PrefixedStore(chainID, store) + space := stack.PrefixedStore(prefixOutput, ch) + return state.NewQueue(space) +} From 30eced21c5dddfaa4a541d3cbe31f17fb0aa13aa Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 17 Jul 2017 21:52:02 +0200 Subject: [PATCH 13/41] Wrote bulk of ibc post packet middleware --- modules/ibc/middleware.go | 77 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/modules/ibc/middleware.go b/modules/ibc/middleware.go index cb32ea603..869795149 100644 --- a/modules/ibc/middleware.go +++ b/modules/ibc/middleware.go @@ -1,6 +1,8 @@ package ibc import ( + "errors" + "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/stack" "github.com/tendermint/basecoin/state" @@ -27,11 +29,82 @@ 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) { - return res, nil + // if it is not a PostPacket, just let it go through + post, ok := tx.Unwrap().(PostPacketTx) + if !ok { + return next.CheckTx(ctx, store, tx) + } + + // parse this packet and get the ibc-enhanced tx and context + ictx, itx, err := m.verifyPost(ctx, store, post) + if err != nil { + return res, err + } + return next.CheckTx(ictx, store, itx) } // 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) { - return res, nil + // if it is not a PostPacket, just let it go through + post, ok := tx.Unwrap().(PostPacketTx) + if !ok { + return next.DeliverTx(ctx, store, tx) + } + + // parse this packet and get the ibc-enhanced tx and context + ictx, itx, err := m.verifyPost(ctx, store, post) + if err != nil { + return res, err + } + return next.DeliverTx(ictx, store, itx) +} + +// verifyPost accepts a message bound for this chain... +// TODO: think about relay +func (m Middleware) verifyPost(ctx basecoin.Context, store state.KVStore, + tx PostPacketTx) (ictx basecoin.Context, itx basecoin.Tx, err error) { + + // make sure the chain is registered + from := tx.FromChainID + if !NewChainSet(store).Exists([]byte(from)) { + err = ErrNotRegistered(from) + return + } + + // make sure this sequence number is the next in the list + q := InputQueue(store, from) + packet := tx.Packet + if q.Tail() != packet.Sequence { + err = errors.New("Incorrect sequence number - out of order") // TODO + return + } + + // look up the referenced header + space := stack.PrefixedStore(from, store) + provider := newDBProvider(space) + // TODO: GetExactHeight helper? + seed, err := provider.GetByHeight(int(tx.FromChainHeight)) + if err != nil { + return ictx, itx, err + } + if seed.Height() != int(tx.FromChainHeight) { + err = errors.New("no such height") // TODO + return + } + + // verify the merkle hash.... + root := seed.Header.AppHash + key := []byte("?????") // TODO! + tx.Proof.Verify(key, packet.Bytes(), root) + + // TODO: verify packet.Permissions + + // add to input queue + q.Push(packet.Bytes()) + + // return the wrapped tx along with the extra permissions + ictx = ictx.WithPermissions(packet.Permissions...) + itx = packet.Tx + return } From 5da2b75fa0a4f3ca7ecda3554be66c7d61c482b0 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 18 Jul 2017 14:27:53 +0200 Subject: [PATCH 14/41] Clean up ibc PostPacket handling --- context.go | 11 +++++++ modules/ibc/errors.go | 62 +++++++++++++++++++++++++++++++-------- modules/ibc/handler.go | 13 ++++---- modules/ibc/middleware.go | 44 +++++++++++++++------------ modules/ibc/provider.go | 13 ++++++++ modules/ibc/store.go | 8 ++--- modules/ibc/tx.go | 31 +------------------- 7 files changed, 110 insertions(+), 72 deletions(-) diff --git a/context.go b/context.go index 24535dc0a..d22102e94 100644 --- a/context.go +++ b/context.go @@ -50,6 +50,17 @@ func (a Actor) WithChain(chainID string) (b Actor) { return } +type Actors []Actor + +func (a Actors) AllHaveChain(chainID string) bool { + for _, b := range a { + if b.ChainID != chainID { + return false + } + } + return true +} + // Context is an interface, so we can implement "secure" variants that // rely on private fields to control the actions type Context interface { diff --git a/modules/ibc/errors.go b/modules/ibc/errors.go index df9d593ec..748c3a948 100644 --- a/modules/ibc/errors.go +++ b/modules/ibc/errors.go @@ -11,21 +11,22 @@ import ( var ( errChainNotRegistered = fmt.Errorf("Chain not registered") errChainAlreadyExists = fmt.Errorf("Chain already exists") + errWrongDestChain = fmt.Errorf("This is not the destination") errNeedsIBCPermission = fmt.Errorf("Needs app-permission to send IBC") errCannotSetPermission = fmt.Errorf("Requesting invalid permission on IBC") - // errNotMember = fmt.Errorf("Not a member") - // errInsufficientSigs = fmt.Errorf("Not enough signatures") - // errNoMembers = fmt.Errorf("No members specified") - // errTooManyMembers = fmt.Errorf("Too many members specified") - // errNotEnoughMembers = fmt.Errorf("Not enough members specified") + errHeaderNotFound = fmt.Errorf("Header not found") + errPacketAlreadyExists = fmt.Errorf("Packet already handled") + errPacketOutOfOrder = fmt.Errorf("Packet out of order") + errInvalidProof = fmt.Errorf("Invalid merkle proof") - IBCCodeChainNotRegistered = abci.CodeType(1001) - IBCCodeChainAlreadyExists = abci.CodeType(1002) - IBCCodePacketAlreadyExists = abci.CodeType(1003) - IBCCodeUnknownHeight = abci.CodeType(1004) - IBCCodeInvalidCommit = abci.CodeType(1005) - IBCCodeInvalidProof = abci.CodeType(1006) - IBCCodeInvalidCall = abci.CodeType(1007) + IBCCodeChainNotRegistered = abci.CodeType(1001) + IBCCodeChainAlreadyExists = abci.CodeType(1002) + IBCCodeUnknownChain = abci.CodeType(1003) + IBCCodeInvalidPacketSequence = abci.CodeType(1004) + IBCCodeUnknownHeight = abci.CodeType(1005) + IBCCodeInvalidCommit = abci.CodeType(1006) + IBCCodeInvalidProof = abci.CodeType(1007) + IBCCodeInvalidCall = abci.CodeType(1008) ) func ErrNotRegistered(chainID string) error { @@ -42,6 +43,13 @@ func IsAlreadyRegistetedErr(err error) bool { return errors.IsSameError(errChainAlreadyExists, err) } +func ErrWrongDestChain(chainID string) error { + return errors.WithMessage(chainID, errWrongDestChain, IBCCodeUnknownChain) +} +func IsWrongDestChainErr(err error) bool { + return errors.IsSameError(errWrongDestChain, err) +} + func ErrNeedsIBCPermission() error { return errors.WithCode(errNeedsIBCPermission, IBCCodeInvalidCall) } @@ -55,3 +63,33 @@ func ErrCannotSetPermission() error { func IsCannotSetPermissionErr(err error) bool { return errors.IsSameError(errCannotSetPermission, err) } + +func ErrHeaderNotFound(h int) error { + msg := fmt.Sprintf("height %d", h) + return errors.WithMessage(msg, errHeaderNotFound, IBCCodeUnknownHeight) +} +func IsHeaderNotFoundErr(err error) bool { + return errors.IsSameError(errHeaderNotFound, err) +} + +func ErrPacketAlreadyExists() error { + return errors.WithCode(errPacketAlreadyExists, IBCCodeInvalidPacketSequence) +} +func IsPacketAlreadyExistsErr(err error) bool { + return errors.IsSameError(errPacketAlreadyExists, err) +} + +func ErrPacketOutOfOrder(seq uint64) error { + msg := fmt.Sprintf("expected %d", seq) + return errors.WithMessage(msg, errPacketOutOfOrder, IBCCodeInvalidPacketSequence) +} +func IsPacketOutOfOrderErr(err error) bool { + return errors.IsSameError(errPacketOutOfOrder, err) +} + +func ErrInvalidProof() error { + return errors.WithCode(errInvalidProof, IBCCodeInvalidProof) +} +func IsInvalidProofErr(err error) bool { + return errors.IsSameError(errInvalidProof, err) +} diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index 91eb3478a..488f556b7 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -92,6 +92,12 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoi 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) @@ -108,13 +114,6 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoi func (h Handler) initSeed(ctx basecoin.Context, store state.KVStore, t RegisterChainTx) (res basecoin.Result, err error) { - // check permission to attach - // nothing set, means anyone can connect - info := LoadInfo(store) - if !info.Registrar.Empty() && !ctx.HasPermission(info.Registrar) { - return res, errors.ErrUnauthorized() - } - chainID := t.ChainID() s := NewChainSet(store) err = s.Register(chainID, ctx.BlockHeight(), t.Seed.Height()) diff --git a/modules/ibc/middleware.go b/modules/ibc/middleware.go index 869795149..16f45060f 100644 --- a/modules/ibc/middleware.go +++ b/modules/ibc/middleware.go @@ -1,8 +1,6 @@ package ibc import ( - "errors" - "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/stack" "github.com/tendermint/basecoin/state" @@ -68,40 +66,48 @@ func (m Middleware) verifyPost(ctx basecoin.Context, store state.KVStore, // make sure the chain is registered from := tx.FromChainID if !NewChainSet(store).Exists([]byte(from)) { - err = ErrNotRegistered(from) - return + return ictx, itx, ErrNotRegistered(from) + } + + // TODO: how to deal with routing/relaying??? + packet := tx.Packet + if packet.DestChain != ctx.ChainID() { + return ictx, itx, ErrWrongDestChain(packet.DestChain) + } + + // verify packet.Permissions all come from the other chain + if !packet.Permissions.AllHaveChain(tx.FromChainID) { + return ictx, itx, ErrCannotSetPermission() } // make sure this sequence number is the next in the list q := InputQueue(store, from) - packet := tx.Packet - if q.Tail() != packet.Sequence { - err = errors.New("Incorrect sequence number - out of order") // TODO - return + tail := q.Tail() + if packet.Sequence < tail { + return ictx, itx, ErrPacketAlreadyExists() + } + if packet.Sequence > tail { + return ictx, itx, ErrPacketOutOfOrder(tail) } // look up the referenced header space := stack.PrefixedStore(from, store) provider := newDBProvider(space) - // TODO: GetExactHeight helper? - seed, err := provider.GetByHeight(int(tx.FromChainHeight)) + seed, err := provider.GetExactHeight(int(tx.FromChainHeight)) if err != nil { return ictx, itx, err } - if seed.Height() != int(tx.FromChainHeight) { - err = errors.New("no such height") // TODO - return - } // verify the merkle hash.... root := seed.Header.AppHash - key := []byte("?????") // TODO! - tx.Proof.Verify(key, packet.Bytes(), root) - - // TODO: verify packet.Permissions + pBytes := packet.Bytes() + valid := tx.Proof.Verify(tx.Key, pBytes, root) + if !valid { + return ictx, itx, ErrInvalidProof() + } // add to input queue - q.Push(packet.Bytes()) + q.Push(pBytes) // return the wrapped tx along with the extra permissions ictx = ictx.WithPermissions(packet.Permissions...) diff --git a/modules/ibc/provider.go b/modules/ibc/provider.go index 28cbd309a..7f140c3c2 100644 --- a/modules/ibc/provider.go +++ b/modules/ibc/provider.go @@ -80,3 +80,16 @@ func (d *dbProvider) GetByHash(hash []byte) (seed certifiers.Seed, err error) { err = wire.ReadBinaryBytes(b, &seed) return } + +// GetExactHeight is like GetByHeight, but returns an error instead of +// closest match if there is no exact match +func (d *dbProvider) GetExactHeight(h int) (seed certifiers.Seed, err error) { + seed, err = d.GetByHeight(h) + if err != nil { + return + } + if seed.Height() != h { + err = ErrHeaderNotFound(h) + } + return +} diff --git a/modules/ibc/store.go b/modules/ibc/store.go index 810377d89..fc37b0414 100644 --- a/modules/ibc/store.go +++ b/modules/ibc/store.go @@ -80,10 +80,10 @@ func (c ChainSet) Register(chainID string, ourHeight uint64, theirHeight int) er // Packet is a wrapped transaction and permission that we want to // send off to another chain. type Packet struct { - DestChain string `json:"dest_chain"` - Sequence uint64 `json:"sequence"` - Permissions []basecoin.Actor `json:"permissions"` - Tx basecoin.Tx `json:"tx"` + DestChain string `json:"dest_chain"` + Sequence uint64 `json:"sequence"` + Permissions basecoin.Actors `json:"permissions"` + Tx basecoin.Tx `json:"tx"` } // Bytes returns a serialization of the Packet diff --git a/modules/ibc/tx.go b/modules/ibc/tx.go index 5f4bfe6cc..9c4f60207 100644 --- a/modules/ibc/tx.go +++ b/modules/ibc/tx.go @@ -110,6 +110,7 @@ type PostPacketTx struct { FromChainHeight uint64 // The block height in which Packet was committed, to check Proof // this proof must match the header and the packet.Bytes() Proof *merkle.IAVLProof + Key []byte Packet Packet } @@ -123,33 +124,3 @@ func (p PostPacketTx) ValidateBasic() error { func (p PostPacketTx) Wrap() basecoin.Tx { return basecoin.Tx{p} } - -// proof := tx.Proof -// if proof == nil { -// sm.res.Code = IBCCodeInvalidProof -// sm.res.Log = "Proof is nil" -// return -// } -// packetBytes := wire.BinaryBytes(packet) - -// // Make sure packet's proof matches given (packet, key, blockhash) -// ok := proof.Verify(packetKeyEgress, packetBytes, header.AppHash) -// if !ok { -// sm.res.Code = IBCCodeInvalidProof -// sm.res.Log = fmt.Sprintf("Proof is invalid. key: %s; packetByes %X; header %v; proof %v", packetKeyEgress, packetBytes, header, proof) -// return -// } - -// // Execute payload -// switch payload := packet.Payload.(type) { -// case DataPayload: -// // do nothing -// case CoinsPayload: -// // Add coins to destination account -// acc := types.GetAccount(sm.store, payload.Address) -// if acc == nil { -// acc = &types.Account{} -// } -// acc.Balance = acc.Balance.Plus(payload.Coins) -// types.SetAccount(sm.store, payload.Address, acc) -// } From f1c969772008a6f97b3f0bd63e0ac4d8060c7b55 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 18 Jul 2017 16:05:36 +0200 Subject: [PATCH 15/41] First ibc registration tests --- modules/ibc/errors.go | 14 ++++++- modules/ibc/handler.go | 1 + modules/ibc/ibc_test.go | 87 +++++++++++++++++++++++++++++++++++++++++ modules/ibc/tx.go | 12 +++++- 4 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 modules/ibc/ibc_test.go diff --git a/modules/ibc/errors.go b/modules/ibc/errors.go index 748c3a948..5df7a7d33 100644 --- a/modules/ibc/errors.go +++ b/modules/ibc/errors.go @@ -18,6 +18,7 @@ var ( errPacketAlreadyExists = fmt.Errorf("Packet already handled") errPacketOutOfOrder = fmt.Errorf("Packet out of order") errInvalidProof = fmt.Errorf("Invalid merkle proof") + msgInvalidCommit = "Invalid header and commit" IBCCodeChainNotRegistered = abci.CodeType(1001) IBCCodeChainAlreadyExists = abci.CodeType(1002) @@ -39,7 +40,7 @@ func IsNotRegisteredErr(err error) bool { func ErrAlreadyRegistered(chainID string) error { return errors.WithMessage(chainID, errChainAlreadyExists, IBCCodeChainAlreadyExists) } -func IsAlreadyRegistetedErr(err error) bool { +func IsAlreadyRegisteredErr(err error) bool { return errors.IsSameError(errChainAlreadyExists, err) } @@ -93,3 +94,14 @@ func ErrInvalidProof() error { func IsInvalidProofErr(err error) bool { return errors.IsSameError(errInvalidProof, err) } + +func ErrInvalidCommit(err error) error { + e := errors.WithMessage(msgInvalidCommit, err, IBCCodeInvalidCommit) + fmt.Println("make", e.ErrorCode()) + return e +} +func IsInvalidCommitErr(err error) bool { + // fmt.Println("check", err.(errors.TMError).ErrorCode(), IBCCodeInvalidCommit) + fmt.Println("check", IBCCodeInvalidCommit) + return errors.HasErrorCode(err, IBCCodeInvalidCommit) +} diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index 488f556b7..76c584ec1 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -114,6 +114,7 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.KVStore, tx basecoi func (h Handler) initSeed(ctx basecoin.Context, store state.KVStore, t RegisterChainTx) (res basecoin.Result, err error) { + // verify that the header looks reasonable chainID := t.ChainID() s := NewChainSet(store) err = s.Register(chainID, ctx.BlockHeight(), t.Seed.Height()) diff --git a/modules/ibc/ibc_test.go b/modules/ibc/ibc_test.go new file mode 100644 index 000000000..76133d5a3 --- /dev/null +++ b/modules/ibc/ibc_test.go @@ -0,0 +1,87 @@ +package ibc + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tendermint/basecoin/stack" + "github.com/tendermint/basecoin/state" + "github.com/tendermint/light-client/certifiers" +) + +type checkErr func(error) bool + +func noErr(err error) bool { + return err == nil +} + +func genEmptySeed(keys certifiers.ValKeys, chain string, h int, + appHash []byte, count int) certifiers.Seed { + + vals := keys.ToValidators(10, 0) + cp := keys.GenCheckpoint(chain, h, nil, vals, appHash, 0, count) + return certifiers.Seed{cp, vals} +} + +func TestIBCRegister(t *testing.T) { + assert := assert.New(t) + + // the validators we use to make seeds + keys := certifiers.GenValKeys(5) + keys2 := certifiers.GenValKeys(7) + appHash := []byte{0, 4, 7, 23} + appHash2 := []byte{12, 34, 56, 78} + + // badSeed doesn't validate + badSeed := genEmptySeed(keys2, "chain-2", 123, appHash, len(keys2)) + badSeed.Header.AppHash = appHash2 + + cases := []struct { + seed certifiers.Seed + checker checkErr + }{ + { + genEmptySeed(keys, "chain-1", 100, appHash, len(keys)), + noErr, + }, + { + genEmptySeed(keys, "chain-1", 200, appHash, len(keys)), + IsAlreadyRegisteredErr, + }, + { + badSeed, + IsInvalidCommitErr, + }, + { + genEmptySeed(keys2, "chain-2", 123, appHash2, 5), + noErr, + }, + } + + ctx := stack.MockContext("hub", 50) + store := state.NewMemKVStore() + // no registrar here + app := stack.New().Dispatch(stack.WrapHandler(NewHandler())) + + for i, tc := range cases { + tx := RegisterChainTx{tc.seed}.Wrap() + _, err := app.DeliverTx(ctx, store, tx) + assert.True(tc.checker(err), "%d: %+v", i, err) + } +} + +func TestIBCUpdate(t *testing.T) { + +} + +func TestIBCCreatePacket(t *testing.T) { + +} + +func TestIBCPostPacket(t *testing.T) { + +} + +func TestIBCSendTx(t *testing.T) { + +} diff --git a/modules/ibc/tx.go b/modules/ibc/tx.go index 9c4f60207..ca6d825d4 100644 --- a/modules/ibc/tx.go +++ b/modules/ibc/tx.go @@ -42,7 +42,11 @@ func (r RegisterChainTx) ChainID() string { // ValidateBasic makes sure this is consistent, without checking the sigs func (r RegisterChainTx) ValidateBasic() error { - return r.Seed.ValidateBasic(r.ChainID()) + err := r.Seed.ValidateBasic(r.ChainID()) + if err != nil { + err = ErrInvalidCommit(err) + } + return err } // Wrap - used to satisfy TxInner @@ -62,7 +66,11 @@ func (u UpdateChainTx) ChainID() string { // ValidateBasic makes sure this is consistent, without checking the sigs func (u UpdateChainTx) ValidateBasic() error { - return u.Seed.ValidateBasic(u.ChainID()) + err := u.Seed.ValidateBasic(u.ChainID()) + if err != nil { + err = ErrInvalidCommit(err) + } + return err } // Wrap - used to satisfy TxInner From 3ddcf91303046d140b58b551beabe1fcfc1ab570 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 18 Jul 2017 16:19:16 +0200 Subject: [PATCH 16/41] Test ibc registration permissions --- modules/ibc/ibc_test.go | 89 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/modules/ibc/ibc_test.go b/modules/ibc/ibc_test.go index 76133d5a3..336e1aa2b 100644 --- a/modules/ibc/ibc_test.go +++ b/modules/ibc/ibc_test.go @@ -1,12 +1,19 @@ package ibc import ( + "encoding/json" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/tmlibs/log" + + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/errors" "github.com/tendermint/basecoin/stack" "github.com/tendermint/basecoin/state" - "github.com/tendermint/light-client/certifiers" ) type checkErr func(error) bool @@ -23,6 +30,7 @@ func genEmptySeed(keys certifiers.ValKeys, chain string, h int, return certifiers.Seed{cp, vals} } +// this tests registration without registrar permissions func TestIBCRegister(t *testing.T) { assert := assert.New(t) @@ -60,7 +68,6 @@ func TestIBCRegister(t *testing.T) { ctx := stack.MockContext("hub", 50) store := state.NewMemKVStore() - // no registrar here app := stack.New().Dispatch(stack.WrapHandler(NewHandler())) for i, tc := range cases { @@ -70,6 +77,84 @@ func TestIBCRegister(t *testing.T) { } } +// this tests registration without registrar permissions +func TestIBCRegisterPermissions(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + // the validators we use to make seeds + keys := certifiers.GenValKeys(4) + appHash := []byte{0x17, 0x21, 0x5, 0x1e} + + foobar := basecoin.Actor{App: "foo", Address: []byte("bar")} + baz := basecoin.Actor{App: "baz", Address: []byte("bar")} + foobaz := basecoin.Actor{App: "foo", Address: []byte("baz")} + + cases := []struct { + seed certifiers.Seed + registrar basecoin.Actor + signer basecoin.Actor + checker checkErr + }{ + // no sig, no registrar + { + seed: genEmptySeed(keys, "chain-1", 100, appHash, len(keys)), + checker: noErr, + }, + // sig, no registrar + { + seed: genEmptySeed(keys, "chain-2", 100, appHash, len(keys)), + signer: foobaz, + checker: noErr, + }, + // registrar, no sig + { + seed: genEmptySeed(keys, "chain-3", 100, appHash, len(keys)), + registrar: foobar, + checker: errors.IsUnauthorizedErr, + }, + // registrar, wrong sig + { + seed: genEmptySeed(keys, "chain-4", 100, appHash, len(keys)), + signer: foobaz, + registrar: foobar, + checker: errors.IsUnauthorizedErr, + }, + // registrar, wrong sig + { + seed: genEmptySeed(keys, "chain-5", 100, appHash, len(keys)), + signer: baz, + registrar: foobar, + checker: errors.IsUnauthorizedErr, + }, + // registrar, proper sig + { + seed: genEmptySeed(keys, "chain-6", 100, appHash, len(keys)), + signer: foobar, + registrar: foobar, + checker: noErr, + }, + } + + store := state.NewMemKVStore() + app := stack.New().Dispatch(stack.WrapHandler(NewHandler())) + + for i, tc := range cases { + // set option specifies the registrar + msg, err := json.Marshal(tc.registrar) + require.Nil(err, "%+v", err) + _, err = app.SetOption(log.NewNopLogger(), store, + NameIBC, OptionRegistrar, string(msg)) + require.Nil(err, "%+v", err) + + // add permissions to the context + ctx := stack.MockContext("hub", 50).WithPermissions(tc.signer) + tx := RegisterChainTx{tc.seed}.Wrap() + _, err = app.DeliverTx(ctx, store, tx) + assert.True(tc.checker(err), "%d: %+v", i, err) + } +} + func TestIBCUpdate(t *testing.T) { } From 883b9836114c3d9eeb6a716cffb526d5d0be1d72 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 18 Jul 2017 16:38:31 +0200 Subject: [PATCH 17/41] Test ibc header updates with dynamic sets --- modules/ibc/errors.go | 9 +++-- modules/ibc/handler.go | 2 +- modules/ibc/ibc_test.go | 73 ++++++++++++++++++++++++++++++++++++++++- modules/ibc/provider.go | 2 +- 4 files changed, 78 insertions(+), 8 deletions(-) diff --git a/modules/ibc/errors.go b/modules/ibc/errors.go index 5df7a7d33..8fafe7a73 100644 --- a/modules/ibc/errors.go +++ b/modules/ibc/errors.go @@ -96,12 +96,11 @@ func IsInvalidProofErr(err error) bool { } func ErrInvalidCommit(err error) error { - e := errors.WithMessage(msgInvalidCommit, err, IBCCodeInvalidCommit) - fmt.Println("make", e.ErrorCode()) - return e + if err == nil { + return nil + } + return errors.WithMessage(msgInvalidCommit, err, IBCCodeInvalidCommit) } func IsInvalidCommitErr(err error) bool { - // fmt.Println("check", err.(errors.TMError).ErrorCode(), IBCCodeInvalidCommit) - fmt.Println("check", IBCCodeInvalidCommit) return errors.HasErrorCode(err, IBCCodeInvalidCommit) } diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index 76c584ec1..ee494d7e1 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -148,7 +148,7 @@ func (h Handler) updateSeed(ctx basecoin.Context, store state.KVStore, // this will import the seed if it is valid in the current context err = cert.Update(seed.Checkpoint, seed.Validators) - return res, err + return res, ErrInvalidCommit(err) } // createPacket makes sure all permissions are good and the destination diff --git a/modules/ibc/ibc_test.go b/modules/ibc/ibc_test.go index 336e1aa2b..903aa6b71 100644 --- a/modules/ibc/ibc_test.go +++ b/modules/ibc/ibc_test.go @@ -77,7 +77,7 @@ func TestIBCRegister(t *testing.T) { } } -// this tests registration without registrar permissions +// this tests permission controls on ibc registration func TestIBCRegisterPermissions(t *testing.T) { assert := assert.New(t) require := require.New(t) @@ -155,8 +155,79 @@ func TestIBCRegisterPermissions(t *testing.T) { } } +// this verifies that we can properly update the headers on the chain func TestIBCUpdate(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + // this is the root seed, that others are evaluated against + keys := certifiers.GenValKeys(7) + appHash := []byte{0, 4, 7, 23} + start := 100 // initial height + root := genEmptySeed(keys, "chain-1", 100, appHash, len(keys)) + + keys2 := keys.Extend(2) + keys3 := keys2.Extend(2) + + // create the app and register the root of trust (for chain-1) + ctx := stack.MockContext("hub", 50) + store := state.NewMemKVStore() + app := stack.New().Dispatch(stack.WrapHandler(NewHandler())) + tx := RegisterChainTx{root}.Wrap() + _, err := app.DeliverTx(ctx, store, tx) + require.Nil(err, "%+v", err) + + cases := []struct { + seed certifiers.Seed + checker checkErr + }{ + // same validator, higher up + { + genEmptySeed(keys, "chain-1", start+50, []byte{22}, len(keys)), + noErr, + }, + // same validator, between existing (not most recent) + { + genEmptySeed(keys, "chain-1", start+5, []byte{15, 43}, len(keys)), + noErr, + }, + // same validators, before root of trust + { + genEmptySeed(keys, "chain-1", start-8, []byte{11, 77}, len(keys)), + IsHeaderNotFoundErr, + }, + // insufficient signatures + { + genEmptySeed(keys, "chain-1", start+60, []byte{24}, len(keys)/2), + IsInvalidCommitErr, + }, + // unregistered chain + { + genEmptySeed(keys, "chain-2", start+60, []byte{24}, len(keys)/2), + IsNotRegisteredErr, + }, + // too much change (keys -> keys3) + { + genEmptySeed(keys3, "chain-1", start+100, []byte{22}, len(keys3)), + IsInvalidCommitErr, + }, + // legit update to validator set (keys -> keys2) + { + genEmptySeed(keys2, "chain-1", start+90, []byte{33}, len(keys2)), + noErr, + }, + // now impossible jump works (keys -> keys2 -> keys3) + { + genEmptySeed(keys3, "chain-1", start+100, []byte{44}, len(keys3)), + noErr, + }, + } + + for i, tc := range cases { + tx := UpdateChainTx{tc.seed}.Wrap() + _, err := app.DeliverTx(ctx, store, tx) + assert.True(tc.checker(err), "%d: %+v", i, err) + } } func TestIBCCreatePacket(t *testing.T) { diff --git a/modules/ibc/provider.go b/modules/ibc/provider.go index 7f140c3c2..2c508836f 100644 --- a/modules/ibc/provider.go +++ b/modules/ibc/provider.go @@ -31,7 +31,7 @@ func newCertifier(store state.KVStore, chainID string, h int) (*certifiers.Inqui seed, err = certifiers.LatestSeed(p) } if err != nil { - return nil, err + return nil, ErrHeaderNotFound(h) } // we have no source for untrusted keys, but use the db to load trusted history From 272a65a2c7edf62acffdc2ce8ae0035592c59da4 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 18 Jul 2017 17:16:28 +0200 Subject: [PATCH 18/41] Add tests for creating packets --- modules/ibc/handler.go | 5 +- modules/ibc/ibc_test.go | 110 ++++++++++++++++++++++++++++++++++++++++ modules/ibc/tx.go | 6 +-- 3 files changed, 117 insertions(+), 4 deletions(-) diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index ee494d7e1..bd3b2b04c 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -1,6 +1,8 @@ package ibc import ( + "fmt" + "github.com/tendermint/go-wire/data" "github.com/tendermint/tmlibs/log" @@ -192,5 +194,6 @@ func (h Handler) createPacket(ctx basecoin.Context, store state.KVStore, packet.Sequence = q.Tail() q.Push(packet.Bytes()) - return res, nil + res = basecoin.Result{Log: fmt.Sprintf("Packet %s %d", dest, packet.Sequence)} + return } diff --git a/modules/ibc/ibc_test.go b/modules/ibc/ibc_test.go index 903aa6b71..8756a9131 100644 --- a/modules/ibc/ibc_test.go +++ b/modules/ibc/ibc_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + wire "github.com/tendermint/go-wire" "github.com/tendermint/light-client/certifiers" "github.com/tendermint/tmlibs/log" @@ -230,11 +231,120 @@ func TestIBCUpdate(t *testing.T) { } } +// try to create an ibc packet and verify the number we get back func TestIBCCreatePacket(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + // this is the root seed, that others are evaluated against + keys := certifiers.GenValKeys(7) + appHash := []byte{1, 2, 3, 4} + start := 100 // initial height + chainID := "cosmos-hub" + root := genEmptySeed(keys, chainID, start, appHash, len(keys)) + + // create the app and register the root of trust (for chain-1) + ctx := stack.MockContext("hub", 50) + store := state.NewMemKVStore() + app := stack.New().Dispatch(stack.WrapHandler(NewHandler())) + tx := RegisterChainTx{root}.Wrap() + _, err := app.DeliverTx(ctx, store, tx) + require.Nil(err, "%+v", err) + + // this is the tx we send, and the needed permission to send it + raw := stack.NewRawTx([]byte{0xbe, 0xef}) + ibcPerm := AllowIBC(stack.NameOK) + somePerm := basecoin.Actor{App: "some", Address: []byte("perm")} + + cases := []struct { + dest string + ibcPerms basecoin.Actors + ctxPerms basecoin.Actors + checker checkErr + }{ + // wrong chain -> error + { + dest: "some-other-chain", + ctxPerms: basecoin.Actors{ibcPerm}, + checker: IsNotRegisteredErr, + }, + + // no ibc permission -> error + { + dest: chainID, + checker: IsNeedsIBCPermissionErr, + }, + + // correct -> nice sequence + { + dest: chainID, + ctxPerms: basecoin.Actors{ibcPerm}, + checker: noErr, + }, + + // requesting invalid permissions -> error + { + dest: chainID, + ibcPerms: basecoin.Actors{somePerm}, + ctxPerms: basecoin.Actors{ibcPerm}, + checker: IsCannotSetPermissionErr, + }, + + // requesting extra permissions when present + { + dest: chainID, + ibcPerms: basecoin.Actors{somePerm}, + ctxPerms: basecoin.Actors{ibcPerm, somePerm}, + checker: noErr, + }, + } + + for i, tc := range cases { + tx := CreatePacketTx{ + DestChain: tc.dest, + Permissions: tc.ibcPerms, + Tx: raw, + }.Wrap() + + myCtx := ctx.WithPermissions(tc.ctxPerms...) + _, err = app.DeliverTx(myCtx, store, tx) + assert.True(tc.checker(err), "%d: %+v", i, err) + } + + // query packet state - make sure both packets are properly writen + p := stack.PrefixedStore(NameIBC, store) + q := OutputQueue(p, chainID) + if assert.Equal(2, q.Size()) { + expected := []struct { + seq uint64 + perm basecoin.Actors + }{ + {0, nil}, + {1, basecoin.Actors{somePerm}}, + } + + for _, tc := range expected { + var packet Packet + err = wire.ReadBinaryBytes(q.Pop(), &packet) + require.Nil(err, "%+v", err) + assert.Equal(chainID, packet.DestChain) + assert.EqualValues(tc.seq, packet.Sequence) + assert.Equal(raw, packet.Tx) + assert.Equal(len(tc.perm), len(packet.Permissions)) + } + } } func TestIBCPostPacket(t *testing.T) { + // make proofs + + // bad chain -> error + // no matching header -> error + // bad proof -> error + // out of order -> error + // invalid permissions -> error + + // all good -> execute tx } diff --git a/modules/ibc/tx.go b/modules/ibc/tx.go index ca6d825d4..69b8bcf41 100644 --- a/modules/ibc/tx.go +++ b/modules/ibc/tx.go @@ -86,9 +86,9 @@ func (u UpdateChainTx) Wrap() basecoin.Tx { // If must have the special `AllowIBC` permission from the app // that can send this packet (so only coins can request SendTx packet) type CreatePacketTx struct { - DestChain string `json:"dest_chain"` - Permissions []basecoin.Actor `json:"permissions"` - Tx basecoin.Tx `json:"tx"` + DestChain string `json:"dest_chain"` + Permissions basecoin.Actors `json:"permissions"` + Tx basecoin.Tx `json:"tx"` } // ValidateBasic makes sure this is consistent - used to satisfy TxInner From 8659c4db686c6e99250042519357151f1805f467 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 19 Jul 2017 17:17:24 +0200 Subject: [PATCH 19/41] Cleanup ibc handler per bucky --- modules/ibc/handler.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index bd3b2b04c..be97ab83f 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -21,32 +21,35 @@ const ( ) var ( + // Semi-random bytes that shouldn't conflict with keys (20 bytes) + // or any strings (non-ascii). + // TODO: consider how to make this more collision-proof.... allowIBC = []byte{0x42, 0xbe, 0xef, 0x1} ) -// AllowIBC is the special code that an app must set to -// enable sending IBC packets for this app-type +// AllowIBC returns a specially crafted Actor that +// enables sending IBC packets for this app type func AllowIBC(app string) basecoin.Actor { return basecoin.Actor{App: app, Address: allowIBC} } -// Handler allows us to update the chain state or create a packet +// Handler updates the chain state or creates an ibc packet type Handler struct{} var _ basecoin.Handler = Handler{} -// NewHandler makes a Handler that allows all chains to connect via IBC. +// NewHandler returns a Handler that allows all chains to connect via IBC. // Set a Registrar via SetOption to restrict it. func NewHandler() Handler { return Handler{} } -// Name - return name space +// Name returns name space func (Handler) Name() string { return NameIBC } -// SetOption - sets the registrar for IBC +// SetOption sets the registrar for IBC func (h Handler) SetOption(l log.Logger, store state.KVStore, module, key, value string) (log string, err error) { if module != NameIBC { return "", errors.ErrUnknownModule(module) @@ -75,6 +78,12 @@ func (h Handler) CheckTx(ctx basecoin.Context, store state.KVStore, 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) @@ -84,7 +93,7 @@ func (h Handler) CheckTx(ctx basecoin.Context, store state.KVStore, tx basecoin. return res, errors.ErrUnknownTxType(tx.Unwrap()) } -// DeliverTx verifies all signatures on the tx and updated the chain state +// 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) { err = tx.ValidateBasic() From 9b099a2f369e99ec178bbdd3af808e420854acd5 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 19 Jul 2017 17:28:43 +0200 Subject: [PATCH 20/41] Fix up ibc to work with new cli reorg --- errors/common.go | 14 +++++++++++--- modules/coin/errors.go | 8 -------- modules/coin/handler.go | 2 +- modules/ibc/tx.go | 6 +----- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/errors/common.go b/errors/common.go index f4fd66deb..64d45c082 100644 --- a/errors/common.go +++ b/errors/common.go @@ -16,6 +16,7 @@ var ( errUnknownTxType = fmt.Errorf("Tx type unknown") errInvalidFormat = fmt.Errorf("Invalid format") errUnknownModule = fmt.Errorf("Unknown module") + errUnknownKey = fmt.Errorf("Unknown key") internalErr = abci.CodeType_InternalError encodingErr = abci.CodeType_EncodingError @@ -39,7 +40,7 @@ func unwrap(i interface{}) interface{} { func ErrUnknownTxType(tx interface{}) TMError { msg := fmt.Sprintf("%T", unwrap(tx)) - return WithMessage(msg, errUnknownTxType, abci.CodeType_UnknownRequest) + return WithMessage(msg, errUnknownTxType, unknownRequest) } func IsUnknownTxTypeErr(err error) bool { return IsSameError(errUnknownTxType, err) @@ -47,19 +48,26 @@ func IsUnknownTxTypeErr(err error) bool { func ErrInvalidFormat(expected string, tx interface{}) TMError { msg := fmt.Sprintf("%T not %s", unwrap(tx), expected) - return WithMessage(msg, errInvalidFormat, abci.CodeType_UnknownRequest) + return WithMessage(msg, errInvalidFormat, unknownRequest) } func IsInvalidFormatErr(err error) bool { return IsSameError(errInvalidFormat, err) } func ErrUnknownModule(mod string) TMError { - return WithMessage(mod, errUnknownModule, abci.CodeType_UnknownRequest) + return WithMessage(mod, errUnknownModule, unknownRequest) } func IsUnknownModuleErr(err error) bool { return IsSameError(errUnknownModule, err) } +func ErrUnknownKey(mod string) TMError { + return WithMessage(mod, errUnknownKey, unknownRequest) +} +func IsUnknownKeyErr(err error) bool { + return IsSameError(errUnknownKey, err) +} + func ErrInternal(msg string) TMError { return New(msg, internalErr) } diff --git a/modules/coin/errors.go b/modules/coin/errors.go index 49513f433..430b59bff 100644 --- a/modules/coin/errors.go +++ b/modules/coin/errors.go @@ -16,7 +16,6 @@ var ( errNoOutputs = fmt.Errorf("No output coins") errInvalidAddress = fmt.Errorf("Invalid address") errInvalidCoins = fmt.Errorf("Invalid coins") - errUnknownKey = fmt.Errorf("Unknown key") invalidInput = abci.CodeType_BaseInvalidInput invalidOutput = abci.CodeType_BaseInvalidOutput @@ -80,10 +79,3 @@ func ErrNoOutputs() errors.TMError { func IsNoOutputsErr(err error) bool { return errors.IsSameError(errNoOutputs, err) } - -func ErrUnknownKey(mod string) errors.TMError { - return errors.WithMessage(mod, errUnknownKey, unknownRequest) -} -func IsUnknownKeyErr(err error) bool { - return errors.IsSameError(errUnknownKey, err) -} diff --git a/modules/coin/handler.go b/modules/coin/handler.go index 08fc2e539..ff1f04037 100644 --- a/modules/coin/handler.go +++ b/modules/coin/handler.go @@ -99,7 +99,7 @@ func (h Handler) SetOption(l log.Logger, store state.SimpleDB, module, key, valu return "Success", nil } - return "", ErrUnknownKey(key) + return "", errors.ErrUnknownKey(key) } func checkTx(ctx basecoin.Context, tx basecoin.Tx) (send SendTx, err error) { diff --git a/modules/ibc/tx.go b/modules/ibc/tx.go index 69b8bcf41..48a09531a 100644 --- a/modules/ibc/tx.go +++ b/modules/ibc/tx.go @@ -5,7 +5,6 @@ import ( merkle "github.com/tendermint/merkleeyes/iavl" "github.com/tendermint/basecoin" - "github.com/tendermint/basecoin/errors" ) // nolint @@ -94,11 +93,8 @@ type CreatePacketTx struct { // ValidateBasic makes sure this is consistent - used to satisfy TxInner func (p CreatePacketTx) ValidateBasic() error { if p.DestChain == "" { - return errors.ErrNoChain() + return ErrWrongDestChain(p.DestChain) } - // if len(p.Permissions) == 0 { - // return ErrNoPermissions() - // } return nil } From 06492fa2123df6f14e0ebc9fa38d34fff495568c Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 19 Jul 2017 20:11:51 +0200 Subject: [PATCH 21/41] testing ibc post packet --- modules/ibc/ibc_test.go | 153 +++++++++++++++++++++++++++++++++++--- modules/ibc/middleware.go | 11 ++- 2 files changed, 152 insertions(+), 12 deletions(-) diff --git a/modules/ibc/ibc_test.go b/modules/ibc/ibc_test.go index 8756a9131..f23ad02ac 100644 --- a/modules/ibc/ibc_test.go +++ b/modules/ibc/ibc_test.go @@ -2,6 +2,7 @@ package ibc import ( "encoding/json" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -9,10 +10,13 @@ import ( wire "github.com/tendermint/go-wire" "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/merkleeyes/iavl" "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/coin" "github.com/tendermint/basecoin/stack" "github.com/tendermint/basecoin/state" ) @@ -335,19 +339,146 @@ func TestIBCCreatePacket(t *testing.T) { } } +func makePostPacket(tree *iavl.IAVLTree, packet Packet, fromID string, fromHeight int) PostPacketTx { + key := []byte(fmt.Sprintf("some-long-prefix-%06d", packet.Sequence)) + tree.Set(key, packet.Bytes()) + _, proof := tree.ConstructProof(key) + if proof == nil { + panic("wtf?") + } + + return PostPacketTx{ + FromChainID: fromID, + FromChainHeight: uint64(fromHeight), + Proof: proof, + Key: key, + Packet: packet, + } +} +func updateChain(app basecoin.Handler, store state.KVStore, keys certifiers.ValKeys, + chain string, h int, appHash []byte) error { + seed := genEmptySeed(keys, chain, h, appHash, len(keys)) + tx := UpdateChainTx{seed}.Wrap() + ctx := stack.MockContext("foo", 123) + _, err := app.DeliverTx(ctx, store, tx) + return err +} + func TestIBCPostPacket(t *testing.T) { - // make proofs + assert := assert.New(t) + require := require.New(t) - // bad chain -> error - // no matching header -> error - // bad proof -> error - // out of order -> error - // invalid permissions -> error + otherID := "chain-1" + ourID := "hub" - // all good -> execute tx - -} - -func TestIBCSendTx(t *testing.T) { + // this is the root seed, that others are evaluated against + keys := certifiers.GenValKeys(7) + appHash := []byte("this is just random garbage") + start := 100 // initial height + root := genEmptySeed(keys, otherID, start, appHash, len(keys)) + // create the app and register the root of trust (for chain-1) + ctx := stack.MockContext(ourID, 50) + store := state.NewMemKVStore() + app := stack.New(). + IBC(NewMiddleware()). + Dispatch( + stack.WrapHandler(NewHandler()), + stack.WrapHandler(coin.NewHandler()), + ) + tx := RegisterChainTx{root}.Wrap() + _, err := app.DeliverTx(ctx, store, tx) + require.Nil(err, "%+v", err) + + recipient := basecoin.Actor{ChainID: ourID, App: auth.NameSigs, Address: []byte("bar")} + sender := basecoin.Actor{ChainID: otherID, App: auth.NameSigs, Address: []byte("foo")} + coinTx := coin.NewSendOneTx( + sender, + recipient, + coin.Coins{{"eth", 100}, {"ltc", 300}}, + ) + + // make proofs for some packets.... + tree := iavl.NewIAVLTree(0, nil) + pbad := Packet{ + DestChain: "something-else", + Sequence: 0, + Tx: coinTx, + } + packetBad := makePostPacket(tree, pbad, "something-else", 123) + + p0 := Packet{ + DestChain: ourID, + Sequence: 0, + Permissions: basecoin.Actors{sender}, + Tx: coinTx, + } + p1 := Packet{ + DestChain: ourID, + Sequence: 1, + Permissions: basecoin.Actors{sender}, + Tx: coinTx, + } + + packet0 := makePostPacket(tree, p0, otherID, start+5) + err = updateChain(app, store, keys, otherID, start+5, tree.Hash()) + require.Nil(err, "%+v", err) + + packet0badHeight := packet0 + packet0badHeight.FromChainHeight -= 2 + + packet1 := makePostPacket(tree, p1, otherID, start+25) + err = updateChain(app, store, keys, otherID, start+25, tree.Hash()) + require.Nil(err, "%+v", err) + + packet1badProof := packet1 + packet1badProof.Key = []byte("random-data") + + ibcPerm := basecoin.Actors{AllowIBC(coin.NameCoin)} + cases := []struct { + packet PostPacketTx + permissions basecoin.Actors + checker checkErr + }{ + // bad chain -> error + {packetBad, ibcPerm, IsNotRegisteredErr}, + + // invalid permissions -> error + {packet0, nil, IsNeedsIBCPermissionErr}, + + // no matching header -> error + {packet0badHeight, ibcPerm, IsHeaderNotFoundErr}, + + // out of order -> error + {packet1, ibcPerm, IsPacketOutOfOrderErr}, + + // all good -> execute tx } + {packet0, ibcPerm, noErr}, + + // bad proof -> error + {packet1badProof, ibcPerm, IsInvalidProofErr}, + + // all good -> execute tx } + {packet1, ibcPerm, noErr}, + + // repeat -> error + {packet0, ibcPerm, IsPacketAlreadyExistsErr}, + } + + for i, tc := range cases { + // cache wrap it like an app, so no state change on error... + myStore := state.NewKVCache(store) + + myCtx := ctx + if len(tc.permissions) > 0 { + myCtx = myCtx.WithPermissions(tc.permissions...) + } + _, err := app.DeliverTx(myCtx, myStore, tc.packet.Wrap()) + assert.True(tc.checker(err), "%d: %+v", i, err) + + // only commit changes on success + if err == nil { + myStore.Sync() + } + } } diff --git a/modules/ibc/middleware.go b/modules/ibc/middleware.go index 16f45060f..0c02088f2 100644 --- a/modules/ibc/middleware.go +++ b/modules/ibc/middleware.go @@ -80,6 +80,15 @@ func (m Middleware) verifyPost(ctx basecoin.Context, store state.KVStore, return ictx, itx, ErrCannotSetPermission() } + // make sure it has AllowIBC + mod, err := packet.Tx.GetMod() + if err != nil { + return ictx, itx, err + } + if !ctx.HasPermission(AllowIBC(mod)) { + return ictx, itx, ErrNeedsIBCPermission() + } + // make sure this sequence number is the next in the list q := InputQueue(store, from) tail := q.Tail() @@ -110,7 +119,7 @@ func (m Middleware) verifyPost(ctx basecoin.Context, store state.KVStore, q.Push(pBytes) // return the wrapped tx along with the extra permissions - ictx = ictx.WithPermissions(packet.Permissions...) + ictx = ctx.WithPermissions(packet.Permissions...) itx = packet.Tx return } From 555e0d8ec8ffdb9fd645748d74b99274d983939c Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 19 Jul 2017 20:25:36 +0200 Subject: [PATCH 22/41] test receiving and verifying incoming ibc packets --- modules/coin/store.go | 16 ++++++++++++++++ modules/ibc/ibc_test.go | 23 +++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/modules/coin/store.go b/modules/coin/store.go index 4d1d4c109..f84ad2bef 100644 --- a/modules/coin/store.go +++ b/modules/coin/store.go @@ -38,10 +38,26 @@ func ChangeCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (Coins, return acct.Coins, err } +// ChainAddr collapses all addresses from another chain into one, so we can +// keep an over-all balance +// +// TODO: is there a better way to do this? +func ChainAddr(addr basecoin.Actor) basecoin.Actor { + if addr.ChainID == "" { + return addr + } + addr.App = "" + addr.Address = nil + return addr +} + // updateCoins will load the account, make all checks, and return the updated account. // // it doesn't save anything, that is up to you to decide (Check/Change Coins) func updateCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (acct Account, err error) { + // if the actor is another chain, we use one address for the chain.... + addr = ChainAddr(addr) + acct, err = loadAccount(store, addr.Bytes()) // we can increase an empty account... if IsNoAccountErr(err) && coins.IsPositive() { diff --git a/modules/ibc/ibc_test.go b/modules/ibc/ibc_test.go index f23ad02ac..c19bbf1ad 100644 --- a/modules/ibc/ibc_test.go +++ b/modules/ibc/ibc_test.go @@ -355,6 +355,7 @@ func makePostPacket(tree *iavl.IAVLTree, packet Packet, fromID string, fromHeigh Packet: packet, } } + func updateChain(app basecoin.Handler, store state.KVStore, keys certifiers.ValKeys, chain string, h int, appHash []byte) error { seed := genEmptySeed(keys, chain, h, appHash, len(keys)) @@ -398,6 +399,14 @@ func TestIBCPostPacket(t *testing.T) { coin.Coins{{"eth", 100}, {"ltc", 300}}, ) + // set some cash on this chain (TODO: via set options...) + otherAddr := coin.ChainAddr(sender) + acct := coin.Account{ + Coins: coin.Coins{{"btc", 300}, {"eth", 2000}, {"ltc", 5000}}, + } + cstore := stack.PrefixedStore(coin.NameCoin, store) + cstore.Set(otherAddr.Bytes(), wire.BinaryBytes(acct)) + // make proofs for some packets.... tree := iavl.NewIAVLTree(0, nil) pbad := Packet{ @@ -419,6 +428,13 @@ func TestIBCPostPacket(t *testing.T) { Permissions: basecoin.Actors{sender}, Tx: coinTx, } + // this sends money we don't have registered + p2 := Packet{ + DestChain: ourID, + Sequence: 2, + Permissions: basecoin.Actors{sender}, + Tx: coin.NewSendOneTx(sender, recipient, coin.Coins{{"missing", 20}}), + } packet0 := makePostPacket(tree, p0, otherID, start+5) err = updateChain(app, store, keys, otherID, start+5, tree.Hash()) @@ -434,6 +450,10 @@ func TestIBCPostPacket(t *testing.T) { packet1badProof := packet1 packet1badProof.Key = []byte("random-data") + packet2 := makePostPacket(tree, p2, otherID, start+50) + err = updateChain(app, store, keys, otherID, start+50, tree.Hash()) + require.Nil(err, "%+v", err) + ibcPerm := basecoin.Actors{AllowIBC(coin.NameCoin)} cases := []struct { packet PostPacketTx @@ -463,6 +483,9 @@ func TestIBCPostPacket(t *testing.T) { // repeat -> error {packet0, ibcPerm, IsPacketAlreadyExistsErr}, + + // packet 2 attempts to spend money this chain doesn't have + {packet2, ibcPerm, coin.IsInsufficientFundsErr}, } for i, tc := range cases { From 0c5f0bdf771e84e6be554f987f1e4add55832541 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 19 Jul 2017 21:03:13 +0200 Subject: [PATCH 23/41] Test outgoing ibc packets add to the chain credit properly --- modules/coin/store.go | 11 ++++++++--- modules/ibc/ibc_test.go | 28 ++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/modules/coin/store.go b/modules/coin/store.go index f84ad2bef..9aa951340 100644 --- a/modules/coin/store.go +++ b/modules/coin/store.go @@ -12,6 +12,8 @@ import ( // GetAccount - Get account from store and address func GetAccount(store state.SimpleDB, addr basecoin.Actor) (Account, error) { + // if the actor is another chain, we use one address for the chain.... + addr = ChainAddr(addr) acct, err := loadAccount(store, addr.Bytes()) // for empty accounts, don't return an error, but rather an empty account @@ -23,12 +25,18 @@ func GetAccount(store state.SimpleDB, addr basecoin.Actor) (Account, error) { // CheckCoins makes sure there are funds, but doesn't change anything func CheckCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (Coins, error) { + // if the actor is another chain, we use one address for the chain.... + addr = ChainAddr(addr) + acct, err := updateCoins(store, addr, coins) return acct.Coins, err } // ChangeCoins changes the money, returns error if it would be negative func ChangeCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (Coins, error) { + // if the actor is another chain, we use one address for the chain.... + addr = ChainAddr(addr) + acct, err := updateCoins(store, addr, coins) if err != nil { return acct.Coins, err @@ -55,9 +63,6 @@ func ChainAddr(addr basecoin.Actor) basecoin.Actor { // // it doesn't save anything, that is up to you to decide (Check/Change Coins) func updateCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (acct Account, err error) { - // if the actor is another chain, we use one address for the chain.... - addr = ChainAddr(addr) - acct, err = loadAccount(store, addr.Bytes()) // we can increase an empty account... if IsNoAccountErr(err) && coins.IsPositive() { diff --git a/modules/ibc/ibc_test.go b/modules/ibc/ibc_test.go index c19bbf1ad..61e58e92b 100644 --- a/modules/ibc/ibc_test.go +++ b/modules/ibc/ibc_test.go @@ -391,6 +391,26 @@ func TestIBCPostPacket(t *testing.T) { _, err := app.DeliverTx(ctx, store, tx) require.Nil(err, "%+v", err) + // set up a rich guy on this chain + wealth := coin.Coins{{"btc", 300}, {"eth", 2000}, {"ltc", 5000}} + rich := coin.NewAccountWithKey(wealth) + _, err = app.SetOption(log.NewNopLogger(), store, + "coin", "account", rich.MakeOption()) + require.Nil(err, "%+v", err) + + // sends money to another guy on a different chain, now other chain has credit + buddy := basecoin.Actor{ChainID: otherID, App: auth.NameSigs, Address: []byte("dude")} + outTx := coin.NewSendOneTx(rich.Actor(), buddy, wealth) + _, err = app.DeliverTx(ctx.WithPermissions(rich.Actor()), store, outTx) + require.Nil(err, "%+v", err) + + // make sure the money moved to the other chain... + cstore := stack.PrefixedStore(coin.NameCoin, store) + acct, err := coin.GetAccount(cstore, coin.ChainAddr(buddy)) + require.Nil(err, "%+v", err) + require.Equal(wealth, acct.Coins) + + // 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 := coin.NewSendOneTx( @@ -399,14 +419,6 @@ func TestIBCPostPacket(t *testing.T) { coin.Coins{{"eth", 100}, {"ltc", 300}}, ) - // set some cash on this chain (TODO: via set options...) - otherAddr := coin.ChainAddr(sender) - acct := coin.Account{ - Coins: coin.Coins{{"btc", 300}, {"eth", 2000}, {"ltc", 5000}}, - } - cstore := stack.PrefixedStore(coin.NameCoin, store) - cstore.Set(otherAddr.Bytes(), wire.BinaryBytes(acct)) - // make proofs for some packets.... tree := iavl.NewIAVLTree(0, nil) pbad := Packet{ From a925c8545c913c4adfdf16b5e6808ed6b93edd39 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 21 Jul 2017 13:44:33 +0200 Subject: [PATCH 24/41] Refactored ibc test packet gen --- modules/ibc/ibc_test.go | 126 +++++++---------------------------- modules/ibc/store.go | 10 +++ modules/ibc/test_helpers.go | 129 ++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 101 deletions(-) create mode 100644 modules/ibc/test_helpers.go diff --git a/modules/ibc/ibc_test.go b/modules/ibc/ibc_test.go index 61e58e92b..3ac88ff22 100644 --- a/modules/ibc/ibc_test.go +++ b/modules/ibc/ibc_test.go @@ -2,7 +2,6 @@ package ibc import ( "encoding/json" - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -10,7 +9,6 @@ import ( wire "github.com/tendermint/go-wire" "github.com/tendermint/light-client/certifiers" - "github.com/tendermint/merkleeyes/iavl" "github.com/tendermint/tmlibs/log" "github.com/tendermint/basecoin" @@ -27,14 +25,6 @@ func noErr(err error) bool { return err == nil } -func genEmptySeed(keys certifiers.ValKeys, chain string, h int, - appHash []byte, count int) certifiers.Seed { - - vals := keys.ToValidators(10, 0) - cp := keys.GenCheckpoint(chain, h, nil, vals, appHash, 0, count) - return certifiers.Seed{cp, vals} -} - // this tests registration without registrar permissions func TestIBCRegister(t *testing.T) { assert := assert.New(t) @@ -339,73 +329,43 @@ func TestIBCCreatePacket(t *testing.T) { } } -func makePostPacket(tree *iavl.IAVLTree, packet Packet, fromID string, fromHeight int) PostPacketTx { - key := []byte(fmt.Sprintf("some-long-prefix-%06d", packet.Sequence)) - tree.Set(key, packet.Bytes()) - _, proof := tree.ConstructProof(key) - if proof == nil { - panic("wtf?") - } - - return PostPacketTx{ - FromChainID: fromID, - FromChainHeight: uint64(fromHeight), - Proof: proof, - Key: key, - Packet: packet, - } -} - -func updateChain(app basecoin.Handler, store state.KVStore, keys certifiers.ValKeys, - chain string, h int, appHash []byte) error { - seed := genEmptySeed(keys, chain, h, appHash, len(keys)) - tx := UpdateChainTx{seed}.Wrap() - ctx := stack.MockContext("foo", 123) - _, err := app.DeliverTx(ctx, store, tx) - return err -} - func TestIBCPostPacket(t *testing.T) { assert := assert.New(t) require := require.New(t) otherID := "chain-1" ourID := "hub" + start := 200 - // this is the root seed, that others are evaluated against - keys := certifiers.GenValKeys(7) - appHash := []byte("this is just random garbage") - start := 100 // initial height - root := genEmptySeed(keys, otherID, start, appHash, len(keys)) - - // create the app and register the root of trust (for chain-1) - ctx := stack.MockContext(ourID, 50) - store := state.NewMemKVStore() + // create the app and our chain app := stack.New(). IBC(NewMiddleware()). Dispatch( stack.WrapHandler(NewHandler()), stack.WrapHandler(coin.NewHandler()), ) - tx := RegisterChainTx{root}.Wrap() - _, err := app.DeliverTx(ctx, store, tx) + ourChain := NewAppChain(app, ourID) + + // set up the other chain and register it with us + otherChain := NewMockChain(otherID, 7) + registerTx := otherChain.GetRegistrationTx(start).Wrap() + _, err := ourChain.DeliverTx(registerTx) require.Nil(err, "%+v", err) // set up a rich guy on this chain wealth := coin.Coins{{"btc", 300}, {"eth", 2000}, {"ltc", 5000}} rich := coin.NewAccountWithKey(wealth) - _, err = app.SetOption(log.NewNopLogger(), store, - "coin", "account", rich.MakeOption()) + _, err = ourChain.SetOption("coin", "account", rich.MakeOption()) require.Nil(err, "%+v", err) // sends money to another guy on a different chain, now other chain has credit buddy := basecoin.Actor{ChainID: otherID, App: auth.NameSigs, Address: []byte("dude")} outTx := coin.NewSendOneTx(rich.Actor(), buddy, wealth) - _, err = app.DeliverTx(ctx.WithPermissions(rich.Actor()), store, outTx) + _, err = ourChain.DeliverTx(outTx, rich.Actor()) require.Nil(err, "%+v", err) // make sure the money moved to the other chain... - cstore := stack.PrefixedStore(coin.NameCoin, store) + cstore := ourChain.GetStore(coin.NameCoin) acct, err := coin.GetAccount(cstore, coin.ChainAddr(buddy)) require.Nil(err, "%+v", err) require.Equal(wealth, acct.Coins) @@ -418,53 +378,29 @@ func TestIBCPostPacket(t *testing.T) { recipient, coin.Coins{{"eth", 100}, {"ltc", 300}}, ) + wrongCoin := coin.NewSendOneTx(sender, recipient, coin.Coins{{"missing", 20}}) - // make proofs for some packets.... - tree := iavl.NewIAVLTree(0, nil) - pbad := Packet{ - DestChain: "something-else", - Sequence: 0, - Tx: coinTx, - } - packetBad := makePostPacket(tree, pbad, "something-else", 123) + randomChain := NewMockChain("something-else", 4) + pbad := NewPacket(coinTx, "something-else", 0) + packetBad, _ := randomChain.MakePostPacket(pbad, 123) - p0 := Packet{ - DestChain: ourID, - Sequence: 0, - Permissions: basecoin.Actors{sender}, - Tx: coinTx, - } - p1 := Packet{ - DestChain: ourID, - Sequence: 1, - Permissions: basecoin.Actors{sender}, - Tx: coinTx, - } - // this sends money we don't have registered - p2 := Packet{ - DestChain: ourID, - Sequence: 2, - Permissions: basecoin.Actors{sender}, - Tx: coin.NewSendOneTx(sender, recipient, coin.Coins{{"missing", 20}}), - } - - packet0 := makePostPacket(tree, p0, otherID, start+5) - err = updateChain(app, store, keys, otherID, start+5, tree.Hash()) - require.Nil(err, "%+v", err) + p0 := NewPacket(coinTx, ourID, 0, sender) + packet0, update0 := otherChain.MakePostPacket(p0, start+5) + require.Nil(ourChain.Update(update0)) packet0badHeight := packet0 packet0badHeight.FromChainHeight -= 2 - packet1 := makePostPacket(tree, p1, otherID, start+25) - err = updateChain(app, store, keys, otherID, start+25, tree.Hash()) - require.Nil(err, "%+v", err) + p1 := NewPacket(coinTx, ourID, 1, sender) + packet1, update1 := otherChain.MakePostPacket(p1, start+25) + require.Nil(ourChain.Update(update1)) packet1badProof := packet1 packet1badProof.Key = []byte("random-data") - packet2 := makePostPacket(tree, p2, otherID, start+50) - err = updateChain(app, store, keys, otherID, start+50, tree.Hash()) - require.Nil(err, "%+v", err) + p2 := NewPacket(wrongCoin, ourID, 2, sender) + packet2, update2 := otherChain.MakePostPacket(p2, start+50) + require.Nil(ourChain.Update(update2)) ibcPerm := basecoin.Actors{AllowIBC(coin.NameCoin)} cases := []struct { @@ -501,19 +437,7 @@ func TestIBCPostPacket(t *testing.T) { } for i, tc := range cases { - // cache wrap it like an app, so no state change on error... - myStore := state.NewKVCache(store) - - myCtx := ctx - if len(tc.permissions) > 0 { - myCtx = myCtx.WithPermissions(tc.permissions...) - } - _, err := app.DeliverTx(myCtx, myStore, tc.packet.Wrap()) + _, err := ourChain.DeliverTx(tc.packet.Wrap(), tc.permissions...) assert.True(tc.checker(err), "%d: %+v", i, err) - - // only commit changes on success - if err == nil { - myStore.Sync() - } } } diff --git a/modules/ibc/store.go b/modules/ibc/store.go index fc37b0414..fc85416aa 100644 --- a/modules/ibc/store.go +++ b/modules/ibc/store.go @@ -86,6 +86,16 @@ type Packet struct { Tx basecoin.Tx `json:"tx"` } +// NewPacket creates a new outgoing packet +func NewPacket(tx basecoin.Tx, dest string, seq uint64, perm ...basecoin.Actor) Packet { + return Packet{ + DestChain: dest, + Sequence: seq, + Permissions: perm, + Tx: tx, + } +} + // Bytes returns a serialization of the Packet func (p Packet) Bytes() []byte { return wire.BinaryBytes(p) diff --git a/modules/ibc/test_helpers.go b/modules/ibc/test_helpers.go new file mode 100644 index 000000000..844a7c9ac --- /dev/null +++ b/modules/ibc/test_helpers.go @@ -0,0 +1,129 @@ +package ibc + +import ( + "fmt" + + "github.com/tendermint/light-client/certifiers" + "github.com/tendermint/merkleeyes/iavl" + "github.com/tendermint/tmlibs/log" + + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/stack" + "github.com/tendermint/basecoin/state" +) + +// MockChain is used to simulate a chain for ibc tests. +// It is able to produce ibc packets and all verification for +// them, but cannot respond to any responses. +type MockChain struct { + keys certifiers.ValKeys + chainID string + tree *iavl.IAVLTree +} + +// NewMockChain initializes a teststore and test validators +func NewMockChain(chainID string, numKeys int) MockChain { + return MockChain{ + keys: certifiers.GenValKeys(numKeys), + chainID: chainID, + tree: iavl.NewIAVLTree(0, nil), + } +} + +// GetRegistrationTx returns a valid tx to register this chain +func (m MockChain) GetRegistrationTx(h int) RegisterChainTx { + seed := genEmptySeed(m.keys, m.chainID, h, m.tree.Hash(), len(m.keys)) + return RegisterChainTx{seed} +} + +// MakePostPacket commits the packet locally and returns the proof, +// in the form of two packets to update the header and prove this packet. +func (m MockChain) MakePostPacket(packet Packet, h int) ( + PostPacketTx, UpdateChainTx) { + + post := makePostPacket(m.tree, packet, m.chainID, h) + seed := genEmptySeed(m.keys, m.chainID, h, m.tree.Hash(), len(m.keys)) + update := UpdateChainTx{seed} + + return post, update +} + +func genEmptySeed(keys certifiers.ValKeys, chain string, h int, + appHash []byte, count int) certifiers.Seed { + + vals := keys.ToValidators(10, 0) + cp := keys.GenCheckpoint(chain, h, nil, vals, appHash, 0, count) + return certifiers.Seed{cp, vals} +} + +func makePostPacket(tree *iavl.IAVLTree, packet Packet, fromID string, fromHeight int) PostPacketTx { + key := []byte(fmt.Sprintf("some-long-prefix-%06d", packet.Sequence)) + tree.Set(key, packet.Bytes()) + _, proof := tree.ConstructProof(key) + if proof == nil { + panic("wtf?") + } + + return PostPacketTx{ + FromChainID: fromID, + FromChainHeight: uint64(fromHeight), + Proof: proof, + Key: key, + Packet: packet, + } +} + +// AppChain is ready to handle tx +type AppChain struct { + chainID string + app basecoin.Handler + store state.KVStore + height int +} + +// NewAppChain returns a chain that is ready to respond to tx +func NewAppChain(app basecoin.Handler, chainID string) *AppChain { + return &AppChain{ + chainID: chainID, + app: app, + store: state.NewMemKVStore(), + height: 123, + } +} + +// IncrementHeight allows us to jump heights, more than the auto-step +// of 1. It returns the new height we are at. +func (a *AppChain) IncrementHeight(delta int) int { + a.height += delta + return a.height +} + +// DeliverTx runs the tx and commits the new tree, incrementing height +// 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) + res, err := a.app.DeliverTx(ctx, store, tx) + if err == nil { + // commit data on success + store.Sync() + } + return res, err +} + +// Update is a shortcut to DeliverTx with this. Also one return value +// to test inline +func (a *AppChain) Update(tx UpdateChainTx) error { + _, err := a.DeliverTx(tx.Wrap()) + return err +} + +// SetOption sets the option on our app +func (a *AppChain) SetOption(mod, key, value string) (string, error) { + return a.app.SetOption(log.NewNopLogger(), a.store, mod, key, value) +} + +// GetStore is used to get the app-specific sub-store +func (a *AppChain) GetStore(app string) state.KVStore { + return stack.PrefixedStore(app, a.store) +} From 374f078b79d0f15b9452c45581f959952b9f5387 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 21 Jul 2017 16:15:58 +0200 Subject: [PATCH 25/41] IBC test just rawtx, coin ibc in that module --- errors/helpers.go | 9 ++++ modules/coin/ibc_test.go | 101 +++++++++++++++++++++++++++++++++++++++ modules/ibc/ibc_test.go | 97 ++++++++++++++----------------------- 3 files changed, 145 insertions(+), 62 deletions(-) create mode 100644 errors/helpers.go create mode 100644 modules/coin/ibc_test.go diff --git a/errors/helpers.go b/errors/helpers.go new file mode 100644 index 000000000..c5286575e --- /dev/null +++ b/errors/helpers.go @@ -0,0 +1,9 @@ +package errors + +// CheckErr is the type of all the check functions here +type CheckErr func(error) bool + +// NoErr is useful for test cases when you want to fulfil the CheckErr type +func NoErr(err error) bool { + return err == nil +} diff --git a/modules/coin/ibc_test.go b/modules/coin/ibc_test.go new file mode 100644 index 000000000..13908cfc2 --- /dev/null +++ b/modules/coin/ibc_test.go @@ -0,0 +1,101 @@ +package coin + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "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" +) + +func TestIBCPostPacket(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + otherID := "chain-2" + ourID := "dex" + start := 200 + + // create the app and our chain + app := stack.New(). + IBC(ibc.NewMiddleware()). + Dispatch( + stack.WrapHandler(NewHandler()), + stack.WrapHandler(ibc.NewHandler()), + ) + ourChain := ibc.NewAppChain(app, ourID) + + // set up the other chain and register it with us + otherChain := ibc.NewMockChain(otherID, 7) + registerTx := otherChain.GetRegistrationTx(start).Wrap() + _, err := ourChain.DeliverTx(registerTx) + require.Nil(err, "%+v", err) + + // set up a rich guy on this chain + wealth := Coins{{"btc", 300}, {"eth", 2000}, {"ltc", 5000}} + rich := NewAccountWithKey(wealth) + _, err = ourChain.SetOption("coin", "account", rich.MakeOption()) + require.Nil(err, "%+v", err) + + // sends money to another guy on a different chain, now other chain has credit + buddy := basecoin.Actor{ChainID: otherID, App: auth.NameSigs, Address: []byte("dude")} + outTx := NewSendOneTx(rich.Actor(), buddy, wealth) + _, err = ourChain.DeliverTx(outTx, rich.Actor()) + require.Nil(err, "%+v", err) + + // make sure the money moved to the other chain... + cstore := ourChain.GetStore(NameCoin) + acct, err := GetAccount(cstore, ChainAddr(buddy)) + require.Nil(err, "%+v", err) + require.Equal(wealth, acct.Coins) + + // 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}}, + ) + wrongCoin := NewSendOneTx(sender, recipient, Coins{{"missing", 20}}) + + p0 := ibc.NewPacket(coinTx, ourID, 0, sender) + packet0, update0 := otherChain.MakePostPacket(p0, start+5) + require.Nil(ourChain.Update(update0)) + + p1 := ibc.NewPacket(coinTx, ourID, 1, sender) + packet1, update1 := otherChain.MakePostPacket(p1, start+25) + require.Nil(ourChain.Update(update1)) + + p2 := ibc.NewPacket(wrongCoin, ourID, 2, sender) + packet2, update2 := otherChain.MakePostPacket(p2, start+50) + require.Nil(ourChain.Update(update2)) + + ibcPerm := basecoin.Actors{ibc.AllowIBC(NameCoin)} + cases := []struct { + packet ibc.PostPacketTx + permissions basecoin.Actors + checker errors.CheckErr + }{ + // out of order -> error + {packet1, ibcPerm, ibc.IsPacketOutOfOrderErr}, + + // all good -> execute tx + {packet0, ibcPerm, errors.NoErr}, + + // all good -> execute tx (even if earlier attempt failed) + {packet1, ibcPerm, errors.NoErr}, + + // packet 2 attempts to spend money this chain doesn't have + {packet2, ibcPerm, IsInsufficientFundsErr}, + } + + for i, tc := range cases { + _, err := ourChain.DeliverTx(tc.packet.Wrap(), tc.permissions...) + assert.True(tc.checker(err), "%d: %+v", i, err) + } +} diff --git a/modules/ibc/ibc_test.go b/modules/ibc/ibc_test.go index 3ac88ff22..0d830f534 100644 --- a/modules/ibc/ibc_test.go +++ b/modules/ibc/ibc_test.go @@ -13,18 +13,10 @@ import ( "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/errors" - "github.com/tendermint/basecoin/modules/auth" - "github.com/tendermint/basecoin/modules/coin" "github.com/tendermint/basecoin/stack" "github.com/tendermint/basecoin/state" ) -type checkErr func(error) bool - -func noErr(err error) bool { - return err == nil -} - // this tests registration without registrar permissions func TestIBCRegister(t *testing.T) { assert := assert.New(t) @@ -41,11 +33,11 @@ func TestIBCRegister(t *testing.T) { cases := []struct { seed certifiers.Seed - checker checkErr + checker errors.CheckErr }{ { genEmptySeed(keys, "chain-1", 100, appHash, len(keys)), - noErr, + errors.NoErr, }, { genEmptySeed(keys, "chain-1", 200, appHash, len(keys)), @@ -57,7 +49,7 @@ func TestIBCRegister(t *testing.T) { }, { genEmptySeed(keys2, "chain-2", 123, appHash2, 5), - noErr, + errors.NoErr, }, } @@ -89,18 +81,18 @@ func TestIBCRegisterPermissions(t *testing.T) { seed certifiers.Seed registrar basecoin.Actor signer basecoin.Actor - checker checkErr + checker errors.CheckErr }{ // no sig, no registrar { seed: genEmptySeed(keys, "chain-1", 100, appHash, len(keys)), - checker: noErr, + checker: errors.NoErr, }, // sig, no registrar { seed: genEmptySeed(keys, "chain-2", 100, appHash, len(keys)), signer: foobaz, - checker: noErr, + checker: errors.NoErr, }, // registrar, no sig { @@ -127,7 +119,7 @@ func TestIBCRegisterPermissions(t *testing.T) { seed: genEmptySeed(keys, "chain-6", 100, appHash, len(keys)), signer: foobar, registrar: foobar, - checker: noErr, + checker: errors.NoErr, }, } @@ -174,17 +166,17 @@ func TestIBCUpdate(t *testing.T) { cases := []struct { seed certifiers.Seed - checker checkErr + checker errors.CheckErr }{ // same validator, higher up { genEmptySeed(keys, "chain-1", start+50, []byte{22}, len(keys)), - noErr, + errors.NoErr, }, // same validator, between existing (not most recent) { genEmptySeed(keys, "chain-1", start+5, []byte{15, 43}, len(keys)), - noErr, + errors.NoErr, }, // same validators, before root of trust { @@ -209,12 +201,12 @@ func TestIBCUpdate(t *testing.T) { // legit update to validator set (keys -> keys2) { genEmptySeed(keys2, "chain-1", start+90, []byte{33}, len(keys2)), - noErr, + errors.NoErr, }, // now impossible jump works (keys -> keys2 -> keys3) { genEmptySeed(keys3, "chain-1", start+100, []byte{44}, len(keys3)), - noErr, + errors.NoErr, }, } @@ -254,7 +246,7 @@ func TestIBCCreatePacket(t *testing.T) { dest string ibcPerms basecoin.Actors ctxPerms basecoin.Actors - checker checkErr + checker errors.CheckErr }{ // wrong chain -> error { @@ -273,7 +265,7 @@ func TestIBCCreatePacket(t *testing.T) { { dest: chainID, ctxPerms: basecoin.Actors{ibcPerm}, - checker: noErr, + checker: errors.NoErr, }, // requesting invalid permissions -> error @@ -289,7 +281,7 @@ func TestIBCCreatePacket(t *testing.T) { dest: chainID, ibcPerms: basecoin.Actors{somePerm}, ctxPerms: basecoin.Actors{ibcPerm, somePerm}, - checker: noErr, + checker: errors.NoErr, }, } @@ -336,13 +328,14 @@ func TestIBCPostPacket(t *testing.T) { otherID := "chain-1" ourID := "hub" start := 200 + msg := "it's okay" // create the app and our chain app := stack.New(). IBC(NewMiddleware()). Dispatch( stack.WrapHandler(NewHandler()), - stack.WrapHandler(coin.NewHandler()), + stack.WrapHandler(stack.OKHandler{Log: msg}), ) ourChain := NewAppChain(app, ourID) @@ -352,61 +345,38 @@ func TestIBCPostPacket(t *testing.T) { _, err := ourChain.DeliverTx(registerTx) require.Nil(err, "%+v", err) - // set up a rich guy on this chain - wealth := coin.Coins{{"btc", 300}, {"eth", 2000}, {"ltc", 5000}} - rich := coin.NewAccountWithKey(wealth) - _, err = ourChain.SetOption("coin", "account", rich.MakeOption()) - require.Nil(err, "%+v", err) - - // sends money to another guy on a different chain, now other chain has credit - buddy := basecoin.Actor{ChainID: otherID, App: auth.NameSigs, Address: []byte("dude")} - outTx := coin.NewSendOneTx(rich.Actor(), buddy, wealth) - _, err = ourChain.DeliverTx(outTx, rich.Actor()) - require.Nil(err, "%+v", err) - - // make sure the money moved to the other chain... - cstore := ourChain.GetStore(coin.NameCoin) - acct, err := coin.GetAccount(cstore, coin.ChainAddr(buddy)) - require.Nil(err, "%+v", err) - require.Equal(wealth, acct.Coins) - - // 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 := coin.NewSendOneTx( - sender, - recipient, - coin.Coins{{"eth", 100}, {"ltc", 300}}, - ) - wrongCoin := coin.NewSendOneTx(sender, recipient, coin.Coins{{"missing", 20}}) + // make a random tx that is to be passed + rawTx := stack.NewRawTx([]byte{17, 24, 3, 8}) randomChain := NewMockChain("something-else", 4) - pbad := NewPacket(coinTx, "something-else", 0) + pbad := NewPacket(rawTx, "something-else", 0) packetBad, _ := randomChain.MakePostPacket(pbad, 123) - p0 := NewPacket(coinTx, ourID, 0, sender) + p0 := NewPacket(rawTx, ourID, 0) packet0, update0 := otherChain.MakePostPacket(p0, start+5) require.Nil(ourChain.Update(update0)) packet0badHeight := packet0 packet0badHeight.FromChainHeight -= 2 - p1 := NewPacket(coinTx, ourID, 1, sender) + theirActor := basecoin.Actor{ChainID: otherID, App: "foo", Address: []byte{1}} + p1 := NewPacket(rawTx, ourID, 1, theirActor) packet1, update1 := otherChain.MakePostPacket(p1, start+25) require.Nil(ourChain.Update(update1)) packet1badProof := packet1 packet1badProof.Key = []byte("random-data") - p2 := NewPacket(wrongCoin, ourID, 2, sender) + ourActor := basecoin.Actor{ChainID: ourID, App: "bar", Address: []byte{2}} + p2 := NewPacket(rawTx, ourID, 2, ourActor) packet2, update2 := otherChain.MakePostPacket(p2, start+50) require.Nil(ourChain.Update(update2)) - ibcPerm := basecoin.Actors{AllowIBC(coin.NameCoin)} + ibcPerm := basecoin.Actors{AllowIBC(stack.NameOK)} cases := []struct { packet PostPacketTx permissions basecoin.Actors - checker checkErr + checker errors.CheckErr }{ // bad chain -> error {packetBad, ibcPerm, IsNotRegisteredErr}, @@ -421,23 +391,26 @@ func TestIBCPostPacket(t *testing.T) { {packet1, ibcPerm, IsPacketOutOfOrderErr}, // all good -> execute tx } - {packet0, ibcPerm, noErr}, + {packet0, ibcPerm, errors.NoErr}, // bad proof -> error {packet1badProof, ibcPerm, IsInvalidProofErr}, // all good -> execute tx } - {packet1, ibcPerm, noErr}, + {packet1, ibcPerm, errors.NoErr}, // repeat -> error {packet0, ibcPerm, IsPacketAlreadyExistsErr}, - // packet 2 attempts to spend money this chain doesn't have - {packet2, ibcPerm, coin.IsInsufficientFundsErr}, + // packet2 contains invalid permissions + {packet2, ibcPerm, IsCannotSetPermissionErr}, } for i, tc := range cases { - _, err := ourChain.DeliverTx(tc.packet.Wrap(), tc.permissions...) + res, err := ourChain.DeliverTx(tc.packet.Wrap(), tc.permissions...) assert.True(tc.checker(err), "%d: %+v", i, err) + if err == nil { + assert.Equal(msg, res.Log) + } } } From 746ae28eaa404526c20c0b402497273347f20e12 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 21 Jul 2017 17:15:49 +0200 Subject: [PATCH 26/41] Add ibc query commands --- cmd/basecli/main.go | 2 + modules/coin/ibc_test.go | 4 + modules/ibc/commands/query.go | 185 ++++++++++++++++++++++++++++++++ modules/ibc/keys.go | 60 +++++++++++ modules/ibc/store.go | 19 +--- modules/roles/commands/query.go | 15 ++- state/queue.go | 15 +++ state/set.go | 13 ++- 8 files changed, 283 insertions(+), 30 deletions(-) create mode 100644 modules/ibc/commands/query.go create mode 100644 modules/ibc/keys.go diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go index 02335e390..3a48febe2 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -19,6 +19,7 @@ import ( basecmd "github.com/tendermint/basecoin/modules/base/commands" coincmd "github.com/tendermint/basecoin/modules/coin/commands" feecmd "github.com/tendermint/basecoin/modules/fee/commands" + ibccmd "github.com/tendermint/basecoin/modules/ibc/commands" noncecmd "github.com/tendermint/basecoin/modules/nonce/commands" rolecmd "github.com/tendermint/basecoin/modules/roles/commands" ) @@ -46,6 +47,7 @@ func main() { coincmd.AccountQueryCmd, noncecmd.NonceQueryCmd, rolecmd.RoleQueryCmd, + ibccmd.IBCQueryCmd, ) proofs.TxPresenters.Register("base", txcmd.BaseTxPresenter{}) diff --git a/modules/coin/ibc_test.go b/modules/coin/ibc_test.go index 13908cfc2..24856ef6f 100644 --- a/modules/coin/ibc_test.go +++ b/modules/coin/ibc_test.go @@ -12,6 +12,10 @@ import ( "github.com/tendermint/basecoin/stack" ) +// TODO: other test making sure tx is output on send, balance is updated + +// This makes sure we respond properly to posttx +// TODO: set credit limit func TestIBCPostPacket(t *testing.T) { assert := assert.New(t) require := require.New(t) diff --git a/modules/ibc/commands/query.go b/modules/ibc/commands/query.go new file mode 100644 index 000000000..38514d4c2 --- /dev/null +++ b/modules/ibc/commands/query.go @@ -0,0 +1,185 @@ +package commands + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/tendermint/basecoin/client/commands" + proofcmd "github.com/tendermint/basecoin/client/commands/proofs" + "github.com/tendermint/basecoin/modules/ibc" + "github.com/tendermint/basecoin/stack" +) + +// IBCQueryCmd - parent command to query ibc info +var IBCQueryCmd = &cobra.Command{ + Use: "ibc", + Short: "Get information about IBC", + RunE: commands.RequireInit(ibcQueryCmd), + // HandlerInfo +} + +// ChainsQueryCmd - get a list of all registered chains +var ChainsQueryCmd = &cobra.Command{ + Use: "chains", + Short: "Get a list of all registered chains", + RunE: commands.RequireInit(chainsQueryCmd), + // ChainSet ([]string) +} + +// ChainQueryCmd - get details on one registered chain +var ChainQueryCmd = &cobra.Command{ + Use: "chain [id]", + Short: "Get details on one registered chain", + RunE: commands.RequireInit(chainQueryCmd), + // ChainInfo +} + +// PacketsQueryCmd - get latest packet in a queue +var PacketsQueryCmd = &cobra.Command{ + Use: "packets", + Short: "Get latest packet in a queue", + RunE: commands.RequireInit(packetsQueryCmd), + // uint64 +} + +// PacketQueryCmd - get the names packet (by queue and sequence) +var PacketQueryCmd = &cobra.Command{ + Use: "packet", + Short: "Get packet with given sequence from the named queue", + RunE: commands.RequireInit(packetQueryCmd), + // Packet +} + +//nolint +const ( + FlagFromChain = "from" + FlagToChain = "to" + FlagSequence = "sequence" +) + +func init() { + IBCQueryCmd.AddCommand( + ChainQueryCmd, + ChainsQueryCmd, + PacketQueryCmd, + PacketsQueryCmd, + ) + + fs1 := PacketsQueryCmd.Flags() + fs1.String(FlagFromChain, "", "Name of the input chain (where packets came from)") + fs1.String(FlagToChain, "", "Name of the output chain (where packets go to)") + + fs2 := PacketQueryCmd.Flags() + fs2.String(FlagFromChain, "", "Name of the input chain (where packets came from)") + fs2.String(FlagToChain, "", "Name of the output chain (where packets go to)") + fs2.Int(FlagSequence, -1, "Name of the output chain (where packets go to)") +} + +func ibcQueryCmd(cmd *cobra.Command, args []string) error { + var res ibc.HandlerInfo + key := stack.PrefixedKey(ibc.NameIBC, ibc.HandlerKey()) + proof, err := proofcmd.GetAndParseAppProof(key, &res) + if err != nil { + return err + } + return proofcmd.OutputProof(res, proof.BlockHeight()) +} + +func chainsQueryCmd(cmd *cobra.Command, args []string) error { + list := [][]byte{} + key := stack.PrefixedKey(ibc.NameIBC, ibc.HandlerKey()) + proof, err := proofcmd.GetAndParseAppProof(key, &list) + if err != nil { + return err + } + + // convert these names to strings for better output + res := make([]string, len(list)) + for i := range list { + res[i] = string(list[i]) + } + + return proofcmd.OutputProof(res, proof.BlockHeight()) +} + +func chainQueryCmd(cmd *cobra.Command, args []string) error { + arg, err := commands.GetOneArg(args, "id") + if err != nil { + return err + } + + var res ibc.ChainInfo + key := stack.PrefixedKey(ibc.NameIBC, ibc.ChainKey(arg)) + proof, err := proofcmd.GetAndParseAppProof(key, &res) + if err != nil { + return err + } + + return proofcmd.OutputProof(res, proof.BlockHeight()) +} + +func assertOne(from, to string) error { + if from == "" && to == "" { + return errors.Errorf("You must specify either --%s or --%s", + FlagFromChain, FlagToChain) + } + if from != "" && to != "" { + return errors.Errorf("You can only specify one of --%s or --%s", + FlagFromChain, FlagToChain) + } + return nil +} + +func packetsQueryCmd(cmd *cobra.Command, args []string) error { + from := viper.GetString(FlagFromChain) + to := viper.GetString(FlagToChain) + err := assertOne(from, to) + if err != nil { + return err + } + + var key []byte + if from != "" { + key = stack.PrefixedKey(ibc.NameIBC, ibc.QueueInKey(from)) + } else { + key = stack.PrefixedKey(ibc.NameIBC, ibc.QueueOutKey(to)) + } + + var res uint64 + proof, err := proofcmd.GetAndParseAppProof(key, &res) + if err != nil { + return err + } + + return proofcmd.OutputProof(res, proof.BlockHeight()) +} + +func packetQueryCmd(cmd *cobra.Command, args []string) error { + from := viper.GetString(FlagFromChain) + to := viper.GetString(FlagToChain) + err := assertOne(from, to) + if err != nil { + return err + } + + seq := viper.GetInt(FlagSequence) + if seq < 0 { + return errors.Errorf("--%s must be a non-negative number", FlagSequence) + } + + var key []byte + if from != "" { + key = stack.PrefixedKey(ibc.NameIBC, ibc.QueueInPacketKey(from, uint64(seq))) + } else { + key = stack.PrefixedKey(ibc.NameIBC, ibc.QueueOutPacketKey(to, uint64(seq))) + } + + var res ibc.Packet + proof, err := proofcmd.GetAndParseAppProof(key, &res) + if err != nil { + return err + } + + return proofcmd.OutputProof(res, proof.BlockHeight()) +} diff --git a/modules/ibc/keys.go b/modules/ibc/keys.go new file mode 100644 index 000000000..e520d1888 --- /dev/null +++ b/modules/ibc/keys.go @@ -0,0 +1,60 @@ +package ibc + +import ( + "github.com/tendermint/basecoin/stack" + "github.com/tendermint/basecoin/state" +) + +const ( + // this is the prefix for the list of chains + // we otherwise use the chainid as prefix, so this must not be an + // alpha-numeric byte + prefixChains = "**" + + prefixInput = "i" + prefixOutput = "o" +) + +// HandlerKey is used for the global permission info +func HandlerKey() []byte { + return []byte{0x2} +} + +// ChainsKey is the key to get info on all chains +func ChainsKey() []byte { + return stack.PrefixedKey(prefixChains, state.SetKey()) +} + +// ChainKey is the key to get info on one chain +func ChainKey(chainID string) []byte { + bkey := state.MakeBKey([]byte(chainID)) + return stack.PrefixedKey(prefixChains, bkey) +} + +// QueueInKey is the key to get newest of the input queue from this chain +func QueueInKey(chainID string) []byte { + return stack.PrefixedKey(chainID, + stack.PrefixedKey(prefixInput, + state.QueueTailKey())) +} + +// QueueOutKey is the key to get v of the output queue from this chain +func QueueOutKey(chainID string) []byte { + return stack.PrefixedKey(chainID, + stack.PrefixedKey(prefixOutput, + state.QueueTailKey())) +} + +// QueueInPacketKey is the key to get given packet from this chain's input queue +func QueueInPacketKey(chainID string, seq uint64) []byte { + return stack.PrefixedKey(chainID, + stack.PrefixedKey(prefixInput, + state.QueueItemKey(seq))) +} + +// QueueOutPacketKey is the key to get given packet from this chain's output queue +func QueueOutPacketKey(chainID string, seq uint64) []byte { + return stack.PrefixedKey(chainID, + stack.PrefixedKey(prefixOutput, + state.QueueItemKey(seq))) +} diff --git a/modules/ibc/store.go b/modules/ibc/store.go index fc85416aa..17506ad65 100644 --- a/modules/ibc/store.go +++ b/modules/ibc/store.go @@ -7,21 +7,6 @@ import ( wire "github.com/tendermint/go-wire" ) -const ( - // this is the prefix for the list of chains - // we otherwise use the chainid as prefix, so this must not be an - // alpha-numeric byte - prefixChains = "**" - - prefixInput = "i" - prefixOutput = "o" -) - -// this is used for the global handler info -var ( - handlerKey = []byte{0x2} -) - // HandlerInfo is the global state of the ibc.Handler type HandlerInfo struct { Registrar basecoin.Actor `json:"registrar"` @@ -30,12 +15,12 @@ type HandlerInfo struct { // Save the HandlerInfo to the store func (h HandlerInfo) Save(store state.KVStore) { b := wire.BinaryBytes(h) - store.Set(handlerKey, b) + store.Set(HandlerKey(), b) } // LoadInfo loads the HandlerInfo from the data store func LoadInfo(store state.KVStore) (h HandlerInfo) { - b := store.Get(handlerKey) + b := store.Get(HandlerKey()) if len(b) > 0 { wire.ReadBinaryBytes(b, &h) } diff --git a/modules/roles/commands/query.go b/modules/roles/commands/query.go index d7c97a8e2..6a333f211 100644 --- a/modules/roles/commands/query.go +++ b/modules/roles/commands/query.go @@ -1,10 +1,9 @@ package commands import ( - "github.com/pkg/errors" "github.com/spf13/cobra" - lcmd "github.com/tendermint/basecoin/client/commands" + "github.com/tendermint/basecoin/client/commands" proofcmd "github.com/tendermint/basecoin/client/commands/proofs" "github.com/tendermint/basecoin/modules/roles" "github.com/tendermint/basecoin/stack" @@ -14,17 +13,15 @@ import ( var RoleQueryCmd = &cobra.Command{ Use: "role [name]", Short: "Get details of a role, with proof", - RunE: lcmd.RequireInit(roleQueryCmd), + RunE: commands.RequireInit(roleQueryCmd), } func roleQueryCmd(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return errors.New("Missing required argument [name]") - } else if len(args) > 1 { - return errors.New("Command only supports one name") + arg, err := commands.GetOneArg(args, "name") + if err != nil { + return err } - - role, err := parseRole(args[0]) + role, err := parseRole(arg) if err != nil { return err } diff --git a/state/queue.go b/state/queue.go index 9460cf1d1..c1b10440c 100644 --- a/state/queue.go +++ b/state/queue.go @@ -8,6 +8,21 @@ var ( dataKey = []byte("d") ) +// QueueHeadKey gives us the key for the height at head of the queue +func QueueHeadKey() []byte { + return headKey +} + +// QueueTailKey gives us the key for the height at tail of the queue +func QueueTailKey() []byte { + return tailKey +} + +// QueueItemKey gives us the key to look up one item by sequence +func QueueItemKey(i uint64) []byte { + return makeKey(i) +} + // Queue allows us to fill up a range of the db, and grab from either end type Queue struct { store KVStore diff --git a/state/set.go b/state/set.go index 98d6a4095..ea3a0ca24 100644 --- a/state/set.go +++ b/state/set.go @@ -7,6 +7,11 @@ import ( wire "github.com/tendermint/go-wire" ) +// SetKey returns the key to get all members of this set +func SetKey() []byte { + return keys +} + // Set allows us to add arbitrary k-v pairs, check existence, // as well as iterate through the set (always in key order) // @@ -29,7 +34,7 @@ func NewSet(store KVStore) *Set { // Set puts a value at a given height. // If the value is nil, or an empty slice, remove the key from the list func (s *Set) Set(key []byte, value []byte) { - s.store.Set(makeBKey(key), value) + s.store.Set(MakeBKey(key), value) if len(value) > 0 { s.addKey(key) } else { @@ -40,7 +45,7 @@ func (s *Set) Set(key []byte, value []byte) { // Get returns the element with a key if it exists func (s *Set) Get(key []byte) []byte { - return s.store.Get(makeBKey(key)) + return s.store.Get(MakeBKey(key)) } // Remove deletes this key from the set (same as setting value = nil) @@ -122,8 +127,8 @@ func (s *Set) storeKeys() { s.store.Set(keys, b) } -// makeBKey prefixes the byte slice for the storage key -func makeBKey(key []byte) []byte { +// MakeBKey prefixes the byte slice for the storage key +func MakeBKey(key []byte) []byte { return append(dataKey, key...) } From de537c34ac1dbbc6fcf0543bd65dede5bf15d389 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 21 Jul 2017 17:45:29 +0200 Subject: [PATCH 27/41] Add cli support for register/update ibc --- cmd/basecli/main.go | 3 + modules/ibc/commands/tx.go | 107 +++++++++++++++++++++++++++++++++++ modules/roles/commands/tx.go | 4 +- 3 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 modules/ibc/commands/tx.go diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go index 3a48febe2..490da2989 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -67,6 +67,9 @@ func main() { coincmd.SendTxCmd, // this enables creating roles rolecmd.CreateRoleTxCmd, + // these are for handling ibc + ibccmd.RegisterChainTxCmd, + ibccmd.UpdateChainTxCmd, ) // Set up the various commands to use diff --git a/modules/ibc/commands/tx.go b/modules/ibc/commands/tx.go new file mode 100644 index 000000000..608082bb1 --- /dev/null +++ b/modules/ibc/commands/tx.go @@ -0,0 +1,107 @@ +package commands + +import ( + "encoding/json" + "os" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/tendermint/basecoin/client/commands" + txcmd "github.com/tendermint/basecoin/client/commands/txs" + "github.com/tendermint/basecoin/modules/ibc" + "github.com/tendermint/light-client/certifiers" +) + +// RegisterChainTxCmd is CLI command to register a new chain for ibc +var RegisterChainTxCmd = &cobra.Command{ + Use: "ibc-register", + Short: "Register a new chain", + RunE: commands.RequireInit(registerChainTxCmd), +} + +// UpdateChainTxCmd is CLI command to update the header for an ibc chain +var UpdateChainTxCmd = &cobra.Command{ + Use: "ibc-update", + Short: "Add new header to an existing chain", + RunE: commands.RequireInit(updateChainTxCmd), +} + +// TODO: post packet (query and all that jazz) + +// TODO: relay! + +//nolint +const ( + FlagSeed = "seed" +) + +func init() { + fs1 := RegisterChainTxCmd.Flags() + fs1.String(FlagSeed, "", "Filename with a seed file") + + fs2 := UpdateChainTxCmd.Flags() + fs2.String(FlagSeed, "", "Filename with a seed file") +} + +func registerChainTxCmd(cmd *cobra.Command, args []string) error { + seed, err := readSeed() + if err != nil { + return err + } + tx := ibc.RegisterChainTx{seed}.Wrap() + return txcmd.DoTx(tx) +} + +func updateChainTxCmd(cmd *cobra.Command, args []string) error { + seed, err := readSeed() + if err != nil { + return err + } + tx := ibc.UpdateChainTx{seed}.Wrap() + return txcmd.DoTx(tx) +} + +func readSeed() (seed certifiers.Seed, err error) { + name := viper.GetString(FlagSeed) + if name == "" { + return seed, errors.New("You must specify a seed file") + } + + var f *os.File + f, err = os.Open(name) + if err != nil { + return seed, errors.Wrap(err, "Cannot read seed file") + } + defer f.Close() + + // read the file as json into a seed + j := json.NewDecoder(f) + err = j.Decode(&seed) + err = errors.Wrap(err, "Invalid seed file") + return +} + +// func readCreateRoleTxFlags() (tx basecoin.Tx, err error) { +// role, err := parseRole(viper.GetString(FlagRole)) +// if err != nil { +// return tx, err +// } + +// sigs := viper.GetInt(FlagMinSigs) +// if sigs < 1 { +// return tx, errors.Errorf("--%s must be at least 1", FlagMinSigs) +// } + +// signers, err := commands.ParseActors(viper.GetString(FlagMembers)) +// if err != nil { +// return tx, err +// } +// if len(signers) == 0 { +// return tx, errors.New("must specify at least one member") +// } + +// tx = roles.NewCreateRoleTx(role, uint32(sigs), signers) +// return tx, nil +// } diff --git a/modules/roles/commands/tx.go b/modules/roles/commands/tx.go index 710413142..d0492445c 100644 --- a/modules/roles/commands/tx.go +++ b/modules/roles/commands/tx.go @@ -11,7 +11,7 @@ import ( "github.com/tendermint/basecoin/modules/roles" ) -// CreateRoleTxCmd is CLI command to send tokens between basecoin accounts +// CreateRoleTxCmd is CLI command to create a new role var CreateRoleTxCmd = &cobra.Command{ Use: "create-role", Short: "Create a new role", @@ -32,7 +32,7 @@ func init() { flags.Int(FlagMinSigs, 0, "Minimum number of signatures needed to assume this role") } -// createRoleTxCmd is an example of how to make a tx +// createRoleTxCmd creates a basic role tx and then wraps, signs, and posts it func createRoleTxCmd(cmd *cobra.Command, args []string) error { tx, err := readCreateRoleTxFlags() if err != nil { From fd10387eb59393d72f8f95c6920338a7c313061a Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 21 Jul 2017 18:25:30 +0200 Subject: [PATCH 28/41] Tested register and update ibc via cli --- app/app.go | 28 ---- app/app_test.go | 30 +++++ client/commands/seeds/export.go | 21 ++- cmd/basecoin/main.go | 40 +++++- tests/cli/ibc.sh | 226 +++++++++++++++++++++----------- 5 files changed, 235 insertions(+), 110 deletions(-) diff --git a/app/app.go b/app/app.go index d27ee26e3..ca62ea80f 100644 --- a/app/app.go +++ b/app/app.go @@ -10,12 +10,6 @@ import ( "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/errors" - "github.com/tendermint/basecoin/modules/auth" - "github.com/tendermint/basecoin/modules/base" - "github.com/tendermint/basecoin/modules/coin" - "github.com/tendermint/basecoin/modules/fee" - "github.com/tendermint/basecoin/modules/nonce" - "github.com/tendermint/basecoin/modules/roles" "github.com/tendermint/basecoin/stack" sm "github.com/tendermint/basecoin/state" "github.com/tendermint/basecoin/version" @@ -50,28 +44,6 @@ func NewBasecoin(handler basecoin.Handler, store *Store, logger log.Logger) *Bas } } -// DefaultHandler - placeholder to just handle sendtx -func DefaultHandler(feeDenom string) basecoin.Handler { - // use the default stack - c := coin.NewHandler() - r := roles.NewHandler() - d := stack.NewDispatcher( - stack.WrapHandler(c), - stack.WrapHandler(r), - ) - return stack.New( - base.Logger{}, - stack.Recovery{}, - auth.Signatures{}, - base.Chain{}, - stack.Checkpoint{OnCheck: true}, - nonce.ReplayCheck{}, - roles.NewMiddleware(), - fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank), - stack.Checkpoint{OnDeliver: true}, - ).Use(d) -} - // GetChainID returns the currently stored chain func (app *Basecoin) GetChainID() string { return app.info.GetChainID(app.state.Committed()) diff --git a/app/app_test.go b/app/app_test.go index d09a69d23..5ea8cc59b 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -13,13 +13,43 @@ 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" wire "github.com/tendermint/go-wire" "github.com/tendermint/tmlibs/log" ) +// 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() + + return stack.New( + base.Logger{}, + stack.Recovery{}, + auth.Signatures{}, + base.Chain{}, + stack.Checkpoint{OnCheck: true}, + nonce.ReplayCheck{}, + ). + IBC(ibc.NewMiddleware()). + Apps( + roles.NewMiddleware(), + fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank), + stack.Checkpoint{OnDeliver: true}, + ). + Dispatch( + stack.WrapHandler(c), + stack.WrapHandler(r), + stack.WrapHandler(i), + ) +} + //-------------------------------------------------------- // test environment is a list of input and output accounts diff --git a/client/commands/seeds/export.go b/client/commands/seeds/export.go index 1ac3ac42a..babd1748a 100644 --- a/client/commands/seeds/export.go +++ b/client/commands/seeds/export.go @@ -1,11 +1,15 @@ package seeds import ( + "encoding/json" + "os" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/tendermint/basecoin/client/commands" + "github.com/tendermint/light-client/certifiers" ) var exportCmd = &cobra.Command{ @@ -40,5 +44,20 @@ func exportSeed(cmd *cobra.Command, args []string) error { } // now get the output file and write it - return seed.Write(path) + return writeSeed(seed, path) +} + +func writeSeed(seed certifiers.Seed, path string) (err error) { + var f *os.File + f, err = os.Create(path) + if err == nil { + stream := json.NewEncoder(f) + err = stream.Encode(seed) + f.Close() + } + // we don't write, but this is not an error + if os.IsExist(err) { + return nil + } + return errors.WithStack(err) } diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 33ba8c1d4..5f98b085d 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -5,15 +5,51 @@ import ( "github.com/tendermint/tmlibs/cli" - "github.com/tendermint/basecoin/app" + "github.com/tendermint/basecoin" "github.com/tendermint/basecoin/cmd/basecoin/commands" + "github.com/tendermint/basecoin/modules/auth" + "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" ) +// 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{}, + auth.Signatures{}, + base.Chain{}, + stack.Checkpoint{OnCheck: true}, + nonce.ReplayCheck{}, + ). + IBC(ibc.NewMiddleware()). + Apps( + roles.NewMiddleware(), + fee.NewSimpleFeeMiddleware(coin.Coin{feeDenom, 0}, fee.Bank), + stack.Checkpoint{OnDeliver: true}, + ). + Dispatch( + stack.WrapHandler(c), + stack.WrapHandler(r), + stack.WrapHandler(i), + ) +} + func main() { rt := commands.RootCmd // require all fees in mycoin - change this in your app! - commands.Handler = app.DefaultHandler("mycoin") + commands.Handler = BuildApp("mycoin") rt.AddCommand( commands.InitCmd, diff --git a/tests/cli/ibc.sh b/tests/cli/ibc.sh index 36f380ded..5a5c91c0d 100755 --- a/tests/cli/ibc.sh +++ b/tests/cli/ibc.sh @@ -62,13 +62,13 @@ oneTimeTearDown() { } test00GetAccount() { - SENDER_1=$(BC_HOME=${CLIENT_1} getAddr $RICH) - RECV_1=$(BC_HOME=${CLIENT_1} getAddr $POOR) export BC_HOME=${CLIENT_1} + SENDER_1=$(getAddr $RICH) + RECV_1=$(getAddr $POOR) assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account 2>/dev/null" assertFalse "line=${LINENO}, has no genesis account" "${CLIENT_EXE} query account $RECV_1 2>/dev/null" - checkAccount $SENDER_1 "0" "9007199254740992" + checkAccount $SENDER_1 "9007199254740992" export BC_HOME=${CLIENT_2} SENDER_2=$(getAddr $RICH) @@ -76,102 +76,170 @@ test00GetAccount() { assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account 2>/dev/null" assertFalse "line=${LINENO}, has no genesis account" "${CLIENT_EXE} query account $RECV_2 2>/dev/null" - checkAccount $SENDER_2 "0" "9007199254740992" + checkAccount $SENDER_2 "9007199254740992" # Make sure that they have different addresses on both chains (they are random keys) assertNotEquals "line=${LINENO}, sender keys must be different" "$SENDER_1" "$SENDER_2" assertNotEquals "line=${LINENO}, recipient keys must be different" "$RECV_1" "$RECV_2" } -test01SendIBCTx() { - # Trigger a cross-chain sendTx... from RICH on chain1 to POOR on chain2 - # we make sure the money was reduced, but nothing arrived - SENDER=$(BC_HOME=${CLIENT_1} getAddr $RICH) - RECV=$(BC_HOME=${CLIENT_2} getAddr $POOR) +test01RegisterChains() { + # let's get the root seeds to cross-register them + ROOT_1="$BASE_DIR_1/root_seed.json" + ${CLIENT_EXE} seeds export $ROOT_1 --home=${CLIENT_1} + assertTrue "line=${LINENO}, export seed failed" $? - export BC_HOME=${CLIENT_1} - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=20002mycoin \ - --sequence=1 --to=${CHAIN_ID_2}/${RECV} --name=$RICH) - txSucceeded $? "$TX" "${CHAIN_ID_2}/${RECV}" + ROOT_2="$BASE_DIR_2/root_seed.json" + ${CLIENT_EXE} seeds export $ROOT_2 --home=${CLIENT_2} + assertTrue "line=${LINENO}, export seed failed" $? + + # register chain2 on chain1 + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-register \ + --sequence=1 --seed=${ROOT_2} --name=$POOR --home=${CLIENT_1}) + txSucceeded $? "$TX" "register chain2 on chain 1" # an example to quit early if there is no point in more tests if [ $? != 0 ]; then echo "aborting!"; return 1; fi - HASH=$(echo $TX | jq .hash | tr -d \") - TX_HEIGHT=$(echo $TX | jq .height) - - # Make sure balance went down and tx is indexed - checkAccount $SENDER "1" "9007199254720990" - checkSendTx $HASH $TX_HEIGHT $SENDER "20002" - - # Make sure nothing arrived - yet - waitForBlock ${PORT_1} - assertFalse "line=${LINENO}, no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV" - - # Start the relay and wait a few blocks... - # (already sent a tx on chain1, so use higher sequence) - startRelay 2 1 - if [ $? != 0 ]; then echo "can't start relay"; cat ${BASE_DIR_1}/../relay.log; return 1; fi - - # Give it a little time, then make sure the money arrived - echo "waiting for relay..." - sleep 1 - waitForBlock ${PORT_1} - waitForBlock ${PORT_2} - - # Check the new account - echo "checking ibc recipient..." - BC_HOME=${CLIENT_2} checkAccount $RECV "0" "20002" - - # Stop relay - printf "stoping relay\n" - kill -9 $PID_RELAY + # register chain1 on chain2 (no money needed... yet) + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-register \ + --sequence=1 --seed=${ROOT_1} --name=$POOR --home=${CLIENT_2}) + txSucceeded $? "$TX" "register chain1 on chain 2" + # an example to quit early if there is no point in more tests + if [ $? != 0 ]; then echo "aborting!"; return 1; fi } -# StartRelay $seq1 $seq2 -# startRelay hooks up a relay between chain1 and chain2 -# it needs the proper sequence number for $RICH on chain1 and chain2 as args -startRelay() { - # Send some cash to the default key, so it can send messages - RELAY_KEY=${BASE_DIR_1}/server/key.json - RELAY_ADDR=$(cat $RELAY_KEY | jq .address | tr -d \") - echo starting relay $PID_RELAY ... +test02UpdateChains() { + # let's get the root seeds to cross-register them + UPDATE_1="$BASE_DIR_1/seed_1.json" + ${CLIENT_EXE} seeds update --home=${CLIENT_1} > /dev/null + ${CLIENT_EXE} seeds export $UPDATE_1 --home=${CLIENT_1} + assertTrue "line=${LINENO}, export seed failed" $? + # make sure it is newer than the other.... + assertNewHeight "line=${LINENO}" $ROOT_1 $UPDATE_1 - # Get paid on chain1 - export BC_HOME=${CLIENT_1} - SENDER=$(getAddr $RICH) - RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=100000mycoin \ - --sequence=$1 --to=$RELAY_ADDR --name=$RICH) - txSucceeded $? "$RES" "$RELAY_ADDR" - if [ $? != 0 ]; then echo "can't pay chain1!"; return 1; fi + UPDATE_2="$BASE_DIR_2/seed_2.json" + ${CLIENT_EXE} seeds update --home=${CLIENT_2} > /dev/null + ${CLIENT_EXE} seeds export $UPDATE_2 --home=${CLIENT_2} + assertTrue "line=${LINENO}, export seed failed" $? + assertNewHeight "line=${LINENO}" $ROOT_2 $UPDATE_2 - # Get paid on chain2 - export BC_HOME=${CLIENT_2} - SENDER=$(getAddr $RICH) - RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=100000mycoin \ - --sequence=$2 --to=$RELAY_ADDR --name=$RICH) - txSucceeded $? "$RES" "$RELAY_ADDR" - if [ $? != 0 ]; then echo "can't pay chain2!"; return 1; fi + # update chain2 on chain1 + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \ + --sequence=2 --seed=${UPDATE_2} --name=$POOR --home=${CLIENT_1}) + txSucceeded $? "$TX" "update chain2 on chain 1" + # an example to quit early if there is no point in more tests + if [ $? != 0 ]; then echo "aborting!"; return 1; fi - # Initialize the relay (register both chains) - ${SERVER_EXE} relay init --chain1-id=$CHAIN_ID_1 --chain2-id=$CHAIN_ID_2 \ - --chain1-addr=tcp://localhost:${PORT_1} --chain2-addr=tcp://localhost:${PORT_2} \ - --genesis1=${BASE_DIR_1}/server/genesis.json --genesis2=${BASE_DIR_2}/server/genesis.json \ - --from=$RELAY_KEY > ${BASE_DIR_1}/../relay.log - if [ $? != 0 ]; then echo "can't initialize relays"; cat ${BASE_DIR_1}/../relay.log; return 1; fi + # update chain1 on chain2 (no money needed... yet) + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \ + --sequence=2 --seed=${UPDATE_1} --name=$POOR --home=${CLIENT_2}) + txSucceeded $? "$TX" "update chain1 on chain 2" + # an example to quit early if there is no point in more tests + if [ $? != 0 ]; then echo "aborting!"; return 1; fi +} - # Now start the relay (constantly send packets) - ${SERVER_EXE} relay start --chain1-id=$CHAIN_ID_1 --chain2-id=$CHAIN_ID_2 \ - --chain1-addr=tcp://localhost:${PORT_1} --chain2-addr=tcp://localhost:${PORT_2} \ - --from=$RELAY_KEY >> ${BASE_DIR_1}/../relay.log & - sleep 2 - PID_RELAY=$! - disown +test03QueryIBC() { - # Return an error if it dies in the first two seconds to make sure it is running - ps $PID_RELAY >/dev/null +} + +# XXX Ex Usage: assertNewHeight $MSG $SEED_1 $SEED_2 +# Desc: Asserts that seed2 has a higher block height than seed 1 +assertNewHeight() { + H1=$(cat $2 | jq .checkpoint.header.height) + H2=$(cat $3 | jq .checkpoint.header.height) + assertTrue "$MSG" "test $H2 -gt $H1" return $? } +# test01SendIBCTx() { +# # Trigger a cross-chain sendTx... from RICH on chain1 to POOR on chain2 +# # we make sure the money was reduced, but nothing arrived +# SENDER=$(BC_HOME=${CLIENT_1} getAddr $RICH) +# RECV=$(BC_HOME=${CLIENT_2} getAddr $POOR) + +# export BC_HOME=${CLIENT_1} +# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=20002mycoin \ +# --sequence=1 --to=${CHAIN_ID_2}/${RECV} --name=$RICH) +# txSucceeded $? "$TX" "${CHAIN_ID_2}/${RECV}" +# # an example to quit early if there is no point in more tests +# if [ $? != 0 ]; then echo "aborting!"; return 1; fi + +# HASH=$(echo $TX | jq .hash | tr -d \") +# TX_HEIGHT=$(echo $TX | jq .height) + +# # Make sure balance went down and tx is indexed +# checkAccount $SENDER "1" "9007199254720990" +# checkSendTx $HASH $TX_HEIGHT $SENDER "20002" + +# # Make sure nothing arrived - yet +# waitForBlock ${PORT_1} +# assertFalse "line=${LINENO}, no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV" + +# # Start the relay and wait a few blocks... +# # (already sent a tx on chain1, so use higher sequence) +# startRelay 2 1 +# if [ $? != 0 ]; then echo "can't start relay"; cat ${BASE_DIR_1}/../relay.log; return 1; fi + +# # Give it a little time, then make sure the money arrived +# echo "waiting for relay..." +# sleep 1 +# waitForBlock ${PORT_1} +# waitForBlock ${PORT_2} + +# # Check the new account +# echo "checking ibc recipient..." +# BC_HOME=${CLIENT_2} checkAccount $RECV "0" "20002" + +# # Stop relay +# printf "stoping relay\n" +# kill -9 $PID_RELAY +# } + +# # StartRelay $seq1 $seq2 +# # startRelay hooks up a relay between chain1 and chain2 +# # it needs the proper sequence number for $RICH on chain1 and chain2 as args +# startRelay() { +# # Send some cash to the default key, so it can send messages +# RELAY_KEY=${BASE_DIR_1}/server/key.json +# RELAY_ADDR=$(cat $RELAY_KEY | jq .address | tr -d \") +# echo starting relay $PID_RELAY ... + +# # Get paid on chain1 +# export BC_HOME=${CLIENT_1} +# SENDER=$(getAddr $RICH) +# RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=100000mycoin \ +# --sequence=$1 --to=$RELAY_ADDR --name=$RICH) +# txSucceeded $? "$RES" "$RELAY_ADDR" +# if [ $? != 0 ]; then echo "can't pay chain1!"; return 1; fi + +# # Get paid on chain2 +# export BC_HOME=${CLIENT_2} +# SENDER=$(getAddr $RICH) +# RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=100000mycoin \ +# --sequence=$2 --to=$RELAY_ADDR --name=$RICH) +# txSucceeded $? "$RES" "$RELAY_ADDR" +# if [ $? != 0 ]; then echo "can't pay chain2!"; return 1; fi + +# # Initialize the relay (register both chains) +# ${SERVER_EXE} relay init --chain1-id=$CHAIN_ID_1 --chain2-id=$CHAIN_ID_2 \ +# --chain1-addr=tcp://localhost:${PORT_1} --chain2-addr=tcp://localhost:${PORT_2} \ +# --genesis1=${BASE_DIR_1}/server/genesis.json --genesis2=${BASE_DIR_2}/server/genesis.json \ +# --from=$RELAY_KEY > ${BASE_DIR_1}/../relay.log +# if [ $? != 0 ]; then echo "can't initialize relays"; cat ${BASE_DIR_1}/../relay.log; return 1; fi + +# # Now start the relay (constantly send packets) +# ${SERVER_EXE} relay start --chain1-id=$CHAIN_ID_1 --chain2-id=$CHAIN_ID_2 \ +# --chain1-addr=tcp://localhost:${PORT_1} --chain2-addr=tcp://localhost:${PORT_2} \ +# --from=$RELAY_KEY >> ${BASE_DIR_1}/../relay.log & +# sleep 2 +# PID_RELAY=$! +# disown + +# # Return an error if it dies in the first two seconds to make sure it is running +# ps $PID_RELAY >/dev/null +# return $? +# } + # Load common then run these tests with shunit2! DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory . $DIR/common.sh From aad5a0f3a075f118ed346cd50100c15fdd059233 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 21 Jul 2017 18:59:11 +0200 Subject: [PATCH 29/41] Test query ibc status and fix bugs --- modules/ibc/commands/query.go | 2 +- modules/ibc/handler.go | 11 +++++++++-- modules/ibc/store.go | 21 +++++++++++++++++++++ tests/cli/ibc.sh | 24 ++++++++++++++++++++++-- 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/modules/ibc/commands/query.go b/modules/ibc/commands/query.go index 38514d4c2..fed670fd1 100644 --- a/modules/ibc/commands/query.go +++ b/modules/ibc/commands/query.go @@ -88,7 +88,7 @@ func ibcQueryCmd(cmd *cobra.Command, args []string) error { func chainsQueryCmd(cmd *cobra.Command, args []string) error { list := [][]byte{} - key := stack.PrefixedKey(ibc.NameIBC, ibc.HandlerKey()) + key := stack.PrefixedKey(ibc.NameIBC, ibc.ChainsKey()) proof, err := proofcmd.GetAndParseAppProof(key, &list) if err != nil { return err diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index be97ab83f..57c7d9638 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -145,7 +145,8 @@ func (h Handler) updateSeed(ctx basecoin.Context, store state.KVStore, t UpdateChainTx) (res basecoin.Result, err error) { chainID := t.ChainID() - if !NewChainSet(store).Exists([]byte(chainID)) { + s := NewChainSet(store) + if !s.Exists([]byte(chainID)) { return res, ErrNotRegistered(chainID) } @@ -159,7 +160,13 @@ func (h Handler) updateSeed(ctx basecoin.Context, store state.KVStore, // this will import the seed if it is valid in the current context err = cert.Update(seed.Checkpoint, seed.Validators) - return res, ErrInvalidCommit(err) + if err != nil { + return res, ErrInvalidCommit(err) + } + + // update the tracked height in chain info + err = s.Update(chainID, t.Seed.Height()) + return res, err } // createPacket makes sure all permissions are good and the destination diff --git a/modules/ibc/store.go b/modules/ibc/store.go index 17506ad65..af5654424 100644 --- a/modules/ibc/store.go +++ b/modules/ibc/store.go @@ -62,6 +62,27 @@ func (c ChainSet) Register(chainID string, ourHeight uint64, theirHeight int) er return nil } +// Update sets the new tracked height on this chain +// returns error if not present +func (c ChainSet) Update(chainID string, theirHeight int) error { + d := c.Set.Get([]byte(chainID)) + if len(d) == 0 { + return ErrNotRegistered(chainID) + } + // load the data + var info ChainInfo + err := wire.ReadBinaryBytes(d, &info) + if err != nil { + return err + } + + // change the remote block and save it + info.RemoteBlock = theirHeight + d = wire.BinaryBytes(info) + c.Set.Set([]byte(chainID), d) + return nil +} + // Packet is a wrapped transaction and permission that we want to // send off to another chain. type Packet struct { diff --git a/tests/cli/ibc.sh b/tests/cli/ibc.sh index 5a5c91c0d..252c297ee 100755 --- a/tests/cli/ibc.sh +++ b/tests/cli/ibc.sh @@ -9,8 +9,8 @@ ACCOUNTS=(jae ethan bucky rigel igor) RICH=${ACCOUNTS[0]} POOR=${ACCOUNTS[4]} -# Uncomment the following line for full stack traces in error output -# CLIENT_EXE="basecli --trace" +# For full stack traces in error output, run +# BC_TRACE=1 ./ibc.sh oneTimeSetUp() { # These are passed in as args @@ -99,6 +99,8 @@ test01RegisterChains() { txSucceeded $? "$TX" "register chain2 on chain 1" # an example to quit early if there is no point in more tests if [ $? != 0 ]; then echo "aborting!"; return 1; fi + # this is used later to check data + REG_HEIGHT=$(echo $TX | jq .height) # register chain1 on chain2 (no money needed... yet) TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-register \ @@ -122,6 +124,9 @@ test02UpdateChains() { ${CLIENT_EXE} seeds export $UPDATE_2 --home=${CLIENT_2} assertTrue "line=${LINENO}, export seed failed" $? assertNewHeight "line=${LINENO}" $ROOT_2 $UPDATE_2 + # this is used later to check query data + REGISTER_2_HEIGHT=$(cat $ROOT_2 | jq .checkpoint.header.height) + UPDATE_2_HEIGHT=$(cat $UPDATE_2 | jq .checkpoint.header.height) # update chain2 on chain1 TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \ @@ -138,8 +143,23 @@ test02UpdateChains() { if [ $? != 0 ]; then echo "aborting!"; return 1; fi } +# make sure all query commands about ibc work... test03QueryIBC() { + # just test on one chain, as they are all symetrical + export BC_HOME=${CLIENT_1} + # make sure we can list all chains + CHAINS=$(${CLIENT_EXE} query ibc chains) + assertTrue "line=${LINENO}, cannot query chains" $? + assertEquals "1" $(echo $CHAINS | jq '.data | length') + assertEquals "line=${LINENO}" "\"$CHAIN_ID_2\"" $(echo $CHAINS | jq '.data[0]') + + # error on unknown chain, data on proper chain + assertFalse "line=${LINENO}, unknown chain" "${CLIENT_EXE} query ibc chain random 2>/dev/null" + CHAIN_INFO=$(${CLIENT_EXE} query ibc chain $CHAIN_ID_2) + assertTrue "line=${LINENO}, cannot query chain $CHAIN_ID_2" $? + assertEquals "line=${LINENO}, register height" $REG_HEIGHT $(echo $CHAIN_INFO | jq .data.registered_at) + assertEquals "line=${LINENO}, tracked height" $UPDATE_2_HEIGHT $(echo $CHAIN_INFO | jq .data.remote_block) } # XXX Ex Usage: assertNewHeight $MSG $SEED_1 $SEED_2 From b7f31ad70a393ced61894a41abc2ab33177e5531 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 21 Jul 2017 19:53:52 +0200 Subject: [PATCH 30/41] Test sendtx with foreign addr creates proper ibc packet --- Makefile | 2 +- app/app_test.go | 3 +- cmd/basecoin/main.go | 11 ++--- docs/guide/counter/plugins/counter/counter.go | 23 +++++---- modules/coin/bench_test.go | 6 +-- modules/coin/handler.go | 48 +++++++++++++++++-- modules/coin/handler_test.go | 5 +- modules/coin/ibc_test.go | 47 +++++++++++++++--- modules/fee/handler_test.go | 2 +- modules/ibc/handler.go | 12 ++--- modules/ibc/middleware.go | 6 +-- modules/ibc/provider.go | 6 +-- modules/ibc/store.go | 10 ++-- modules/ibc/test_helpers.go | 8 ++-- state/queue.go | 8 ++++ 15 files changed, 137 insertions(+), 60 deletions(-) diff --git a/Makefile b/Makefile index 0d3eac4ce..089ee47c4 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 5ea8cc59b..4bef8443c 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 5f98b085d..b99482e1d 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 16a10650c..e9c2b503d 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 34bf39bd1..bc83ea90c 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 ff1f04037..ffb75aa00 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 11959eccf..1edd12d87 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 24856ef6f..29c5462e5 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 b775c2d56..000d99f0b 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 57c7d9638..cba557970 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 0c02088f2..7852bf1e5 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 2c508836f..7ff8858d8 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 af5654424..e675455d7 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 844a7c9ac..9d118141e 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 c1b10440c..756f90bb6 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) From 89a8c0bf08550443198eb543f14967ada16f9ab5 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 21 Jul 2017 19:54:40 +0200 Subject: [PATCH 31/41] Remvoed old ibc code --- plugins/ibc/ibc.go | 600 ---------------------------------------- plugins/ibc/ibc_test.go | 512 ---------------------------------- 2 files changed, 1112 deletions(-) delete mode 100644 plugins/ibc/ibc.go delete mode 100644 plugins/ibc/ibc_test.go diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go deleted file mode 100644 index 0117a101e..000000000 --- a/plugins/ibc/ibc.go +++ /dev/null @@ -1,600 +0,0 @@ -package ibc - -// import ( -// "bytes" -// "encoding/json" -// "errors" -// "fmt" -// "net/url" -// "strconv" -// "strings" - -// abci "github.com/tendermint/abci/types" -// "github.com/tendermint/go-wire" -// merkle "github.com/tendermint/merkleeyes/iavl" -// cmn "github.com/tendermint/tmlibs/common" - -// "github.com/tendermint/basecoin/types" -// tm "github.com/tendermint/tendermint/types" -// ) - -// const ( -// // Key parts -// _IBC = "ibc" -// _BLOCKCHAIN = "blockchain" -// _GENESIS = "genesis" -// _STATE = "state" -// _HEADER = "header" -// _EGRESS = "egress" -// _INGRESS = "ingress" -// _CONNECTION = "connection" -// ) - -// type IBCPluginState struct { -// // @[:ibc, :blockchain, :genesis, ChainID] <~ BlockchainGenesis -// // @[:ibc, :blockchain, :state, ChainID] <~ BlockchainState -// // @[:ibc, :blockchain, :header, ChainID, Height] <~ tm.Header -// // @[:ibc, :egress, Src, Dst, Sequence] <~ Packet -// // @[:ibc, :ingress, Dst, Src, Sequence] <~ Packet -// // @[:ibc, :connection, Src, Dst] <~ Connection # TODO - keep connection state -// } - -// type BlockchainGenesis struct { -// ChainID string -// Genesis string -// } - -// type BlockchainState struct { -// ChainID string -// Validators []*tm.Validator -// LastBlockHash []byte -// LastBlockHeight uint64 -// } - -// type Packet struct { -// SrcChainID string -// DstChainID string -// Sequence uint64 -// Type string // redundant now that Type() is a method on Payload ? -// Payload Payload -// } - -// func NewPacket(src, dst string, seq uint64, payload Payload) Packet { -// return Packet{ -// SrcChainID: src, -// DstChainID: dst, -// Sequence: seq, -// Type: payload.Type(), -// Payload: payload, -// } -// } - -// // GetSequenceNumber gets the sequence number for packets being sent from the src chain to the dst chain. -// // The sequence number counts how many packets have been sent. -// // The next packet must include the latest sequence number. -// func GetSequenceNumber(store state.SimpleDB, src, dst string) uint64 { -// sequenceKey := toKey(_IBC, _EGRESS, src, dst) -// seqBytes := store.Get(sequenceKey) -// if seqBytes == nil { -// return 0 -// } -// seq, err := strconv.ParseUint(string(seqBytes), 10, 64) -// if err != nil { -// cmn.PanicSanity(err.Error()) -// } -// return seq -// } - -// // SetSequenceNumber sets the sequence number for packets being sent from the src chain to the dst chain -// func SetSequenceNumber(store state.SimpleDB, src, dst string, seq uint64) { -// sequenceKey := toKey(_IBC, _EGRESS, src, dst) -// store.Set(sequenceKey, []byte(strconv.FormatUint(seq, 10))) -// } - -// // SaveNewIBCPacket creates an IBC packet with the given payload from the src chain to the dst chain -// // using the correct sequence number. It also increments the sequence number by 1 -// func SaveNewIBCPacket(state state.SimpleDB, src, dst string, payload Payload) { -// // fetch sequence number and increment by 1 -// seq := GetSequenceNumber(state, src, dst) -// SetSequenceNumber(state, src, dst, seq+1) - -// // save ibc packet -// packetKey := toKey(_IBC, _EGRESS, src, dst, cmn.Fmt("%v", seq)) -// packet := NewPacket(src, dst, uint64(seq), payload) -// save(state, packetKey, packet) -// } - -// func GetIBCPacket(state state.SimpleDB, src, dst string, seq uint64) (Packet, error) { -// packetKey := toKey(_IBC, _EGRESS, src, dst, cmn.Fmt("%v", seq)) -// packetBytes := state.Get(packetKey) - -// var packet Packet -// err := wire.ReadBinaryBytes(packetBytes, &packet) -// return packet, err -// } - -// //-------------------------------------------------------------------------------- - -// const ( -// PayloadTypeBytes = byte(0x01) -// PayloadTypeCoins = byte(0x02) -// ) - -// var _ = wire.RegisterInterface( -// struct{ Payload }{}, -// wire.ConcreteType{DataPayload{}, PayloadTypeBytes}, -// wire.ConcreteType{CoinsPayload{}, PayloadTypeCoins}, -// ) - -// type Payload interface { -// AssertIsPayload() -// Type() string -// ValidateBasic() abci.Result -// } - -// func (DataPayload) AssertIsPayload() {} -// func (CoinsPayload) AssertIsPayload() {} - -// type DataPayload []byte - -// func (p DataPayload) Type() string { -// return "data" -// } - -// func (p DataPayload) ValidateBasic() abci.Result { -// return abci.OK -// } - -// type CoinsPayload struct { -// Address []byte -// Coins coin.Coins -// } - -// func (p CoinsPayload) Type() string { -// return "coin" -// } - -// func (p CoinsPayload) ValidateBasic() abci.Result { -// // TODO: validate -// return abci.OK -// } - -// //-------------------------------------------------------------------------------- - -// const ( -// IBCTxTypeRegisterChain = byte(0x01) -// IBCTxTypeUpdateChain = byte(0x02) -// IBCTxTypePacketCreate = byte(0x03) -// IBCTxTypePacketPost = byte(0x04) - -// IBCCodeEncodingError = abci.CodeType(1001) -// IBCCodeChainAlreadyExists = abci.CodeType(1002) -// IBCCodePacketAlreadyExists = abci.CodeType(1003) -// IBCCodeUnknownHeight = abci.CodeType(1004) -// IBCCodeInvalidCommit = abci.CodeType(1005) -// IBCCodeInvalidProof = abci.CodeType(1006) -// ) - -// var _ = wire.RegisterInterface( -// struct{ IBCTx }{}, -// wire.ConcreteType{IBCRegisterChainTx{}, IBCTxTypeRegisterChain}, -// wire.ConcreteType{IBCUpdateChainTx{}, IBCTxTypeUpdateChain}, -// wire.ConcreteType{IBCPacketCreateTx{}, IBCTxTypePacketCreate}, -// wire.ConcreteType{IBCPacketPostTx{}, IBCTxTypePacketPost}, -// ) - -// type IBCTx interface { -// AssertIsIBCTx() -// ValidateBasic() abci.Result -// } - -// func (IBCRegisterChainTx) AssertIsIBCTx() {} -// func (IBCUpdateChainTx) AssertIsIBCTx() {} -// func (IBCPacketCreateTx) AssertIsIBCTx() {} -// func (IBCPacketPostTx) AssertIsIBCTx() {} - -// type IBCRegisterChainTx struct { -// BlockchainGenesis -// } - -// func (IBCRegisterChainTx) ValidateBasic() (res abci.Result) { -// // TODO - validate -// return -// } - -// type IBCUpdateChainTx struct { -// Header tm.Header -// Commit tm.Commit -// // TODO: NextValidators -// } - -// func (IBCUpdateChainTx) ValidateBasic() (res abci.Result) { -// // TODO - validate -// return -// } - -// type IBCPacketCreateTx struct { -// Packet -// } - -// func (IBCPacketCreateTx) ValidateBasic() (res abci.Result) { -// // TODO - validate -// return -// } - -// type IBCPacketPostTx struct { -// FromChainID string // The immediate source of the packet, not always Packet.SrcChainID -// FromChainHeight uint64 // The block height in which Packet was committed, to check Proof -// Packet -// Proof *merkle.IAVLProof -// } - -// func (IBCPacketPostTx) ValidateBasic() (res abci.Result) { -// // TODO - validate -// return -// } - -// //-------------------------------------------------------------------------------- - -// type IBCPlugin struct { -// } - -// func (ibc *IBCPlugin) Name() string { -// return "IBC" -// } - -// func (ibc *IBCPlugin) StateKey() []byte { -// return []byte("IBCPlugin.State") -// } - -// func New() *IBCPlugin { -// return &IBCPlugin{} -// } - -// func (ibc *IBCPlugin) SetOption(store state.SimpleDB, key string, value string) (log string) { -// return "" -// } - -// func (ibc *IBCPlugin) RunTx(store state.SimpleDB, ctx types.CallContext, txBytes []byte) (res abci.Result) { -// // Decode tx -// var tx IBCTx -// err := wire.ReadBinaryBytes(txBytes, &tx) -// if err != nil { -// return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) -// } - -// // Validate tx -// res = tx.ValidateBasic() -// if res.IsErr() { -// return res.PrependLog("ValidateBasic Failed: ") -// } - -// // TODO - Check whether sufficient funds - -// defer func() { -// // TODO - Refund any remaining funds left over -// // e.g. !ctx.Coins.Minus(tx.Fee).IsZero() -// // ctx.CallerAccount is synced w/ store, so just modify that and store it. -// // NOTE: We should use the CallContext to store fund/refund information. -// }() - -// sm := &IBCStateMachine{store, ctx, abci.OK} - -// switch tx := tx.(type) { -// case IBCRegisterChainTx: -// sm.runRegisterChainTx(tx) -// case IBCUpdateChainTx: -// sm.runUpdateChainTx(tx) -// case IBCPacketCreateTx: -// sm.runPacketCreateTx(tx) -// case IBCPacketPostTx: -// sm.runPacketPostTx(tx) -// } - -// return sm.res -// } - -// type IBCStateMachine struct { -// store state.SimpleDB -// ctx types.CallContext -// res abci.Result -// } - -// func (sm *IBCStateMachine) runRegisterChainTx(tx IBCRegisterChainTx) { -// chainGenKey := toKey(_IBC, _BLOCKCHAIN, _GENESIS, tx.ChainID) -// chainStateKey := toKey(_IBC, _BLOCKCHAIN, _STATE, tx.ChainID) -// chainGen := tx.BlockchainGenesis - -// // Parse genesis -// chainGenDoc := new(tm.GenesisDoc) -// err := json.Unmarshal([]byte(chainGen.Genesis), chainGenDoc) -// if err != nil { -// sm.res.Code = IBCCodeEncodingError -// sm.res.Log = "Genesis doc couldn't be parsed: " + err.Error() -// return -// } - -// // Make sure chainGen doesn't already exist -// if exists(sm.store, chainGenKey) { -// sm.res.Code = IBCCodeChainAlreadyExists -// sm.res.Log = "Already exists" -// return -// } - -// // Save new BlockchainGenesis -// save(sm.store, chainGenKey, chainGen) - -// // Create new BlockchainState -// chainState := BlockchainState{ -// ChainID: chainGenDoc.ChainID, -// Validators: make([]*tm.Validator, len(chainGenDoc.Validators)), -// LastBlockHash: nil, -// LastBlockHeight: 0, -// } -// // Make validators slice -// for i, val := range chainGenDoc.Validators { -// pubKey := val.PubKey -// address := pubKey.Address() -// chainState.Validators[i] = &tm.Validator{ -// Address: address, -// PubKey: pubKey, -// VotingPower: val.Amount, -// } -// } - -// // Save new BlockchainState -// save(sm.store, chainStateKey, chainState) -// } - -// func (sm *IBCStateMachine) runUpdateChainTx(tx IBCUpdateChainTx) { -// chainID := tx.Header.ChainID -// chainStateKey := toKey(_IBC, _BLOCKCHAIN, _STATE, chainID) - -// // Make sure chainState exists -// if !exists(sm.store, chainStateKey) { -// return // Chain does not exist, do nothing -// } - -// // Load latest chainState -// var chainState BlockchainState -// exists, err := load(sm.store, chainStateKey, &chainState) -// if err != nil { -// sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Loading ChainState: %v", err.Error())) -// return -// } -// if !exists { -// sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Missing ChainState")) -// return -// } - -// // Check commit against last known state & validators -// err = verifyCommit(chainState, &tx.Header, &tx.Commit) -// if err != nil { -// sm.res.Code = IBCCodeInvalidCommit -// sm.res.Log = cmn.Fmt("Invalid Commit: %v", err.Error()) -// return -// } - -// // Store header -// headerKey := toKey(_IBC, _BLOCKCHAIN, _HEADER, chainID, cmn.Fmt("%v", tx.Header.Height)) -// save(sm.store, headerKey, tx.Header) - -// // Update chainState -// chainState.LastBlockHash = tx.Header.Hash() -// chainState.LastBlockHeight = uint64(tx.Header.Height) - -// // Store chainState -// save(sm.store, chainStateKey, chainState) -// } - -// func (sm *IBCStateMachine) runPacketCreateTx(tx IBCPacketCreateTx) { -// packet := tx.Packet -// packetKey := toKey(_IBC, _EGRESS, -// packet.SrcChainID, -// packet.DstChainID, -// cmn.Fmt("%v", packet.Sequence), -// ) -// // Make sure packet doesn't already exist -// if exists(sm.store, packetKey) { -// sm.res.Code = IBCCodePacketAlreadyExists -// // TODO: .AppendLog() does not update sm.res -// sm.res.Log = "Already exists" -// return -// } - -// // Execute the payload -// switch payload := tx.Packet.Payload.(type) { -// case DataPayload: -// // do nothing -// case CoinsPayload: -// // ensure enough coins were sent in tx to cover the payload coins -// if !sm.ctx.Coins.IsGTE(payload.Coins) { -// sm.res.Code = abci.CodeType_InsufficientFunds -// sm.res.Log = fmt.Sprintf("Not enough funds sent in tx (%v) to send %v via IBC", sm.ctx.Coins, payload.Coins) -// return -// } - -// // deduct coins from context -// sm.ctx.Coins = sm.ctx.Coins.Minus(payload.Coins) -// } - -// // Save new Packet -// save(sm.store, packetKey, packet) - -// // set the sequence number -// SetSequenceNumber(sm.store, packet.SrcChainID, packet.DstChainID, packet.Sequence) -// } - -// func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { -// packet := tx.Packet -// packetKeyEgress := toKey(_IBC, _EGRESS, -// packet.SrcChainID, -// packet.DstChainID, -// cmn.Fmt("%v", packet.Sequence), -// ) -// packetKeyIngress := toKey(_IBC, _INGRESS, -// packet.DstChainID, -// packet.SrcChainID, -// cmn.Fmt("%v", packet.Sequence), -// ) -// headerKey := toKey(_IBC, _BLOCKCHAIN, _HEADER, -// tx.FromChainID, -// cmn.Fmt("%v", tx.FromChainHeight), -// ) - -// // Make sure packet doesn't already exist -// if exists(sm.store, packetKeyIngress) { -// sm.res.Code = IBCCodePacketAlreadyExists -// sm.res.Log = "Already exists" -// return -// } - -// // Save new Packet (just for fun) -// save(sm.store, packetKeyIngress, packet) - -// // Load Header and make sure it exists -// // If it exists, we already checked a valid commit for it in UpdateChainTx -// var header tm.Header -// exists, err := load(sm.store, headerKey, &header) -// if err != nil { -// sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Loading Header: %v", err.Error())) -// return -// } -// if !exists { -// sm.res.Code = IBCCodeUnknownHeight -// sm.res.Log = cmn.Fmt("Loading Header: Unknown height") -// return -// } - -// proof := tx.Proof -// if proof == nil { -// sm.res.Code = IBCCodeInvalidProof -// sm.res.Log = "Proof is nil" -// return -// } -// packetBytes := wire.BinaryBytes(packet) - -// // Make sure packet's proof matches given (packet, key, blockhash) -// ok := proof.Verify(packetKeyEgress, packetBytes, header.AppHash) -// if !ok { -// sm.res.Code = IBCCodeInvalidProof -// sm.res.Log = fmt.Sprintf("Proof is invalid. key: %s; packetByes %X; header %v; proof %v", packetKeyEgress, packetBytes, header, proof) -// return -// } - -// // Execute payload -// switch payload := packet.Payload.(type) { -// case DataPayload: -// // do nothing -// case CoinsPayload: -// // Add coins to destination account -// acc := types.GetAccount(sm.store, payload.Address) -// if acc == nil { -// acc = &types.Account{} -// } -// acc.Balance = acc.Balance.Plus(payload.Coins) -// types.SetAccount(sm.store, payload.Address, acc) -// } - -// return -// } - -// func (ibc *IBCPlugin) InitChain(store state.SimpleDB, vals []*abci.Validator) { -// } - -// func (cp *IBCPlugin) BeginBlock(store state.SimpleDB, hash []byte, header *abci.Header) { -// } - -// func (cp *IBCPlugin) EndBlock(store state.SimpleDB, height uint64) (res abci.ResponseEndBlock) { -// return -// } - -// //-------------------------------------------------------------------------------- -// // TODO: move to utils - -// // Returns true if exists, false if nil. -// func exists(store state.SimpleDB, key []byte) (exists bool) { -// value := store.Get(key) -// return len(value) > 0 -// } - -// // Load bytes from store by reading value for key and read into ptr. -// // Returns true if exists, false if nil. -// // Returns err if decoding error. -// func load(store state.SimpleDB, key []byte, ptr interface{}) (exists bool, err error) { -// value := store.Get(key) -// if len(value) > 0 { -// err = wire.ReadBinaryBytes(value, ptr) -// if err != nil { -// return true, errors.New( -// cmn.Fmt("Error decoding key 0x%X = 0x%X: %v", key, value, err.Error()), -// ) -// } -// return true, nil -// } else { -// return false, nil -// } -// } - -// // Save bytes to store by writing obj's go-wire binary bytes. -// func save(store state.SimpleDB, key []byte, obj interface{}) { -// store.Set(key, wire.BinaryBytes(obj)) -// } - -// // Key parts are URL escaped and joined with ',' -// func toKey(parts ...string) []byte { -// escParts := make([]string, len(parts)) -// for i, part := range parts { -// escParts[i] = url.QueryEscape(part) -// } -// return []byte(strings.Join(escParts, ",")) -// } - -// // NOTE: Commit's votes include ValidatorAddress, so can be matched up -// // against chainState.Validators, even if the validator set had changed. -// // For the purpose of the demo, we assume that the validator set hadn't changed, -// // though we should check that explicitly. -// func verifyCommit(chainState BlockchainState, header *tm.Header, commit *tm.Commit) error { - -// // Ensure that chainState and header ChainID match. -// if chainState.ChainID != header.ChainID { -// return errors.New(cmn.Fmt("Expected header.ChainID %v, got %v", chainState.ChainID, header.ChainID)) -// } -// // Ensure things aren't empty -// if len(chainState.Validators) == 0 { -// return errors.New(cmn.Fmt("Blockchain has no validators")) // NOTE: Why would this happen? -// } -// if len(commit.Precommits) == 0 { -// return errors.New(cmn.Fmt("Commit has no signatures")) -// } -// chainID := chainState.ChainID -// vals := chainState.Validators -// valSet := tm.NewValidatorSet(vals) - -// var blockID tm.BlockID -// for _, pc := range commit.Precommits { -// // XXX: incorrect. we want the one for +2/3, not just the first one -// if pc != nil { -// blockID = pc.BlockID -// } -// } -// if blockID.IsZero() { -// return errors.New("All precommits are nil!") -// } - -// // NOTE: Currently this only works with the exact same validator set. -// // Not this, but perhaps "ValidatorSet.VerifyCommitAny" should expose -// // the functionality to verify commits even after validator changes. -// err := valSet.VerifyCommit(chainID, blockID, header.Height, commit) -// if err != nil { -// return err -// } - -// // Ensure the committed blockID matches the header -// if !bytes.Equal(header.Hash(), blockID.Hash) { -// return errors.New(cmn.Fmt("blockID.Hash (%X) does not match header.Hash (%X)", blockID.Hash, header.Hash())) -// } - -// // All ok! -// return nil -// } diff --git a/plugins/ibc/ibc_test.go b/plugins/ibc/ibc_test.go deleted file mode 100644 index 2950633c5..000000000 --- a/plugins/ibc/ibc_test.go +++ /dev/null @@ -1,512 +0,0 @@ -package ibc - -// import ( -// "bytes" -// "encoding/json" -// "sort" -// "strings" -// "testing" - -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/require" - -// abci "github.com/tendermint/abci/types" -// crypto "github.com/tendermint/go-crypto" -// "github.com/tendermint/go-wire" -// eyes "github.com/tendermint/merkleeyes/client" -// "github.com/tendermint/merkleeyes/iavl" -// cmn "github.com/tendermint/tmlibs/common" - -// "github.com/tendermint/basecoin/types" -// tm "github.com/tendermint/tendermint/types" -// ) - -// // NOTE: PrivAccounts are sorted by Address, -// // GenesisDoc, not necessarily. -// func genGenesisDoc(chainID string, numVals int) (*tm.GenesisDoc, []types.PrivAccount) { -// var privAccs []types.PrivAccount -// genDoc := &tm.GenesisDoc{ -// ChainID: chainID, -// Validators: nil, -// } - -// for i := 0; i < numVals; i++ { -// name := cmn.Fmt("%v_val_%v", chainID, i) -// privAcc := types.PrivAccountFromSecret(name) -// genDoc.Validators = append(genDoc.Validators, tm.GenesisValidator{ -// PubKey: privAcc.PubKey, -// Amount: 1, -// Name: name, -// }) -// privAccs = append(privAccs, privAcc) -// } - -// // Sort PrivAccounts -// sort.Sort(PrivAccountsByAddress(privAccs)) - -// return genDoc, privAccs -// } - -// //------------------------------------- -// // Implements sort for sorting PrivAccount by address. - -// type PrivAccountsByAddress []types.PrivAccount - -// func (pas PrivAccountsByAddress) Len() int { -// return len(pas) -// } - -// func (pas PrivAccountsByAddress) Less(i, j int) bool { -// return bytes.Compare(pas[i].Account.PubKey.Address(), pas[j].Account.PubKey.Address()) == -1 -// } - -// func (pas PrivAccountsByAddress) Swap(i, j int) { -// it := pas[i] -// pas[i] = pas[j] -// pas[j] = it -// } - -// //-------------------------------------------------------------------------------- - -// var testGenesisDoc = `{ -// "app_hash": "", -// "chain_id": "test_chain_1", -// "genesis_time": "0001-01-01T00:00:00.000Z", -// "validators": [ -// { -// "amount": 10, -// "name": "", -// "pub_key": { -// "type": "ed25519", -// "data":"D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A" -// } -// } -// ], -// "app_options": { -// "accounts": [ -// { -// "pub_key": { -// "type": "ed25519", -// "data": "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" -// }, -// "coins": [ -// { -// "denom": "mycoin", -// "amount": 9007199254740992 -// } -// ] -// } -// ] -// } -// }` - -// func TestIBCGenesisFromString(t *testing.T) { -// eyesClient := eyes.NewLocalClient("", 0) -// store := types.NewKVCache(eyesClient) -// store.SetLogging() // Log all activity - -// ibcPlugin := New() -// ctx := types.NewCallContext(nil, nil, coin.Coins{}) - -// registerChain(t, ibcPlugin, store, ctx, "test_chain", testGenesisDoc) -// } - -// //-------------------------------------------------------------------------------- - -// func TestIBCPluginRegister(t *testing.T) { -// require := require.New(t) - -// eyesClient := eyes.NewLocalClient("", 0) -// store := types.NewKVCache(eyesClient) -// store.SetLogging() // Log all activity - -// ibcPlugin := New() -// ctx := types.NewCallContext(nil, nil, coin.Coins{}) - -// chainID_1 := "test_chain" -// genDoc_1, _ := genGenesisDoc(chainID_1, 4) -// genDocJSON_1, err := json.Marshal(genDoc_1) -// require.Nil(err) - -// // Register a malformed chain -// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ -// BlockchainGenesis{ -// ChainID: "test_chain", -// Genesis: "", -// }, -// }})) -// assertAndLog(t, store, res, IBCCodeEncodingError) - -// // Successfully register a chain -// registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1)) - -// // Duplicate request fails -// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ -// BlockchainGenesis{ -// ChainID: "test_chain", -// Genesis: string(genDocJSON_1), -// }, -// }})) -// assertAndLog(t, store, res, IBCCodeChainAlreadyExists) -// } - -// func TestIBCPluginPost(t *testing.T) { -// require := require.New(t) - -// eyesClient := eyes.NewLocalClient("", 0) -// store := types.NewKVCache(eyesClient) -// store.SetLogging() // Log all activity - -// ibcPlugin := New() -// ctx := types.NewCallContext(nil, nil, coin.Coins{}) - -// chainID_1 := "test_chain" -// genDoc_1, _ := genGenesisDoc(chainID_1, 4) -// genDocJSON_1, err := json.Marshal(genDoc_1) -// require.Nil(err) - -// // Register a chain -// registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1)) - -// // Create a new packet (for testing) -// packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world"))) -// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ -// Packet: packet, -// }})) -// assertAndLog(t, store, res, abci.CodeType_OK) - -// // Post a duplicate packet -// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ -// Packet: packet, -// }})) -// assertAndLog(t, store, res, IBCCodePacketAlreadyExists) -// } - -// func TestIBCPluginPayloadBytes(t *testing.T) { -// assert := assert.New(t) -// require := require.New(t) - -// eyesClient := eyes.NewLocalClient("", 0) -// store := types.NewKVCache(eyesClient) -// store.SetLogging() // Log all activity - -// ibcPlugin := New() -// ctx := types.NewCallContext(nil, nil, coin.Coins{}) - -// chainID_1 := "test_chain" -// genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) -// genDocJSON_1, err := json.Marshal(genDoc_1) -// require.Nil(err) - -// // Register a chain -// registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1)) - -// // Create a new packet (for testing) -// packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world"))) -// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ -// Packet: packet, -// }})) -// assertAndLog(t, store, res, abci.CodeType_OK) - -// // Construct a Header that includes the above packet. -// store.Sync() -// resCommit := eyesClient.CommitSync() -// appHash := resCommit.Data -// header := newHeader("test_chain", 999, appHash, []byte("must_exist")) - -// // Construct a Commit that signs above header -// commit := constructCommit(privAccs_1, header) - -// // Update a chain -// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ -// Header: header, -// Commit: commit, -// }})) -// assertAndLog(t, store, res, abci.CodeType_OK) - -// // Get proof for the packet -// packetKey := toKey(_IBC, _EGRESS, -// packet.SrcChainID, -// packet.DstChainID, -// cmn.Fmt("%v", packet.Sequence), -// ) -// resQuery, err := eyesClient.QuerySync(abci.RequestQuery{ -// Path: "/store", -// Data: packetKey, -// Prove: true, -// }) -// assert.Nil(err) -// var proof *iavl.IAVLProof -// err = wire.ReadBinaryBytes(resQuery.Proof, &proof) -// assert.Nil(err) - -// // Post a packet -// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketPostTx{ -// FromChainID: "test_chain", -// FromChainHeight: 999, -// Packet: packet, -// Proof: proof, -// }})) -// assertAndLog(t, store, res, abci.CodeType_OK) -// } - -// func TestIBCPluginPayloadCoins(t *testing.T) { -// assert := assert.New(t) -// require := require.New(t) - -// eyesClient := eyes.NewLocalClient("", 0) -// store := types.NewKVCache(eyesClient) -// store.SetLogging() // Log all activity - -// ibcPlugin := New() -// coins := coin.Coins{ -// coin.Coin{ -// Denom: "mycoin", -// Amount: 100, -// }, -// } -// ctx := types.NewCallContext(nil, nil, coins) - -// chainID_1 := "test_chain" -// genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) -// genDocJSON_1, err := json.Marshal(genDoc_1) -// require.Nil(err) - -// // Register a chain -// registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1)) - -// // send coins to this addr on the other chain -// destinationAddr := []byte("some address") -// coinsBad := coin.Coins{coin.Coin{"mycoin", 200}} -// coinsGood := coin.Coins{coin.Coin{"mycoin", 1}} - -// // Try to send too many coins -// packet := NewPacket("test_chain", "dst_chain", 0, CoinsPayload{ -// Address: destinationAddr, -// Coins: coinsBad, -// }) -// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ -// Packet: packet, -// }})) -// assertAndLog(t, store, res, abci.CodeType_InsufficientFunds) - -// // Send a small enough number of coins -// packet = NewPacket("test_chain", "dst_chain", 0, CoinsPayload{ -// Address: destinationAddr, -// Coins: coinsGood, -// }) -// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ -// Packet: packet, -// }})) -// assertAndLog(t, store, res, abci.CodeType_OK) - -// // Construct a Header that includes the above packet. -// store.Sync() -// resCommit := eyesClient.CommitSync() -// appHash := resCommit.Data -// header := newHeader("test_chain", 999, appHash, []byte("must_exist")) - -// // Construct a Commit that signs above header -// commit := constructCommit(privAccs_1, header) - -// // Update a chain -// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ -// Header: header, -// Commit: commit, -// }})) -// assertAndLog(t, store, res, abci.CodeType_OK) - -// // Get proof for the packet -// packetKey := toKey(_IBC, _EGRESS, -// packet.SrcChainID, -// packet.DstChainID, -// cmn.Fmt("%v", packet.Sequence), -// ) -// resQuery, err := eyesClient.QuerySync(abci.RequestQuery{ -// Path: "/store", -// Data: packetKey, -// Prove: true, -// }) -// assert.Nil(err) -// var proof *iavl.IAVLProof -// err = wire.ReadBinaryBytes(resQuery.Proof, &proof) -// assert.Nil(err) - -// // Account should be empty before the tx -// acc := types.GetAccount(store, destinationAddr) -// assert.Nil(acc) - -// // Post a packet -// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketPostTx{ -// FromChainID: "test_chain", -// FromChainHeight: 999, -// Packet: packet, -// Proof: proof, -// }})) -// assertAndLog(t, store, res, abci.CodeType_OK) - -// // Account should now have some coins -// acc = types.GetAccount(store, destinationAddr) -// assert.Equal(acc.Balance, coinsGood) -// } - -// func TestIBCPluginBadCommit(t *testing.T) { -// require := require.New(t) - -// eyesClient := eyes.NewLocalClient("", 0) -// store := types.NewKVCache(eyesClient) -// store.SetLogging() // Log all activity - -// ibcPlugin := New() -// ctx := types.NewCallContext(nil, nil, coin.Coins{}) - -// chainID_1 := "test_chain" -// genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) -// genDocJSON_1, err := json.Marshal(genDoc_1) -// require.Nil(err) - -// // Successfully register a chain -// registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1)) - -// // Construct a Header -// header := newHeader("test_chain", 999, nil, []byte("must_exist")) - -// // Construct a Commit that signs above header -// commit := constructCommit(privAccs_1, header) - -// // Update a chain with a broken commit -// // Modify the first byte of the first signature -// sig := commit.Precommits[0].Signature.Unwrap().(crypto.SignatureEd25519) -// sig[0] += 1 -// commit.Precommits[0].Signature = sig.Wrap() -// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ -// Header: header, -// Commit: commit, -// }})) -// assertAndLog(t, store, res, IBCCodeInvalidCommit) - -// } - -// func TestIBCPluginBadProof(t *testing.T) { -// assert := assert.New(t) -// require := require.New(t) - -// eyesClient := eyes.NewLocalClient("", 0) -// store := types.NewKVCache(eyesClient) -// store.SetLogging() // Log all activity - -// ibcPlugin := New() -// ctx := types.NewCallContext(nil, nil, coin.Coins{}) - -// chainID_1 := "test_chain" -// genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) -// genDocJSON_1, err := json.Marshal(genDoc_1) -// require.Nil(err) - -// // Successfully register a chain -// registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1)) - -// // Create a new packet (for testing) -// packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world"))) -// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ -// Packet: packet, -// }})) -// assertAndLog(t, store, res, abci.CodeType_OK) - -// // Construct a Header that includes the above packet. -// store.Sync() -// resCommit := eyesClient.CommitSync() -// appHash := resCommit.Data -// header := newHeader("test_chain", 999, appHash, []byte("must_exist")) - -// // Construct a Commit that signs above header -// commit := constructCommit(privAccs_1, header) - -// // Update a chain -// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ -// Header: header, -// Commit: commit, -// }})) -// assertAndLog(t, store, res, abci.CodeType_OK) - -// // Get proof for the packet -// packetKey := toKey(_IBC, _EGRESS, -// packet.SrcChainID, -// packet.DstChainID, -// cmn.Fmt("%v", packet.Sequence), -// ) -// resQuery, err := eyesClient.QuerySync(abci.RequestQuery{ -// Path: "/store", -// Data: packetKey, -// Prove: true, -// }) -// assert.Nil(err) -// var proof *iavl.IAVLProof -// err = wire.ReadBinaryBytes(resQuery.Proof, &proof) -// assert.Nil(err) - -// // Mutate the proof -// proof.InnerNodes[0].Height += 1 - -// // Post a packet -// res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketPostTx{ -// FromChainID: "test_chain", -// FromChainHeight: 999, -// Packet: packet, -// Proof: proof, -// }})) -// assertAndLog(t, store, res, IBCCodeInvalidProof) -// } - -// //------------------------------------- -// // utils - -// func assertAndLog(t *testing.T, store *types.KVCache, res abci.Result, codeExpected abci.CodeType) { -// assert := assert.New(t) -// assert.Equal(codeExpected, res.Code, res.Log) -// t.Log(">>", strings.Join(store.GetLogLines(), "\n")) -// store.ClearLogLines() -// } - -// func newHeader(chainID string, height int, appHash, valHash []byte) tm.Header { -// return tm.Header{ -// ChainID: chainID, -// Height: height, -// AppHash: appHash, -// ValidatorsHash: valHash, -// } -// } - -// func registerChain(t *testing.T, ibcPlugin *IBCPlugin, store *types.KVCache, ctx types.CallContext, chainID, genDoc string) { -// res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ -// BlockchainGenesis{ -// ChainID: chainID, -// Genesis: genDoc, -// }, -// }})) -// assertAndLog(t, store, res, abci.CodeType_OK) -// } - -// func constructCommit(privAccs []types.PrivAccount, header tm.Header) tm.Commit { -// blockHash := header.Hash() -// blockID := tm.BlockID{Hash: blockHash} -// commit := tm.Commit{ -// BlockID: blockID, -// Precommits: make([]*tm.Vote, len(privAccs)), -// } -// for i, privAcc := range privAccs { -// vote := &tm.Vote{ -// ValidatorAddress: privAcc.Account.PubKey.Address(), -// ValidatorIndex: i, -// Height: 999, -// Round: 0, -// Type: tm.VoteTypePrecommit, -// BlockID: tm.BlockID{Hash: blockHash}, -// } -// vote.Signature = privAcc.PrivKey.Sign( -// tm.SignBytes("test_chain", vote), -// ) -// commit.Precommits[i] = vote -// } -// return commit -// } From 6135345af8caa76b399fa660e52679bcc18663a2 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 21 Jul 2017 20:24:53 +0200 Subject: [PATCH 32/41] Add issuer position to grant credit --- cmd/basecoin/commands/init.go | 7 ++-- modules/coin/handler.go | 62 ++++++++++++++++++++++------------- modules/coin/handler_test.go | 31 ++++++++++++++++++ modules/coin/store.go | 36 ++++++++++++++++++++ modules/coin/tx.go | 27 +++++++++++++++ 5 files changed, 139 insertions(+), 24 deletions(-) diff --git a/cmd/basecoin/commands/init.go b/cmd/basecoin/commands/init.go index ec0d62435..bd63ca2ad 100644 --- a/cmd/basecoin/commands/init.go +++ b/cmd/basecoin/commands/init.go @@ -120,7 +120,10 @@ func GetGenesisJSON(chainID, addr string) string { "amount": 9007199254740992 } ] - }] + }], + "plugin_options": [ + "coin/issuer", {"app": "sigs", "address": "%s"} + ] } -}`, chainID, addr) +}`, chainID, addr, addr) } diff --git a/modules/coin/handler.go b/modules/coin/handler.go index ffb75aa00..823bfb78a 100644 --- a/modules/coin/handler.go +++ b/modules/coin/handler.go @@ -1,8 +1,6 @@ package coin import ( - "fmt" - "github.com/tendermint/go-wire/data" "github.com/tendermint/tmlibs/log" @@ -83,7 +81,6 @@ func (h Handler) DeliverTx(ctx basecoin.Context, store state.SimpleDB, 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 @@ -117,25 +114,11 @@ func (h Handler) SetOption(l log.Logger, store state.SimpleDB, if module != NameCoin { return "", errors.ErrUnknownModule(module) } - if key == "account" { - var acc GenesisAccount - err = data.FromJSON([]byte(value), &acc) - if err != nil { - return "", err - } - acc.Balance.Sort() - addr, err := acc.GetAddr() - if err != nil { - return "", ErrInvalidAddress() - } - // this sets the permission for a public key signature, use that app - actor := auth.SigPerm(addr) - err = storeAccount(store, actor.Bytes(), acc.ToAccount()) - if err != nil { - return "", err - } - return "Success", nil - + switch key { + case "account": + return setAccount(store, value) + case "issuer": + return setIssuer(store, value) } return "", errors.ErrUnknownKey(key) } @@ -159,3 +142,38 @@ func checkTx(ctx basecoin.Context, tx basecoin.Tx) (send SendTx, err error) { } return send, nil } + +func setAccount(store state.KVStore, value string) (log string, err error) { + var acc GenesisAccount + err = data.FromJSON([]byte(value), &acc) + if err != nil { + return "", err + } + acc.Balance.Sort() + addr, err := acc.GetAddr() + if err != nil { + return "", ErrInvalidAddress() + } + // this sets the permission for a public key signature, use that app + actor := auth.SigPerm(addr) + err = storeAccount(store, actor.Bytes(), acc.ToAccount()) + if err != nil { + return "", err + } + return "Success", nil +} + +// setIssuer sets a permission for some super-powerful account to +// mint money +func setIssuer(store state.KVStore, value string) (log string, err error) { + var issuer basecoin.Actor + err = data.FromJSON([]byte(value), &issuer) + if err != nil { + return "", err + } + err = storeIssuer(store, issuer) + if err != nil { + return "", err + } + return "Success", nil +} diff --git a/modules/coin/handler_test.go b/modules/coin/handler_test.go index 1edd12d87..dc0086fd3 100644 --- a/modules/coin/handler_test.go +++ b/modules/coin/handler_test.go @@ -216,3 +216,34 @@ func TestSetOption(t *testing.T) { } } } + +func TestSetIssuer(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + cases := []struct { + issuer basecoin.Actor + }{ + {basecoin.Actor{App: "sig", Address: []byte("gwkfgk")}}, + // and set back to empty (nil is valid, but assert.Equals doesn't match) + {basecoin.Actor{Address: []byte{}}}, + {basecoin.Actor{ChainID: "other", App: "role", Address: []byte("vote")}}, + } + + h := NewHandler() + l := log.NewNopLogger() + for i, tc := range cases { + store := state.NewMemKVStore() + key := "issuer" + + value, err := json.Marshal(tc.issuer) + require.Nil(err, "%d,%d: %+v", i, err) + _, err = h.SetOption(l, store, NameCoin, key, string(value), nil) + require.Nil(err, "%+v", err) + + // check state is proper + info, err := loadHandlerInfo(store) + assert.Nil(err, "%d: %+v", i, err) + assert.Equal(tc.issuer, info.Issuer) + } +} diff --git a/modules/coin/store.go b/modules/coin/store.go index 9aa951340..58e031961 100644 --- a/modules/coin/store.go +++ b/modules/coin/store.go @@ -84,7 +84,11 @@ func updateCoins(store state.SimpleDB, addr basecoin.Actor, coins Coins) (acct A // Account - coin account structure type Account struct { + // Coins is how much is on the account Coins Coins `json:"coins"` + // Credit is how much has been "fronted" to the account + // (this is usually 0 except for trusted chains) + Credit Coins `json:"credit"` } func loadAccount(store state.SimpleDB, key []byte) (acct Account, err error) { @@ -107,3 +111,35 @@ func storeAccount(store state.SimpleDB, key []byte, acct Account) error { store.Set(key, bin) return nil // real stores can return error... } + +// HandlerInfo - this is global info on the coin handler +type HandlerInfo struct { + Issuer basecoin.Actor `json:"issuer"` +} + +// TODO: where to store these special pieces?? +var handlerKey = []byte{12, 34} + +func loadHandlerInfo(store state.KVStore) (info HandlerInfo, err error) { + data := store.Get(handlerKey) + if len(data) == 0 { + return info, nil + } + err = wire.ReadBinaryBytes(data, &info) + if err != nil { + msg := "Error reading handler info" + return info, errors.ErrInternal(msg) + } + return info, nil +} + +func storeIssuer(store state.KVStore, issuer basecoin.Actor) error { + info, err := loadHandlerInfo(store) + if err != nil { + return err + } + info.Issuer = issuer + d := wire.BinaryBytes(info) + store.Set(handlerKey, d) + return nil // real stores can return error... +} diff --git a/modules/coin/tx.go b/modules/coin/tx.go index b3598970e..9dfcffc11 100644 --- a/modules/coin/tx.go +++ b/modules/coin/tx.go @@ -157,3 +157,30 @@ func (tx SendTx) String() string { func (tx SendTx) Wrap() basecoin.Tx { return basecoin.Tx{tx} } + +//----------------------------------------------------------------------------- + +// CreditTx - this allows a special issuer to give an account credit +// Satisfies: TxInner +type CreditTx struct { + Debitor basecoin.Actor `json:"debitor"` + // Credit is the amount to change the credit... + // This may be negative to remove some over-issued credit, + // but can never bring the credit or the balance to negative + Credit Coins `json:"credit"` +} + +// NewCreditTx - modify the credit granted to a given account +func NewCreditTx(debitor basecoin.Actor, credit Coins) basecoin.Tx { + return CreditTx{Debitor: debitor, Credit: credit}.Wrap() +} + +// Wrap - used to satisfy TxInner +func (tx CreditTx) Wrap() basecoin.Tx { + return basecoin.Tx{tx} +} + +// ValidateBasic - used to satisfy TxInner +func (tx CreditTx) ValidateBasic() error { + return nil +} From 3027eeb3c33a3323c5a4c2d7dda4155f0cef6de6 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 21 Jul 2017 23:28:44 +0200 Subject: [PATCH 33/41] Add CreditTx and tests --- modules/coin/errors.go | 20 ++++-- modules/coin/handler.go | 125 ++++++++++++++++++++++++----------- modules/coin/handler_test.go | 111 ++++++++++++++++++++++++++++++- modules/coin/ibc_test.go | 2 +- modules/ibc/handler.go | 17 ++--- 5 files changed, 217 insertions(+), 58 deletions(-) diff --git a/modules/coin/errors.go b/modules/coin/errors.go index 430b59bff..39c334e8b 100644 --- a/modules/coin/errors.go +++ b/modules/coin/errors.go @@ -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) } diff --git a/modules/coin/handler.go b/modules/coin/handler.go index 823bfb78a..a38c7b6cf 100644 --- a/modules/coin/handler.go +++ b/modules/coin/handler.go @@ -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 { diff --git a/modules/coin/handler_test.go b/modules/coin/handler_test.go index dc0086fd3..b0f20c42f 100644 --- a/modules/coin/handler_test.go +++ b/modules/coin/handler_test.go @@ -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) + } + } +} diff --git a/modules/coin/ibc_test.go b/modules/coin/ibc_test.go index 29c5462e5..7b4014a32 100644 --- a/modules/coin/ibc_test.go +++ b/modules/coin/ibc_test.go @@ -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) diff --git a/modules/ibc/handler.go b/modules/ibc/handler.go index cba557970..4665f5f32 100644 --- a/modules/ibc/handler.go +++ b/modules/ibc/handler.go @@ -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) From 9640547c01fa9fd1fae4017133f2a27f81a5d692 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 21 Jul 2017 23:47:18 +0200 Subject: [PATCH 34/41] Expose credit tx to cli and test --- cmd/basecli/main.go | 1 + cmd/basecoin/commands/init.go | 2 +- modules/coin/commands/tx.go | 36 ++++++++++++++++++++++++++++++++++- modules/coin/tx.go | 10 +++++++--- modules/fee/tx.go | 2 +- tests/cli/basictx.sh | 17 +++++++++++++++++ 6 files changed, 62 insertions(+), 6 deletions(-) diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go index 490da2989..934b3df2b 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -65,6 +65,7 @@ func main() { txcmd.RootCmd.AddCommand( // This is the default transaction, optional in your app coincmd.SendTxCmd, + coincmd.CreditTxCmd, // this enables creating roles rolecmd.CreateRoleTxCmd, // these are for handling ibc diff --git a/cmd/basecoin/commands/init.go b/cmd/basecoin/commands/init.go index bd63ca2ad..ce9bcff40 100644 --- a/cmd/basecoin/commands/init.go +++ b/cmd/basecoin/commands/init.go @@ -122,7 +122,7 @@ func GetGenesisJSON(chainID, addr string) string { ] }], "plugin_options": [ - "coin/issuer", {"app": "sigs", "address": "%s"} + "coin/issuer", {"app": "sigs", "addr": "%s"} ] } }`, chainID, addr, addr) diff --git a/modules/coin/commands/tx.go b/modules/coin/commands/tx.go index 4f130ed8b..aa23fab82 100644 --- a/modules/coin/commands/tx.go +++ b/modules/coin/commands/tx.go @@ -17,6 +17,13 @@ var SendTxCmd = &cobra.Command{ RunE: commands.RequireInit(sendTxCmd), } +// CreditTxCmd is CLI command to issue credit to one account +var CreditTxCmd = &cobra.Command{ + Use: "credit", + Short: "issue credit to one account", + RunE: commands.RequireInit(creditTxCmd), +} + //nolint const ( FlagTo = "to" @@ -29,9 +36,12 @@ func init() { flags.String(FlagTo, "", "Destination address for the bits") flags.String(FlagAmount, "", "Coins to send in the format ,...") flags.String(FlagFrom, "", "Address sending coins, if not first signer") + + fs2 := CreditTxCmd.Flags() + fs2.String(FlagTo, "", "Destination address for the bits") + fs2.String(FlagAmount, "", "Coins to send in the format ,...") } -// sendTxCmd is an example of how to make a tx func sendTxCmd(cmd *cobra.Command, args []string) error { tx, err := readSendTxFlags() if err != nil { @@ -62,6 +72,30 @@ func readSendTxFlags() (tx basecoin.Tx, err error) { return } +func creditTxCmd(cmd *cobra.Command, args []string) error { + tx, err := readCreditTxFlags() + if err != nil { + return err + } + return txcmd.DoTx(tx) +} + +func readCreditTxFlags() (tx basecoin.Tx, err error) { + // parse to address + toAddr, err := commands.ParseActor(viper.GetString(FlagTo)) + if err != nil { + return tx, err + } + + amount, err := coin.ParseCoins(viper.GetString(FlagAmount)) + if err != nil { + return tx, err + } + + tx = coin.CreditTx{Debitor: toAddr, Credit: amount}.Wrap() + return +} + func readFromAddr() (basecoin.Actor, error) { from := viper.GetString(FlagFrom) if from == "" { diff --git a/modules/coin/tx.go b/modules/coin/tx.go index 9dfcffc11..bc5f77e44 100644 --- a/modules/coin/tx.go +++ b/modules/coin/tx.go @@ -7,13 +7,17 @@ import ( ) func init() { - basecoin.TxMapper.RegisterImplementation(SendTx{}, TypeSend, ByteSend) + basecoin.TxMapper. + RegisterImplementation(SendTx{}, TypeSend, ByteSend). + RegisterImplementation(CreditTx{}, TypeCredit, ByteCredit) } // we reserve the 0x20-0x3f range for standard modules const ( - ByteSend = 0x20 - TypeSend = NameCoin + "/send" + ByteSend = 0x20 + TypeSend = NameCoin + "/send" + ByteCredit = 0x21 + TypeCredit = NameCoin + "/credit" ) //----------------------------------------------------------------------------- diff --git a/modules/fee/tx.go b/modules/fee/tx.go index d2f61f732..20ce861fd 100644 --- a/modules/fee/tx.go +++ b/modules/fee/tx.go @@ -7,7 +7,7 @@ import ( // nolint const ( - ByteFees = 0x21 + ByteFees = 0x28 TypeFees = NameFee + "/tx" ) diff --git a/tests/cli/basictx.sh b/tests/cli/basictx.sh index d6ab7ea9e..dadc833c7 100755 --- a/tests/cli/basictx.sh +++ b/tests/cli/basictx.sh @@ -83,6 +83,23 @@ test02SendTxWithFee() { } +test03CreditTx() { + SENDER=$(getAddr $RICH) + RECV=$(getAddr $POOR) + + # make sure we are controlled by permissions (only rich can issue credit) + assertFalse "line=${LINENO}, bad password" "echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=1000mycoin --sequence=1 --to=$RECV --name=$POOR" + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=1000mycoin --sequence=3 --to=$RECV --name=$RICH) + txSucceeded $? "$TX" "$RECV" + HASH=$(echo $TX | jq .hash | tr -d \") + TX_HEIGHT=$(echo $TX | jq .height) + + # receiver got cash, sender didn't lose any (1000 more than last check) + checkAccount $RECV "2082" + checkAccount $SENDER "9007199254739900" +} + + # Load common then run these tests with shunit2! DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory . $DIR/common.sh From 95b16b38305eb137ed30f8d3c9d5528bbbd1a788 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Sat, 22 Jul 2017 06:06:14 -0400 Subject: [PATCH 35/41] Code cleanup from emmanuels comment --- client/commands/seeds/export.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/client/commands/seeds/export.go b/client/commands/seeds/export.go index babd1748a..b37c11bfb 100644 --- a/client/commands/seeds/export.go +++ b/client/commands/seeds/export.go @@ -48,16 +48,12 @@ func exportSeed(cmd *cobra.Command, args []string) error { } func writeSeed(seed certifiers.Seed, path string) (err error) { - var f *os.File - f, err = os.Create(path) - if err == nil { - stream := json.NewEncoder(f) - err = stream.Encode(seed) - f.Close() - } - // we don't write, but this is not an error - if os.IsExist(err) { - return nil + f, err := os.Create(path) + if err != nil { + return errors.WithStack(err) } + defer f.Close() + stream := json.NewEncoder(f) + err = stream.Encode(seed) return errors.WithStack(err) } From b7abee64f0893fdf41cab3dcf62075d9b150cdf1 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Sat, 22 Jul 2017 08:09:42 -0400 Subject: [PATCH 36/41] Test creating packet and query via cli --- modules/coin/handler.go | 8 +++++- modules/ibc/commands/query.go | 2 +- tests/cli/ibc.sh | 54 +++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/modules/coin/handler.go b/modules/coin/handler.go index a38c7b6cf..e22dacd26 100644 --- a/modules/coin/handler.go +++ b/modules/coin/handler.go @@ -118,7 +118,13 @@ func (h Handler) sendTx(ctx basecoin.Context, store state.SimpleDB, 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}) + inputs := make([]TxInput, len(send.Inputs)) + for i := range send.Inputs { + inputs[i] = send.Inputs[i] + inputs[i].Address = inputs[i].Address.WithChain(ctx.ChainID()) + } + + outTx := NewSendTx(inputs, []TxOutput{out}) packet := ibc.CreatePacketTx{ DestChain: out.Address.ChainID, Permissions: senders, diff --git a/modules/ibc/commands/query.go b/modules/ibc/commands/query.go index fed670fd1..67e9430c7 100644 --- a/modules/ibc/commands/query.go +++ b/modules/ibc/commands/query.go @@ -73,7 +73,7 @@ func init() { fs2 := PacketQueryCmd.Flags() fs2.String(FlagFromChain, "", "Name of the input chain (where packets came from)") fs2.String(FlagToChain, "", "Name of the output chain (where packets go to)") - fs2.Int(FlagSequence, -1, "Name of the output chain (where packets go to)") + fs2.Int(FlagSequence, -1, "Index of the packet in the queue (starts with 0)") } func ibcQueryCmd(cmd *cobra.Command, args []string) error { diff --git a/tests/cli/ibc.sh b/tests/cli/ibc.sh index 252c297ee..8fcad2814 100755 --- a/tests/cli/ibc.sh +++ b/tests/cli/ibc.sh @@ -162,6 +162,60 @@ test03QueryIBC() { assertEquals "line=${LINENO}, tracked height" $UPDATE_2_HEIGHT $(echo $CHAIN_INFO | jq .data.remote_block) } +# Trigger a cross-chain sendTx... from RICH on chain1 to POOR on chain2 +# we make sure the money was reduced, but nothing arrived +test04SendIBCPacket() { + export BC_HOME=${CLIENT_1} + + # make sure there are no packets yet + PACKETS=$(${CLIENT_EXE} query ibc packets --to=$CHAIN_ID_2 2>/dev/null) + assertFalse "line=${LINENO}, packet query" $? + + SENDER=$(getAddr $RICH) + RECV=$(BC_HOME=${CLIENT_2} getAddr $POOR) + + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=20002mycoin \ + --to=${CHAIN_ID_2}::${RECV} --name=$RICH) + txSucceeded $? "$TX" "${CHAIN_ID_2}::${RECV}" + # quit early if there is no point in more tests + if [ $? != 0 ]; then echo "aborting!"; return 1; fi + + HASH=$(echo $TX | jq .hash | tr -d \") + TX_HEIGHT=$(echo $TX | jq .height) + + # Make sure balance went down and tx is indexed + checkAccount $SENDER "9007199254720990" + checkSendTx $HASH $TX_HEIGHT $SENDER "20002" + + # look, we wrote a packet + PACKETS=$(${CLIENT_EXE} query ibc packets --to=$CHAIN_ID_2) + assertTrue "line=${LINENO}, packets query" $? + assertEquals "line=${LINENO}, packet count" 1 $(echo $PACKETS | jq .data) + + # and look at the packet itself + PACKET=$(${CLIENT_EXE} query ibc packet --to=$CHAIN_ID_2 --sequence=0) + assertTrue "line=${LINENO}, packet query" $? + echo $PACKET | jq . + + # nothing arrived + # look, we wrote a packet + ARRIVED=$(${CLIENT_EXE} query ibc packets --from=$CHAIN_ID_1 --home=$CLIENT_2 2>/dev/null) + assertFalse "line=${LINENO}, packet query" $? + assertFalse "line=${LINENO}, no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV" +} + +test05ReceiveIBCPacket() { + export BC_HOME=${CLIENT_2} + + # make some credit, so we can accept the packet + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=60006mycoin --to=$CHAIN_1:: --name=$RICH) + txSucceeded $? "$TX" "${CHAIN_ID_2}::${RECV}" + checkAccount $CHAIN_2:: "60006" + + # now, we try to post it.... +} + + # XXX Ex Usage: assertNewHeight $MSG $SEED_1 $SEED_2 # Desc: Asserts that seed2 has a higher block height than seed 1 assertNewHeight() { From d0920ac1cffc7264b0231410e3c501d3760e0930 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Sat, 22 Jul 2017 09:09:30 -0400 Subject: [PATCH 37/41] Add post packet to cli and test... bug --- cmd/basecli/main.go | 1 + modules/ibc/commands/query.go | 45 ++++++++++++++++++++++-- modules/ibc/commands/tx.go | 66 ++++++++++++++++++++--------------- modules/ibc/ibc_test.go | 15 ++++---- modules/ibc/middleware.go | 9 ----- modules/ibc/tx.go | 14 ++++---- stack/context.go | 4 ++- tests/cli/ibc.sh | 35 +++++++++++++++++-- 8 files changed, 129 insertions(+), 60 deletions(-) diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go index 934b3df2b..6813dd916 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -71,6 +71,7 @@ func main() { // these are for handling ibc ibccmd.RegisterChainTxCmd, ibccmd.UpdateChainTxCmd, + ibccmd.PostPacketTxCmd, ) // Set up the various commands to use diff --git a/modules/ibc/commands/query.go b/modules/ibc/commands/query.go index 67e9430c7..80bb6c752 100644 --- a/modules/ibc/commands/query.go +++ b/modules/ibc/commands/query.go @@ -1,6 +1,8 @@ package commands import ( + "fmt" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -9,6 +11,9 @@ import ( proofcmd "github.com/tendermint/basecoin/client/commands/proofs" "github.com/tendermint/basecoin/modules/ibc" "github.com/tendermint/basecoin/stack" + "github.com/tendermint/go-wire/data" + "github.com/tendermint/light-client/proofs" + "github.com/tendermint/merkleeyes/iavl" ) // IBCQueryCmd - parent command to query ibc info @@ -175,11 +180,45 @@ func packetQueryCmd(cmd *cobra.Command, args []string) error { key = stack.PrefixedKey(ibc.NameIBC, ibc.QueueOutPacketKey(to, uint64(seq))) } - var res ibc.Packet - proof, err := proofcmd.GetAndParseAppProof(key, &res) + // Input queue just display the results + if from != "" { + var packet ibc.Packet + proof, err := proofcmd.GetAndParseAppProof(key, &packet) + if err != nil { + return err + } + return proofcmd.OutputProof(packet, proof.BlockHeight()) + } + + // output queue, create a post packet + var packet ibc.Packet + proof, err := proofcmd.GetAndParseAppProof(key, &packet) if err != nil { return err } - return proofcmd.OutputProof(res, proof.BlockHeight()) + // TODO: oh so ugly. fix before merge! + // wait, i want to change go-merkle too.... + appProof := proof.(proofs.AppProof) + extractedProof, err := iavl.ReadProof(appProof.Proof) + if err != nil { + return err + } + + // create the post packet here. + post := ibc.PostPacketTx{ + FromChainID: commands.GetChainID(), + FromChainHeight: proof.BlockHeight(), + Key: key, + Packet: packet, + Proof: extractedProof, + } + + // print json direct, as we don't need to wrap with the height + res, err := data.ToJSON(post) + if err != nil { + return err + } + fmt.Println(string(res)) + return nil } diff --git a/modules/ibc/commands/tx.go b/modules/ibc/commands/tx.go index 608082bb1..475d35809 100644 --- a/modules/ibc/commands/tx.go +++ b/modules/ibc/commands/tx.go @@ -28,13 +28,19 @@ var UpdateChainTxCmd = &cobra.Command{ RunE: commands.RequireInit(updateChainTxCmd), } -// TODO: post packet (query and all that jazz) +// PostPacketTxCmd is CLI command to post ibc packet on the destination chain +var PostPacketTxCmd = &cobra.Command{ + Use: "ibc-post", + Short: "Post an ibc packet on the destination chain", + RunE: commands.RequireInit(postPacketTxCmd), +} // TODO: relay! //nolint const ( - FlagSeed = "seed" + FlagSeed = "seed" + FlagPacket = "packet" ) func init() { @@ -43,6 +49,9 @@ func init() { fs2 := UpdateChainTxCmd.Flags() fs2.String(FlagSeed, "", "Filename with a seed file") + + fs3 := PostPacketTxCmd.Flags() + fs3.String(FlagPacket, "", "Filename with a packet to post") } func registerChainTxCmd(cmd *cobra.Command, args []string) error { @@ -63,45 +72,44 @@ func updateChainTxCmd(cmd *cobra.Command, args []string) error { return txcmd.DoTx(tx) } +func postPacketTxCmd(cmd *cobra.Command, args []string) error { + post, err := readPostPacket() + if err != nil { + return err + } + return txcmd.DoTx(post.Wrap()) +} + func readSeed() (seed certifiers.Seed, err error) { name := viper.GetString(FlagSeed) if name == "" { return seed, errors.New("You must specify a seed file") } + err = readFile(name, &seed) + return +} + +func readPostPacket() (post ibc.PostPacketTx, err error) { + name := viper.GetString(FlagPacket) + if name == "" { + return post, errors.New("You must specify a packet file") + } + + err = readFile(name, &post) + return +} + +func readFile(name string, input interface{}) (err error) { var f *os.File f, err = os.Open(name) if err != nil { - return seed, errors.Wrap(err, "Cannot read seed file") + return errors.WithStack(err) } defer f.Close() // read the file as json into a seed j := json.NewDecoder(f) - err = j.Decode(&seed) - err = errors.Wrap(err, "Invalid seed file") - return + err = j.Decode(input) + return errors.Wrap(err, "Invalid file") } - -// func readCreateRoleTxFlags() (tx basecoin.Tx, err error) { -// role, err := parseRole(viper.GetString(FlagRole)) -// if err != nil { -// return tx, err -// } - -// sigs := viper.GetInt(FlagMinSigs) -// if sigs < 1 { -// return tx, errors.Errorf("--%s must be at least 1", FlagMinSigs) -// } - -// signers, err := commands.ParseActors(viper.GetString(FlagMembers)) -// if err != nil { -// return tx, err -// } -// if len(signers) == 0 { -// return tx, errors.New("must specify at least one member") -// } - -// tx = roles.NewCreateRoleTx(role, uint32(sigs), signers) -// return tx, nil -// } diff --git a/modules/ibc/ibc_test.go b/modules/ibc/ibc_test.go index 0d830f534..c31aeae53 100644 --- a/modules/ibc/ibc_test.go +++ b/modules/ibc/ibc_test.go @@ -381,29 +381,26 @@ func TestIBCPostPacket(t *testing.T) { // bad chain -> error {packetBad, ibcPerm, IsNotRegisteredErr}, - // invalid permissions -> error - {packet0, nil, IsNeedsIBCPermissionErr}, - // no matching header -> error - {packet0badHeight, ibcPerm, IsHeaderNotFoundErr}, + {packet0badHeight, nil, IsHeaderNotFoundErr}, // out of order -> error {packet1, ibcPerm, IsPacketOutOfOrderErr}, - // all good -> execute tx } + // all good -> execute tx {packet0, ibcPerm, errors.NoErr}, // bad proof -> error {packet1badProof, ibcPerm, IsInvalidProofErr}, - // all good -> execute tx } - {packet1, ibcPerm, errors.NoErr}, + // all good -> execute tx (no special permission needed) + {packet1, nil, errors.NoErr}, // repeat -> error - {packet0, ibcPerm, IsPacketAlreadyExistsErr}, + {packet0, nil, IsPacketAlreadyExistsErr}, // packet2 contains invalid permissions - {packet2, ibcPerm, IsCannotSetPermissionErr}, + {packet2, nil, IsCannotSetPermissionErr}, } for i, tc := range cases { diff --git a/modules/ibc/middleware.go b/modules/ibc/middleware.go index 7852bf1e5..fe6176964 100644 --- a/modules/ibc/middleware.go +++ b/modules/ibc/middleware.go @@ -80,15 +80,6 @@ func (m Middleware) verifyPost(ctx basecoin.Context, store state.SimpleDB, return ictx, itx, ErrCannotSetPermission() } - // make sure it has AllowIBC - mod, err := packet.Tx.GetMod() - if err != nil { - return ictx, itx, err - } - if !ctx.HasPermission(AllowIBC(mod)) { - return ictx, itx, ErrNeedsIBCPermission() - } - // make sure this sequence number is the next in the list q := InputQueue(store, from) tail := q.Tail() diff --git a/modules/ibc/tx.go b/modules/ibc/tx.go index 48a09531a..8ee4d934c 100644 --- a/modules/ibc/tx.go +++ b/modules/ibc/tx.go @@ -1,6 +1,7 @@ package ibc import ( + "github.com/tendermint/go-wire/data" "github.com/tendermint/light-client/certifiers" merkle "github.com/tendermint/merkleeyes/iavl" @@ -109,13 +110,14 @@ func (p CreatePacketTx) Wrap() basecoin.Tx { // right now, enforce that these packets are only sent directly, // not routed over the hub. add routing later. type PostPacketTx struct { - // make sure we have this header... - FromChainID string // The immediate source of the packet, not always Packet.SrcChainID - FromChainHeight uint64 // The block height in which Packet was committed, to check Proof + // The immediate source of the packet, not always Packet.SrcChainID + FromChainID string `json:"src_chain"` + // The block height in which Packet was committed, to check Proof + FromChainHeight uint64 `json:"src_height"` // this proof must match the header and the packet.Bytes() - Proof *merkle.IAVLProof - Key []byte - Packet Packet + Proof *merkle.IAVLProof `json:"proof"` + Key data.Bytes `json:"key"` + Packet Packet `json:"packet"` } // ValidateBasic makes sure this is consistent - used to satisfy TxInner diff --git a/stack/context.go b/stack/context.go index 9e3c247e8..7359adbe1 100644 --- a/stack/context.go +++ b/stack/context.go @@ -21,8 +21,10 @@ type secureContext struct { // NewContext - create a new secureContext func NewContext(chain string, height uint64, logger log.Logger) basecoin.Context { + mock := MockContext(chain, height).(naiveContext) + mock.Logger = logger return secureContext{ - naiveContext: MockContext(chain, height).(naiveContext), + naiveContext: mock, } } diff --git a/tests/cli/ibc.sh b/tests/cli/ibc.sh index 8fcad2814..876d0675d 100755 --- a/tests/cli/ibc.sh +++ b/tests/cli/ibc.sh @@ -195,10 +195,11 @@ test04SendIBCPacket() { # and look at the packet itself PACKET=$(${CLIENT_EXE} query ibc packet --to=$CHAIN_ID_2 --sequence=0) assertTrue "line=${LINENO}, packet query" $? - echo $PACKET | jq . + assertEquals "line=${LINENO}, proper src" "\"$CHAIN_ID_1\"" $(echo $PACKET | jq .src_chain) + assertEquals "line=${LINENO}, proper dest" "\"$CHAIN_ID_2\"" $(echo $PACKET | jq .packet.dest_chain) + assertEquals "line=${LINENO}, proper sequence" "0" $(echo $PACKET | jq .packet.sequence) # nothing arrived - # look, we wrote a packet ARRIVED=$(${CLIENT_EXE} query ibc packets --from=$CHAIN_ID_1 --home=$CLIENT_2 2>/dev/null) assertFalse "line=${LINENO}, packet query" $? assertFalse "line=${LINENO}, no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV" @@ -212,7 +213,35 @@ test05ReceiveIBCPacket() { txSucceeded $? "$TX" "${CHAIN_ID_2}::${RECV}" checkAccount $CHAIN_2:: "60006" - # now, we try to post it.... + # now, we try to post it.... (this is PACKET from last test) + + # get the seed and post it + SRC_HEIGHT=$(echo $PACKET | jq .src_height) + PACKET_SEED="$BASE_DIR_1/packet_seed.json" + ${CLIENT_EXE} seeds export $PACKET_SEED --home=${CLIENT_1} --height=$SRC_HEIGHT + assertTrue "line=${LINENO}, export seed failed" $? + + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \ + --seed=${PACKET_SEED} --name=$POOR) + txSucceeded $? "$TX" "prepare packet chain1 on chain 2" + # an example to quit early if there is no point in more tests + if [ $? != 0 ]; then echo "aborting!"; return 1; fi + + # write the packet to the file + POST_PACKET="$BASE_DIR_1/post_packet.json" + echo $PACKET > $POST_PACKET + + # post it as a tx (cross-fingers) + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-post \ + --packet=${POST_PACKET} --name=$POOR) + txSucceeded $? "$TX" "post packet from chain1 on chain 2" + + # TODO: more queries on stuff... + + # look, we wrote a packet + PACKETS=$(${CLIENT_EXE} query ibc packets --from=$CHAIN_ID_1) + assertTrue "line=${LINENO}, packets query" $? + assertEquals "line=${LINENO}, packet count" 1 $(echo $PACKETS | jq .data) } From e90d6db516edb6699dbccdea7769550cf8312075 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Sat, 22 Jul 2017 09:51:14 -0400 Subject: [PATCH 38/41] Update seed to a given height --- client/commands/seeds/update.go | 16 ++++++-- modules/ibc/commands/query.go | 2 + tests/cli/ibc.sh | 68 ++++++++++++++++++--------------- 3 files changed, 52 insertions(+), 34 deletions(-) diff --git a/client/commands/seeds/update.go b/client/commands/seeds/update.go index 153f090f8..a3be67ace 100644 --- a/client/commands/seeds/update.go +++ b/client/commands/seeds/update.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/tendermint/light-client/certifiers" @@ -12,12 +13,13 @@ import ( var updateCmd = &cobra.Command{ Use: "update", - Short: "Update seed to current chain state if possible", + Short: "Update seed to current height if possible", RunE: commands.RequireInit(updateSeed), SilenceUsage: true, } func init() { + updateCmd.Flags().Int(heightFlag, 0, "Update to this height, not latest") RootCmd.AddCommand(updateCmd) } @@ -27,14 +29,20 @@ func updateSeed(cmd *cobra.Command, args []string) error { return err } - // get the lastest from our source - seed, err := certifiers.LatestSeed(cert.SeedSource) + h := viper.GetInt(heightFlag) + var seed certifiers.Seed + if h <= 0 { + // get the lastest from our source + seed, err = certifiers.LatestSeed(cert.SeedSource) + } else { + seed, err = cert.SeedSource.GetByHeight(h) + } if err != nil { return err } - fmt.Printf("Trying to update to height: %d...\n", seed.Height()) // let the certifier do it's magic to update.... + fmt.Printf("Trying to update to height: %d...\n", seed.Height()) err = cert.Update(seed.Checkpoint, seed.Validators) if err != nil { return err diff --git a/modules/ibc/commands/query.go b/modules/ibc/commands/query.go index 80bb6c752..38c3d01e1 100644 --- a/modules/ibc/commands/query.go +++ b/modules/ibc/commands/query.go @@ -16,6 +16,8 @@ import ( "github.com/tendermint/merkleeyes/iavl" ) +// TODO: query seeds (register/update) + // IBCQueryCmd - parent command to query ibc info var IBCQueryCmd = &cobra.Command{ Use: "ibc", diff --git a/tests/cli/ibc.sh b/tests/cli/ibc.sh index 876d0675d..c9a86382a 100755 --- a/tests/cli/ibc.sh +++ b/tests/cli/ibc.sh @@ -205,44 +205,52 @@ test04SendIBCPacket() { assertFalse "line=${LINENO}, no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV" } -test05ReceiveIBCPacket() { - export BC_HOME=${CLIENT_2} +# test05ReceiveIBCPacket() { +# export BC_HOME=${CLIENT_2} - # make some credit, so we can accept the packet - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=60006mycoin --to=$CHAIN_1:: --name=$RICH) - txSucceeded $? "$TX" "${CHAIN_ID_2}::${RECV}" - checkAccount $CHAIN_2:: "60006" +# # make some credit, so we can accept the packet +# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=60006mycoin --to=$CHAIN_1:: --name=$RICH) +# txSucceeded $? "$TX" "${CHAIN_ID_2}::${RECV}" +# checkAccount $CHAIN_2:: "60006" - # now, we try to post it.... (this is PACKET from last test) +# # now, we try to post it.... (this is PACKET from last test) - # get the seed and post it - SRC_HEIGHT=$(echo $PACKET | jq .src_height) - PACKET_SEED="$BASE_DIR_1/packet_seed.json" - ${CLIENT_EXE} seeds export $PACKET_SEED --home=${CLIENT_1} --height=$SRC_HEIGHT - assertTrue "line=${LINENO}, export seed failed" $? +# # get the seed and post it +# SRC_HEIGHT=$(echo $PACKET | jq .src_height) +# # FIXME: this should auto-update on proofs... +# ${CLIENT_EXE} seeds update --height=$SRC_HEIGHT --home=${CLIENT_1} > /dev/null +# assertTrue "line=${LINENO}, update seed failed" $? - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \ - --seed=${PACKET_SEED} --name=$POOR) - txSucceeded $? "$TX" "prepare packet chain1 on chain 2" - # an example to quit early if there is no point in more tests - if [ $? != 0 ]; then echo "aborting!"; return 1; fi +# PACKET_SEED="$BASE_DIR_1/packet_seed.json" +# ${CLIENT_EXE} seeds export $PACKET_SEED --home=${CLIENT_1} #--height=$SRC_HEIGHT +# assertTrue "line=${LINENO}, export seed failed" $? +# echo "**** SEED ****" +# cat $PACKET_SEED | jq . - # write the packet to the file - POST_PACKET="$BASE_DIR_1/post_packet.json" - echo $PACKET > $POST_PACKET +# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \ +# --seed=${PACKET_SEED} --name=$POOR) +# txSucceeded $? "$TX" "prepare packet chain1 on chain 2" +# # an example to quit early if there is no point in more tests +# if [ $? != 0 ]; then echo "aborting!"; return 1; fi - # post it as a tx (cross-fingers) - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-post \ - --packet=${POST_PACKET} --name=$POOR) - txSucceeded $? "$TX" "post packet from chain1 on chain 2" +# # write the packet to the file +# POST_PACKET="$BASE_DIR_1/post_packet.json" +# echo $PACKET > $POST_PACKET +# echo "**** POST ****" +# cat $POST_PACKET | jq . - # TODO: more queries on stuff... +# # post it as a tx (cross-fingers) +# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-post \ +# --packet=${POST_PACKET} --name=$POOR) +# txSucceeded $? "$TX" "post packet from chain1 on chain 2" - # look, we wrote a packet - PACKETS=$(${CLIENT_EXE} query ibc packets --from=$CHAIN_ID_1) - assertTrue "line=${LINENO}, packets query" $? - assertEquals "line=${LINENO}, packet count" 1 $(echo $PACKETS | jq .data) -} +# # TODO: more queries on stuff... + +# # look, we wrote a packet +# PACKETS=$(${CLIENT_EXE} query ibc packets --from=$CHAIN_ID_1) +# assertTrue "line=${LINENO}, packets query" $? +# assertEquals "line=${LINENO}, packet count" 1 $(echo $PACKETS | jq .data) +# } # XXX Ex Usage: assertNewHeight $MSG $SEED_1 $SEED_2 From f32e6c9b7d22de48f9b94c24bbf23781fe986608 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 24 Jul 2017 10:31:51 -0400 Subject: [PATCH 39/41] Updated light-client --- client/commands/common.go | 2 +- glide.lock | 9 +++++---- modules/ibc/provider.go | 2 +- tests/cli/rpc.sh | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/client/commands/common.go b/client/commands/common.go index a7ea8da7d..4bb565b6f 100644 --- a/client/commands/common.go +++ b/client/commands/common.go @@ -79,7 +79,7 @@ func GetCertifier() (*certifiers.InquiringCertifier, error) { return nil, err } cert := certifiers.NewInquiring( - viper.GetString(ChainFlag), seed.Validators, trust, source) + viper.GetString(ChainFlag), seed, trust, source) return cert, nil } diff --git a/glide.lock b/glide.lock index f68a89d0c..b13de0224 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 45eed61138603d4d03518ea822068cf32b45d0a219bb7f3b836e52129f2a3a2b -updated: 2017-07-26T19:44:39.753066441-04:00 +hash: 8c438edb7d269da439141e62f3e0c931fa9efaee54b13ce1e7330dc99179fddd +updated: 2017-07-24T10:22:44.120576373-04:00 imports: - name: github.com/bgentry/speakeasy version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd @@ -133,15 +133,16 @@ imports: - data - data/base58 - name: github.com/tendermint/light-client - version: 1c53d04dcc65c2fd15526152ed0651af10a09982 + version: fd166a86ccc2e4d2abcf2e765e70f341b7676d89 subpackages: - certifiers - certifiers/client - certifiers/files - proofs - name: github.com/tendermint/merkleeyes - version: 0310013053953eef80def3619aeb1e3a3254f452 + version: 439776e0199f1812e132aa7b97463e8550906690 subpackages: + - app - client - iavl - name: github.com/tendermint/tendermint diff --git a/modules/ibc/provider.go b/modules/ibc/provider.go index 7ff8858d8..d5fb877da 100644 --- a/modules/ibc/provider.go +++ b/modules/ibc/provider.go @@ -35,7 +35,7 @@ func newCertifier(store state.SimpleDB, chainID string, h int) (*certifiers.Inqu } // we have no source for untrusted keys, but use the db to load trusted history - cert := certifiers.NewInquiring(chainID, seed.Validators, p, + cert := certifiers.NewInquiring(chainID, seed, p, certifiers.MissingProvider{}) return cert, nil } diff --git a/tests/cli/rpc.sh b/tests/cli/rpc.sh index 1026ee890..0a1575d10 100755 --- a/tests/cli/rpc.sh +++ b/tests/cli/rpc.sh @@ -98,7 +98,7 @@ test02GetSecure() { # assertFalse "missing height" "${CLIENT_EXE} rpc headers" HEADERS=$(${CLIENT_EXE} rpc headers --min=$CHEIGHT --max=$HEIGHT) assertTrue "line=${LINENO}, get headers" "$?" - assertEquals "line=${LINENO}, proper height" "$HEIGHT" $(echo $HEADERS | jq '.last_height') + assertEquals "line=${LINENO}, proper height" "$HEIGHT" $(echo $HEADERS | jq '.block_metas[0].header.height') assertEquals "line=${LINENO}, two headers" "2" $(echo $HEADERS | jq '.block_metas | length') # should we check these headers? CHEAD=$(echo $COMMIT | jq .header) From 6632d88b3d3a596f7718cd5a41cb6ceabfabcd95 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 24 Jul 2017 10:55:06 -0400 Subject: [PATCH 40/41] IBC post packet test passes --- glide.lock | 2 +- modules/coin/commands/query.go | 1 + tests/cli/ibc.sh | 74 +++++++++++++++++----------------- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/glide.lock b/glide.lock index b13de0224..a586e1436 100644 --- a/glide.lock +++ b/glide.lock @@ -133,7 +133,7 @@ imports: - data - data/base58 - name: github.com/tendermint/light-client - version: fd166a86ccc2e4d2abcf2e765e70f341b7676d89 + version: fcf4e411583135a1900157b8b0274c41e20ea3a1 subpackages: - certifiers - certifiers/client diff --git a/modules/coin/commands/query.go b/modules/coin/commands/query.go index e77614bea..1faabb621 100644 --- a/modules/coin/commands/query.go +++ b/modules/coin/commands/query.go @@ -28,6 +28,7 @@ func accountQueryCmd(cmd *cobra.Command, args []string) error { if err != nil { return err } + act = coin.ChainAddr(act) key := stack.PrefixedKey(coin.NameCoin, act.Bytes()) acc := coin.Account{} diff --git a/tests/cli/ibc.sh b/tests/cli/ibc.sh index c9a86382a..849516cf2 100755 --- a/tests/cli/ibc.sh +++ b/tests/cli/ibc.sh @@ -205,52 +205,52 @@ test04SendIBCPacket() { assertFalse "line=${LINENO}, no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV" } -# test05ReceiveIBCPacket() { -# export BC_HOME=${CLIENT_2} +test05ReceiveIBCPacket() { + export BC_HOME=${CLIENT_2} -# # make some credit, so we can accept the packet -# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=60006mycoin --to=$CHAIN_1:: --name=$RICH) -# txSucceeded $? "$TX" "${CHAIN_ID_2}::${RECV}" -# checkAccount $CHAIN_2:: "60006" + # make some credit, so we can accept the packet + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx credit --amount=60006mycoin --to=$CHAIN_ID_1:: --name=$RICH) + txSucceeded $? "$TX" "${CHAIN_ID_1}::" + checkAccount $CHAIN_ID_1:: "60006" -# # now, we try to post it.... (this is PACKET from last test) + # now, we try to post it.... (this is PACKET from last test) -# # get the seed and post it -# SRC_HEIGHT=$(echo $PACKET | jq .src_height) -# # FIXME: this should auto-update on proofs... -# ${CLIENT_EXE} seeds update --height=$SRC_HEIGHT --home=${CLIENT_1} > /dev/null -# assertTrue "line=${LINENO}, update seed failed" $? + # get the seed and post it + SRC_HEIGHT=$(echo $PACKET | jq .src_height) + # FIXME: this should auto-update on proofs... + ${CLIENT_EXE} seeds update --height=$SRC_HEIGHT --home=${CLIENT_1} > /dev/null + assertTrue "line=${LINENO}, update seed failed" $? -# PACKET_SEED="$BASE_DIR_1/packet_seed.json" -# ${CLIENT_EXE} seeds export $PACKET_SEED --home=${CLIENT_1} #--height=$SRC_HEIGHT -# assertTrue "line=${LINENO}, export seed failed" $? -# echo "**** SEED ****" -# cat $PACKET_SEED | jq . + PACKET_SEED="$BASE_DIR_1/packet_seed.json" + ${CLIENT_EXE} seeds export $PACKET_SEED --home=${CLIENT_1} #--height=$SRC_HEIGHT + assertTrue "line=${LINENO}, export seed failed" $? + # echo "**** SEED ****" + # cat $PACKET_SEED | jq . -# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \ -# --seed=${PACKET_SEED} --name=$POOR) -# txSucceeded $? "$TX" "prepare packet chain1 on chain 2" -# # an example to quit early if there is no point in more tests -# if [ $? != 0 ]; then echo "aborting!"; return 1; fi + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-update \ + --seed=${PACKET_SEED} --name=$POOR) + txSucceeded $? "$TX" "prepare packet chain1 on chain 2" + # an example to quit early if there is no point in more tests + if [ $? != 0 ]; then echo "aborting!"; return 1; fi -# # write the packet to the file -# POST_PACKET="$BASE_DIR_1/post_packet.json" -# echo $PACKET > $POST_PACKET -# echo "**** POST ****" -# cat $POST_PACKET | jq . + # write the packet to the file + POST_PACKET="$BASE_DIR_1/post_packet.json" + echo $PACKET > $POST_PACKET + # echo "**** POST ****" + # cat $POST_PACKET | jq . -# # post it as a tx (cross-fingers) -# TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-post \ -# --packet=${POST_PACKET} --name=$POOR) -# txSucceeded $? "$TX" "post packet from chain1 on chain 2" + # post it as a tx (cross-fingers) + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx ibc-post \ + --packet=${POST_PACKET} --name=$POOR) + txSucceeded $? "$TX" "post packet from chain1 on chain 2" -# # TODO: more queries on stuff... + # TODO: more queries on stuff... -# # look, we wrote a packet -# PACKETS=$(${CLIENT_EXE} query ibc packets --from=$CHAIN_ID_1) -# assertTrue "line=${LINENO}, packets query" $? -# assertEquals "line=${LINENO}, packet count" 1 $(echo $PACKETS | jq .data) -# } + # look, we wrote a packet + PACKETS=$(${CLIENT_EXE} query ibc packets --from=$CHAIN_ID_1) + assertTrue "line=${LINENO}, packets query" $? + assertEquals "line=${LINENO}, packet count" 1 $(echo $PACKETS | jq .data) +} # XXX Ex Usage: assertNewHeight $MSG $SEED_1 $SEED_2 From bd14f0bfb915e19161418f7bd1bbb5c45782e679 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 27 Jul 2017 16:49:22 -0400 Subject: [PATCH 41/41] Fix up imports after megre --- benchmarks/app_test.go | 2 +- glide.lock | 11 ++++++----- glide.yaml | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/benchmarks/app_test.go b/benchmarks/app_test.go index 4d0c228f8..96487afe0 100644 --- a/benchmarks/app_test.go +++ b/benchmarks/app_test.go @@ -32,7 +32,7 @@ func DefaultHandler(feeDenom string) basecoin.Handler { c := coin.NewHandler() r := roles.NewHandler() d := stack.NewDispatcher( - stack.WrapHandler(c), + c, stack.WrapHandler(r), ) return stack.New( diff --git a/glide.lock b/glide.lock index a586e1436..5071a9ab7 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 8c438edb7d269da439141e62f3e0c931fa9efaee54b13ce1e7330dc99179fddd -updated: 2017-07-24T10:22:44.120576373-04:00 +hash: 2848c30b31fb205f846dd7dfca14ebed8a3249cbc5aaa759066b2bab3e4bbf42 +updated: 2017-07-27T16:46:31.962147949-04:00 imports: - name: github.com/bgentry/speakeasy version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd @@ -57,6 +57,8 @@ imports: - json/parser - json/scanner - json/token +- name: github.com/howeyc/crc16 + version: 96a97a1abb579c7ff1a8ffa77f2e72d1c314b57f - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 - name: github.com/jmhodges/levigo @@ -117,7 +119,7 @@ imports: - edwards25519 - extra25519 - name: github.com/tendermint/go-crypto - version: d31cfbaeaa4d930798ec327b52917975f3203c11 + version: bf355d1b58b27d4e98d8fb237eb14887b93a88f7 subpackages: - cmd - keys @@ -140,9 +142,8 @@ imports: - certifiers/files - proofs - name: github.com/tendermint/merkleeyes - version: 439776e0199f1812e132aa7b97463e8550906690 + version: 0310013053953eef80def3619aeb1e3a3254f452 subpackages: - - app - client - iavl - name: github.com/tendermint/tendermint diff --git a/glide.yaml b/glide.yaml index be5c66c3d..e10ba05f9 100644 --- a/glide.yaml +++ b/glide.yaml @@ -22,7 +22,7 @@ import: subpackages: - data - package: github.com/tendermint/light-client - version: 1c53d04dcc65c2fd15526152ed0651af10a09982 + version: unstable subpackages: - proofs - certifiers