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} }