diff --git a/modules/ibc/errors.go b/modules/ibc/errors.go index a81836a9c7..ee4e786728 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 8de36d42d7..cce8cb596d 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 91aebb77a4..28cbd309a2 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 a81836a9c7..31bbace0b7 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 6827586630..0131456ed5 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