From 95a5a7c9a0102b73287c2b3146ff743b9dd71d09 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Sun, 18 Mar 2018 13:32:16 +0100 Subject: [PATCH] IBC implementation almost done --- docs/spec/ibc/mvp1.md | 3 + examples/basecoin/app/app.go | 2 +- x/ibc/commands/ibctx.go | 7 +- x/ibc/commands/relay.go | 88 ++++++++++++------------ x/ibc/handler.go | 14 ++-- x/ibc/mapper.go | 128 ++++++++++++++++++++++------------- 6 files changed, 141 insertions(+), 101 deletions(-) diff --git a/docs/spec/ibc/mvp1.md b/docs/spec/ibc/mvp1.md index 7901e3ba86..5678b74ebc 100644 --- a/docs/spec/ibc/mvp1.md +++ b/docs/spec/ibc/mvp1.md @@ -9,6 +9,9 @@ The initial implementation of IBC will include just enough for simple coin transfers between chains, with safety features such as ACK messages being added later. +It is a complete stand-alone module. It includes the commands to send IBC +packets as well as to post them to the destination chain. + ### IBC Module ```go diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index 7bedb36269..6f820e4659 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -56,7 +56,7 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp { // add handlers coinKeeper := bank.NewCoinKeeper(app.accountMapper) coolMapper := cool.NewMapper(app.capKeyMainStore) - ibcMapper := ibc.NewMapper(app.capKeyIBCStore) + ibcMapper := ibc.NewIBCMapper(app.capKeyIBCStore) app.Router(). AddRoute("bank", bank.NewHandler(coinKeeper)). AddRoute("cool", cool.NewHandler(coinKeeper, coolMapper)). diff --git a/x/ibc/commands/ibctx.go b/x/ibc/commands/ibctx.go index f49270966e..d438b436d6 100644 --- a/x/ibc/commands/ibctx.go +++ b/x/ibc/commands/ibctx.go @@ -51,13 +51,13 @@ func (c sendCommander) sendIBCTransfer(cmd *cobra.Command, args []string) error fmt.Printf("%+v\n", msg) - bz := sdk.StdSignBytes(viper.GetString(flagChain), []int64{viper.GetInt64(client.FlagSequence)}, msg) - res, err := builder.BroadcastTx(bz) + res, err := builder.SignBuildBroadcast(msg, c.cdc) if err != nil { return err } fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil } @@ -73,10 +73,9 @@ func buildMsg(from sdk.Address) (sdk.Msg, error) { if err != nil { return nil, err } - to := sdk.Address(bz) - packet := ibc.NewIBCPacket(from, to, coins, viper.GetString(flagChain), + packet := ibc.NewIBCPacket(from, to, coins, client.FlagChainID, viper.GetString(flagChain)) msg := ibc.IBCTransferMsg{ diff --git a/x/ibc/commands/relay.go b/x/ibc/commands/relay.go index b2f116b5d2..7d277c790d 100644 --- a/x/ibc/commands/relay.go +++ b/x/ibc/commands/relay.go @@ -23,6 +23,12 @@ const ( FlagToChainNode = "to-chain-node" ) +type relayCommander struct { + cdc *wire.Codec + address sdk.Address + ibcStore string +} + func IBCRelayCmd(cdc *wire.Codec) *cobra.Command { cmdr := relayCommander{ cdc: cdc, @@ -33,17 +39,20 @@ func IBCRelayCmd(cdc *wire.Codec) *cobra.Command { Use: "relay", Run: cmdr.runIBCRelay, } + cmd.Flags().String(client.FlagName, "", "Name of private key with which to sign") cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses") cmd.Flags().String(FlagFromChainID, "", "Chain ID for ibc node to check outgoing packets") cmd.Flags().String(FlagFromChainNode, "tcp://localhost:46657", ": to tendermint rpc interface for this chain") cmd.Flags().String(FlagToChainID, "", "Chain ID for ibc node to broadcast incoming packets") cmd.Flags().String(FlagToChainNode, "tcp://localhost:46658", ": to tendermint rpc interface for this chain") + cmd.MarkFlagRequired(client.FlagName) cmd.MarkFlagRequired(FlagFromChainID) cmd.MarkFlagRequired(FlagFromChainNode) cmd.MarkFlagRequired(FlagToChainID) cmd.MarkFlagRequired(FlagToChainNode) + viper.BindPFlag(client.FlagName, cmd.Flags().Lookup(client.FlagName)) viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) viper.BindPFlag(FlagFromChainID, cmd.Flags().Lookup(FlagFromChainID)) @@ -54,18 +63,13 @@ func IBCRelayCmd(cdc *wire.Codec) *cobra.Command { return cmd } -type relayCommander struct { - cdc *wire.Codec - address sdk.Address - ibcStore string -} - func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) { fromChainID := viper.GetString(FlagFromChainID) fromChainNode := viper.GetString(FlagFromChainNode) toChainID := viper.GetString(FlagToChainID) toChainNode := viper.GetString(FlagToChainNode) address, err := builder.GetFromAddress() + if err != nil { panic(err) } @@ -74,44 +78,8 @@ func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) { c.loop(fromChainID, fromChainNode, toChainID, toChainNode) } -// https://github.com/cosmos/cosmos-sdk/blob/master/client/helpers.go using specified address - -func query(id string, key []byte, storeName string) (res []byte, err error) { - orig := viper.GetString(client.FlagNode) - viper.Set(client.FlagNode, id) - res, err = builder.Query(key, storeName) - viper.Set(client.FlagNode, orig) - return res, err -} - -func broadcastTx(id string, tx []byte) error { - orig := viper.GetString(client.FlagNode) - viper.Set(client.FlagNode, id) - _, err := builder.BroadcastTx(tx) - viper.Set(client.FlagNode, orig) - return err -} - -func (c relayCommander) refine(bz []byte, sequence int64) []byte { - var packet ibc.IBCPacket - if err := c.cdc.UnmarshalBinary(bz, &packet); err != nil { - panic(err) - } - - msg := ibc.IBCReceiveMsg{ - IBCPacket: packet, - Relayer: c.address, - Sequence: sequence, - } - res, err := builder.SignAndBuild(msg, c.cdc) - if err != nil { - panic(err) - } - return res -} - func (c relayCommander) loop(fromChainID, fromChainNode, toChainID, toChainNode string) { - ingressKey := ibc.IngressKey(fromChainID) + ingressKey := ibc.IngressSequenceKey(fromChainID) processedbz, err := query(toChainNode, ingressKey, c.ibcStore) if err != nil { @@ -161,3 +129,37 @@ OUTER: processed = egressLength } } + +func query(id string, key []byte, storeName string) (res []byte, err error) { + orig := viper.GetString(client.FlagNode) + viper.Set(client.FlagNode, id) + res, err = builder.Query(key, storeName) + viper.Set(client.FlagNode, orig) + return res, err +} + +func broadcastTx(id string, tx []byte) error { + orig := viper.GetString(client.FlagNode) + viper.Set(client.FlagNode, id) + _, err := builder.BroadcastTx(tx) + viper.Set(client.FlagNode, orig) + return err +} + +func (c relayCommander) refine(bz []byte, sequence int64) []byte { + var packet ibc.IBCPacket + if err := c.cdc.UnmarshalBinary(bz, &packet); err != nil { + panic(err) + } + + msg := ibc.IBCReceiveMsg{ + IBCPacket: packet, + Relayer: c.address, + Sequence: sequence, + } + res, err := builder.SignAndBuild(msg, c.cdc) + if err != nil { + panic(err) + } + return res +} diff --git a/x/ibc/handler.go b/x/ibc/handler.go index eca9ced33d..6bc1ea786e 100644 --- a/x/ibc/handler.go +++ b/x/ibc/handler.go @@ -4,7 +4,6 @@ import ( "reflect" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank" ) @@ -22,32 +21,37 @@ func NewHandler(ibcm IBCMapper, ck bank.CoinKeeper) sdk.Handler { } } +// Handle outgoing IBC packets. func handleIBCTransferMsg(ctx sdk.Context, ibcm IBCMapper, ck bank.CoinKeeper, msg IBCTransferMsg) sdk.Result { packet := msg.IBCPacket + _, err := ck.SubtractCoins(ctx, packet.SrcAddr, packet.Coins) if err != nil { return err.Result() } - ibcm.PushPacket(ctx, packet) + + err = ibcm.PostIBCPacket(ctx, packet) + if err != nil { + return err.Result() + } return sdk.Result{} } func handleIBCReceiveMsg(ctx sdk.Context, ibcm IBCMapper, ck bank.CoinKeeper, msg IBCReceiveMsg) sdk.Result { packet := msg.IBCPacket + seq := ibcm.GetIngressSequence(ctx, packet.SrcChain) if msg.Sequence != seq { return ErrInvalidSequence().Result() } - ibcm.SetIngressSequence(ctx, packet.SrcChain, seq+1) _, err := ck.AddCoins(ctx, packet.DestAddr, packet.Coins) if err != nil { return err.Result() } - // handle packet - // packet.Handle(ctx)... + ibcm.SetIngressSequence(ctx, packet.SrcChain, seq+1) return sdk.Result{} } diff --git a/x/ibc/mapper.go b/x/ibc/mapper.go index 7980fd19ee..49714ec162 100644 --- a/x/ibc/mapper.go +++ b/x/ibc/mapper.go @@ -4,36 +4,95 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" ) type IBCMapper struct { - ibcKey sdk.StoreKey - cdc *wire.Codec + key sdk.StoreKey + cdc *wire.Codec } -func NewMapper(ibcKey sdk.StoreKey) IBCMapper { +// XXX: The IBCMapper should not take a CoinKeeper. Rather have the CoinKeeper +// take an IBCMapper. +func NewIBCMapper(key sdk.StoreKey) IBCMapper { + // XXX: How are these codecs supposed to work? cdc := wire.NewCodec() return IBCMapper{ - ibcKey: ibcKey, - cdc: cdc, + key: key, + cdc: cdc, } } -func IngressKey(srcChain string) []byte { - return []byte(fmt.Sprintf("ingress/%s", srcChain)) +// XXX: This is not the public API. This will change in MVP2 and will henceforth +// only be invoked from another module directly and not through a user +// transaction. +// TODO: Handle invalid IBC packets and return errors. +func (ibcm IBCMapper) PostIBCPacket(ctx sdk.Context, packet IBCPacket) sdk.Error { + // write everything into the state + store := ctx.KVStore(ibcm.key) + index := ibcm.getEgressLength(store, packet.DestChain) + bz, err := ibcm.cdc.MarshalBinary(packet) + if err != nil { + panic(err) + } + + store.Set(EgressKey(packet.DestChain, index), bz) + bz, err = ibcm.cdc.MarshalBinary(int64(index + 1)) + if err != nil { + panic(err) + } + store.Set(EgressLengthKey(packet.DestChain), bz) + + return nil } -func EgressKey(destChain string, index int64) []byte { - return []byte(fmt.Sprintf("egress/%s/%d", destChain, index)) +// XXX: In the future every module is able to register it's own handler for +// handling it's own IBC packets. The "ibc" handler will only route the packets +// to the appropriate callbacks. +// XXX: For now this handles all interactions with the CoinKeeper. +// XXX: This needs to do some authentication checking. +func (ibcm IBCMapper) ReceiveIBCPacket(ctx sdk.Context, packet IBCPacket) sdk.Error { + return nil } -func EgressLengthKey(destChain string) []byte { - return []byte(fmt.Sprintf("egress/%s", destChain)) +// -------------------------- +// Functions for accessing the underlying KVStore. + +func (ibcm IBCMapper) GetIngressSequence(ctx sdk.Context, srcChain string) int64 { + store := ctx.KVStore(ibcm.key) + key := IngressSequenceKey(srcChain) + + bz := store.Get(key) + if bz == nil { + zero, err := ibcm.cdc.MarshalBinary(int64(0)) + if err != nil { + panic(err) + } + store.Set(key, zero) + return 0 + } + + var res int64 + err := ibcm.cdc.UnmarshalBinary(bz, &res) + if err != nil { + panic(err) + } + return res } +func (ibcm IBCMapper) SetIngressSequence(ctx sdk.Context, srcChain string, sequence int64) { + store := ctx.KVStore(ibcm.key) + key := IngressSequenceKey(srcChain) + + bz, err := ibcm.cdc.MarshalBinary(sequence) + if err != nil { + panic(err) + } + store.Set(key, bz) +} + +// Retrieves the index of the currently stored outgoing IBC packets. func (ibcm IBCMapper) getEgressLength(store sdk.KVStore, destChain string) int64 { bz := store.Get(EgressLengthKey(destChain)) if bz == nil { @@ -51,44 +110,17 @@ func (ibcm IBCMapper) getEgressLength(store sdk.KVStore, destChain string) int64 return res } -func (ibcm IBCMapper) GetIngressSequence(ctx sdk.Context, srcChain string) int64 { - store := ctx.KVStore(ibcm.ibcKey) - bz := store.Get(IngressKey(srcChain)) - if bz == nil { - zero, err := ibcm.cdc.MarshalBinary(int64(0)) - if err != nil { - panic(err) - } - store.Set(IngressKey(srcChain), zero) - return 0 - } - var res int64 - if err := ibcm.cdc.UnmarshalBinary(bz, &res); err != nil { - panic(err) - } - return res +// Stores an outgoing IBC packet under "egress/chain_id/index". +func EgressKey(destChain string, index int64) []byte { + return []byte(fmt.Sprintf("egress/%s/%d", destChain, index)) } -func (ibcm IBCMapper) SetIngressSequence(ctx sdk.Context, srcChain string, sequence int64) { - store := ctx.KVStore(ibcm.ibcKey) - bz, err := ibcm.cdc.MarshalBinary(sequence) - if err != nil { - panic(err) - } - store.Set(IngressKey(srcChain), bz) +// Stores the number of outgoing IBC packets under "egress/index". +func EgressLengthKey(destChain string) []byte { + return []byte(fmt.Sprintf("egress/%s", destChain)) } -func (ibcm IBCMapper) PushPacket(ctx sdk.Context, packet IBCPacket) { - store := ctx.KVStore(ibcm.ibcKey) - len := ibcm.getEgressLength(store, packet.DestChain) - packetbz, err := ibcm.cdc.MarshalBinary(packet) - if err != nil { - panic(err) - } - store.Set(EgressKey(packet.DestChain, len), packetbz) - lenbz, err := ibcm.cdc.MarshalBinary(int64(len + 1)) - if err != nil { - panic(err) - } - store.Set(EgressLengthKey(packet.DestChain), lenbz) +// Stores the sequence number of incoming IBC packet under "ingress/index". +func IngressSequenceKey(srcChain string) []byte { + return []byte(fmt.Sprintf("ingress/%s", srcChain)) }