From 87d09fd22014bd539ac59d267c4973bca4da7fb4 Mon Sep 17 00:00:00 2001 From: Joon Date: Tue, 5 Nov 2019 09:08:55 -0800 Subject: [PATCH] ICS 04 Implementation (#4548) * merge from ics04 branch * merge from ics04 branch * fix lint * add port * fix test * add mocks * fix connid -> portid in handshake.go * add mock * add ibc module.go, finalize mock * add keeper * add StoreKey const * fix test * Apply suggestions from code review Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * applying review in progress * apply review - make querier interface * fix cli errors * fix dependency * fix dependency * reflect method name change * revise querier interface to work both on cli & store * revise querier interface to work both on cli & store * revise querier interface to work both on cli & store * reflect downstream change * fix cli * reflect downstream changes * reflect downstream changes * fix from address in tx cli * fix cli in progress(squash later) * fix cli * remove timeout, add channel cli * fix golangci * fix cli * Clean up * fix mock cli in progress * finalize cleanup, mock cli wip * add cli for mocksend * fix handler * rm commented lines * address review in progress * address review, rm cleanup/closing * rename mock packages * fix interface for gaia * rename Path -> Prefix * Store accessor upstream changes (#5119) * Store accessor upstream changes (#5119) * add comments, reformat merkle querier * rm merkle/utils * ICS 23 upstream changes (#5120) * ICS 23 upstream changes (#5120) * update Value * update test * fix * ICS 02 upstream changes (#5122) * ICS 02 upstream changes (#5122) * ICS 03 upstream changes (#5123) * ICS 03 upstream changes (#5123) * update test * cleanup types and submodule * more cleanup and godocs * remove counterPartyManager/State and cleanup * implement SubmitMisbehaviour and refactor * errors * events * fix test * refactors * WIP refactor ICS03 * remove Mapping * remove store accessors * proposed refactor * remove store accessors from ICS02 * refactor queriers, handler and clean keeper * logger and tx long description * ineffassign * Apply suggestions from code review Co-Authored-By: Jack Zampolin * Apply suggestions from code review Co-Authored-By: Jack Zampolin * remove store accessors * refactor handshake to update it to the latest ICS03 spec * update handler and msgs * add verification functions * update verification * ICS02 module.go * top level x/ibc structure * update connection queries * update connection tx * remove extra files * refactor: remove store accessors, update keeper and types to match spec (WIP) * update handshake and packet * implement packet timeouts * implement send and receive packet * implement packet ACK * update handler * add channel errors * channel querier * update expected client keeper and export verification funcs * ICS 05 Implementation * release port and godocs * Update x/ibc/02-client/client/cli/query.go Co-Authored-By: Jack Zampolin * Update x/ibc/02-client/types/tendermint/consensus_state.go Co-Authored-By: Jack Zampolin * address some of the review comments * resolve some TODOs and address comments from review * update connection versioning * minor error updates * update ICS04 with downstream changes * Implement tx cli actions * add MsgSendPacket handler; msg validation, errors and events * update errors and add port Keeper to ibc Keeper * minor UX improvements * rename pkg * fixes * refactor ICS23 * cleanup types * ICS 5 updates (#5222) * Validate port identifiers * Refactor to static bind * Add comments * Add 'GetPorts' query function * rename pkg and fix import * implement batch verification * gosimple suggestion * various fixes; remove legacy tests; remove commitment path query * alias * minor updates from ICS23 * renaming * update verification and rename root funcs * rm legacy tests; add query proofs support * remove capability key generation and authentication logic * move querier to x/ibc * update query.go to use 'custom/...' query path * add tests * ICS 24 Implementation (#5229) * add validation functions * validate path in ics-23 * address @fede comments * move errors into host package * flatten ICS23 structure * fix ApplyPrefix * updates from ICS23 and ICS24 * msg.ValidateBasic and ADR09 evidence interface * complete types testing * delete empty test file * remove ibc errors from core error package * custom JSON marshaling; msg.ValidateBasic; renaming of variables * minor update * custom JSON marshaling * use host validation for port ids * downstream changes; custom marshal JSON; msg validation, and update errors * update errors and aliases * start batch-verify tests * update msg validation and CLI UX * minor changes on commitment types * fix channel and packet check (#5243) * R4R - Store consensus state correctly (#5242) * store consensus state correctly * fix client example * update alias * update alias * update alias and keeper.GetPort() * authenticate port ID; remove send packet msg from CLI * comment out handlers * use testsuite * Integrate Evidence Implementation into ICS-02 (#5258) * implement evidence in ics-02 * fix build errors and import cycles * address fede comments * remove unnecessary pubkey and fix init * add tests * finish tendermint tests * complete merge * Add tests for msgs * upstream changes * fix * upstream changes * fix cons state * context changes * fix cli tx * upstream changes * upstream changes * upstream changes --- x/ibc/04-channel/alias.go | 123 +++++ x/ibc/04-channel/client/cli/query.go | 55 +++ x/ibc/04-channel/client/cli/tx.go | 535 +++++++++++++++++++++ x/ibc/04-channel/client/utils/utils.go | 70 +++ x/ibc/04-channel/exported/exported.go | 16 + x/ibc/04-channel/handler.go | 153 ++++++ x/ibc/04-channel/keeper/handshake.go | 410 ++++++++++++++++ x/ibc/04-channel/keeper/keeper.go | 146 ++++++ x/ibc/04-channel/keeper/packet.go | 357 ++++++++++++++ x/ibc/04-channel/keeper/querier.go | 32 ++ x/ibc/04-channel/keeper/timeout.go | 195 ++++++++ x/ibc/04-channel/module.go | 25 + x/ibc/04-channel/types/channel.go | 225 +++++++++ x/ibc/04-channel/types/codec.go | 28 ++ x/ibc/04-channel/types/errors.go | 73 +++ x/ibc/04-channel/types/events.go | 27 ++ x/ibc/04-channel/types/expected_keepers.go | 31 ++ x/ibc/04-channel/types/keys.go | 85 ++++ x/ibc/04-channel/types/msgs.go | 379 +++++++++++++++ x/ibc/04-channel/types/packet.go | 108 +++++ x/ibc/04-channel/types/querier.go | 71 +++ x/ibc/handler.go | 22 + x/ibc/keeper/keeper.go | 4 + x/ibc/keeper/querier.go | 9 + x/ibc/module.go | 2 + 25 files changed, 3181 insertions(+) create mode 100644 x/ibc/04-channel/alias.go create mode 100644 x/ibc/04-channel/client/cli/query.go create mode 100644 x/ibc/04-channel/client/cli/tx.go create mode 100644 x/ibc/04-channel/client/utils/utils.go create mode 100644 x/ibc/04-channel/exported/exported.go create mode 100644 x/ibc/04-channel/handler.go create mode 100644 x/ibc/04-channel/keeper/handshake.go create mode 100644 x/ibc/04-channel/keeper/keeper.go create mode 100644 x/ibc/04-channel/keeper/packet.go create mode 100644 x/ibc/04-channel/keeper/querier.go create mode 100644 x/ibc/04-channel/keeper/timeout.go create mode 100644 x/ibc/04-channel/module.go create mode 100644 x/ibc/04-channel/types/channel.go create mode 100644 x/ibc/04-channel/types/codec.go create mode 100644 x/ibc/04-channel/types/errors.go create mode 100644 x/ibc/04-channel/types/events.go create mode 100644 x/ibc/04-channel/types/expected_keepers.go create mode 100644 x/ibc/04-channel/types/keys.go create mode 100644 x/ibc/04-channel/types/msgs.go create mode 100644 x/ibc/04-channel/types/packet.go create mode 100644 x/ibc/04-channel/types/querier.go diff --git a/x/ibc/04-channel/alias.go b/x/ibc/04-channel/alias.go new file mode 100644 index 000000000000..25f73b478565 --- /dev/null +++ b/x/ibc/04-channel/alias.go @@ -0,0 +1,123 @@ +package channel + +// nolint +// autogenerated code using github.com/rigelrozanski/multitool +// aliases generated for the following subdirectories: +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/ibc/04-channel/keeper +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types + +import ( + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/keeper" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" +) + +const ( + NONE = types.NONE + UNORDERED = types.UNORDERED + ORDERED = types.ORDERED + OrderNone = types.OrderNone + OrderUnordered = types.OrderUnordered + OrderOrdered = types.OrderOrdered + CLOSED = types.CLOSED + INIT = types.INIT + OPENTRY = types.OPENTRY + OPEN = types.OPEN + StateClosed = types.StateClosed + StateInit = types.StateInit + StateOpenTry = types.StateOpenTry + StateOpen = types.StateOpen + DefaultCodespace = types.DefaultCodespace + CodeChannelExists = types.CodeChannelExists + CodeChannelNotFound = types.CodeChannelNotFound + CodeInvalidCounterpartyChannel = types.CodeInvalidCounterpartyChannel + CodeChannelCapabilityNotFound = types.CodeChannelCapabilityNotFound + CodeInvalidPacket = types.CodeInvalidPacket + CodeSequenceNotFound = types.CodeSequenceNotFound + CodePacketTimeout = types.CodePacketTimeout + CodeInvalidChannel = types.CodeInvalidChannel + CodeInvalidChannelState = types.CodeInvalidChannelState + CodeInvalidChannelProof = types.CodeInvalidChannelProof + AttributeKeySenderPort = types.AttributeKeySenderPort + AttributeKeyReceiverPort = types.AttributeKeyReceiverPort + AttributeKeyChannelID = types.AttributeKeyChannelID + AttributeKeySequence = types.AttributeKeySequence + SubModuleName = types.SubModuleName + StoreKey = types.StoreKey + RouterKey = types.RouterKey + QuerierRoute = types.QuerierRoute + QueryChannel = types.QueryChannel +) + +var ( + // functions aliases + NewKeeper = keeper.NewKeeper + QuerierChannel = keeper.QuerierChannel + NewChannel = types.NewChannel + NewCounterparty = types.NewCounterparty + OrderFromString = types.OrderFromString + StateFromString = types.StateFromString + RegisterCodec = types.RegisterCodec + ErrChannelExists = types.ErrChannelExists + ErrChannelNotFound = types.ErrChannelNotFound + ErrInvalidCounterpartyChannel = types.ErrInvalidCounterpartyChannel + ErrChannelCapabilityNotFound = types.ErrChannelCapabilityNotFound + ErrInvalidPacket = types.ErrInvalidPacket + ErrSequenceNotFound = types.ErrSequenceNotFound + ErrPacketTimeout = types.ErrPacketTimeout + ErrInvalidChannel = types.ErrInvalidChannel + ErrInvalidChannelState = types.ErrInvalidChannelState + ErrInvalidChannelProof = types.ErrInvalidChannelProof + ChannelPath = types.ChannelPath + ChannelCapabilityPath = types.ChannelCapabilityPath + NextSequenceSendPath = types.NextSequenceSendPath + NextSequenceRecvPath = types.NextSequenceRecvPath + PacketCommitmentPath = types.PacketCommitmentPath + PacketAcknowledgementPath = types.PacketAcknowledgementPath + KeyChannel = types.KeyChannel + KeyChannelCapabilityPath = types.KeyChannelCapabilityPath + KeyNextSequenceSend = types.KeyNextSequenceSend + KeyNextSequenceRecv = types.KeyNextSequenceRecv + KeyPacketCommitment = types.KeyPacketCommitment + KeyPacketAcknowledgement = types.KeyPacketAcknowledgement + NewMsgChannelOpenInit = types.NewMsgChannelOpenInit + NewMsgChannelOpenTry = types.NewMsgChannelOpenTry + NewMsgChannelOpenAck = types.NewMsgChannelOpenAck + NewMsgChannelOpenConfirm = types.NewMsgChannelOpenConfirm + NewMsgChannelCloseInit = types.NewMsgChannelCloseInit + NewMsgChannelCloseConfirm = types.NewMsgChannelCloseConfirm + NewPacket = types.NewPacket + NewOpaquePacket = types.NewOpaquePacket + NewChannelResponse = types.NewChannelResponse + NewQueryChannelParams = types.NewQueryChannelParams + + // variable aliases + SubModuleCdc = types.SubModuleCdc + EventTypeChannelOpenInit = types.EventTypeChannelOpenInit + EventTypeChannelOpenTry = types.EventTypeChannelOpenTry + EventTypeChannelOpenAck = types.EventTypeChannelOpenAck + EventTypeChannelOpenConfirm = types.EventTypeChannelOpenConfirm + EventTypeChannelCloseInit = types.EventTypeChannelCloseInit + EventTypeChannelCloseConfirm = types.EventTypeChannelCloseConfirm + AttributeValueCategory = types.AttributeValueCategory +) + +type ( + Keeper = keeper.Keeper + Channel = types.Channel + Counterparty = types.Counterparty + Order = types.Order + State = types.State + ClientKeeper = types.ClientKeeper + ConnectionKeeper = types.ConnectionKeeper + PortKeeper = types.PortKeeper + MsgChannelOpenInit = types.MsgChannelOpenInit + MsgChannelOpenTry = types.MsgChannelOpenTry + MsgChannelOpenAck = types.MsgChannelOpenAck + MsgChannelOpenConfirm = types.MsgChannelOpenConfirm + MsgChannelCloseInit = types.MsgChannelCloseInit + MsgChannelCloseConfirm = types.MsgChannelCloseConfirm + Packet = types.Packet + OpaquePacket = types.OpaquePacket + ChannelResponse = types.ChannelResponse + QueryChannelParams = types.QueryChannelParams +) diff --git a/x/ibc/04-channel/client/cli/query.go b/x/ibc/04-channel/client/cli/query.go new file mode 100644 index 000000000000..04bfcfcbbe92 --- /dev/null +++ b/x/ibc/04-channel/client/cli/query.go @@ -0,0 +1,55 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + cli "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/client/utils" +) + +// GetQueryCmd returns the query commands for IBC channels +func GetQueryCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + ics04ChannelQueryCmd := &cobra.Command{ + Use: "channel", + Short: "IBC channel query subcommands", + DisableFlagParsing: true, + } + + ics04ChannelQueryCmd.AddCommand(cli.GetCommands( + GetCmdQueryChannel(storeKey, cdc), + )...) + + return ics04ChannelQueryCmd +} + +// GetCmdQueryChannel defines the command to query a channel end +func GetCmdQueryChannel(queryRoute string, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "end [port-id] [channel-id]", + Short: "Query a channel end", + Long: strings.TrimSpace(fmt.Sprintf(`Query an IBC channel end + +Example: +$ %s query ibc channel end [port-id] [channel-id] + `, version.ClientName), + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + ch, err := utils.QueryChannel(cliCtx, args[0], args[1], queryRoute) + if err != nil { + return err + } + + return cliCtx.PrintOutput(ch) + }, + } + return cmd +} diff --git a/x/ibc/04-channel/client/cli/tx.go b/x/ibc/04-channel/client/cli/tx.go new file mode 100644 index 000000000000..833ed29af01c --- /dev/null +++ b/x/ibc/04-channel/client/cli/tx.go @@ -0,0 +1,535 @@ +package cli + +import ( + "fmt" + "io/ioutil" + "os" + "strconv" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + clientutils "github.com/cosmos/cosmos-sdk/x/ibc/02-client/client/utils" + clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" + abci "github.com/tendermint/tendermint/abci/types" +) + +const ( + FlagOrdered = "ordered" + FlagIBCVersion = "ibc-version" + FlagNode1 = "node1" + FlagNode2 = "node2" + FlagFrom1 = "from1" + FlagFrom2 = "from2" + FlagChainID2 = "chain-id2" +) + +// GetTxCmd returns the transaction commands for IBC Connections +func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + ics04ChannelTxCmd := &cobra.Command{ + Use: "channel", + Short: "IBC channel transaction subcommands", + } + + ics04ChannelTxCmd.AddCommand(client.PostCommands( + GetMsgChannelOpenInitCmd(storeKey, cdc), + GetMsgChannelOpenTryCmd(storeKey, cdc), + GetMsgChannelOpenAckCmd(storeKey, cdc), + GetMsgChannelOpenConfirmCmd(storeKey, cdc), + GetMsgChannelCloseInitCmd(storeKey, cdc), + GetMsgChannelCloseConfirmCmd(storeKey, cdc), + GetCmdHandshake(storeKey, cdc), + )...) + + return ics04ChannelTxCmd +} + +// TODO: module needs to pass the capability key (i.e store key) + +// GetMsgChannelOpenInitCmd returns the command to create a MsgChannelOpenInit transaction +func GetMsgChannelOpenInitCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "open-init [port-id] [channel-id] [counterparty-port-id] [counterparty-channel-id] [connection-hops]", + Short: "Creates and sends a ChannelOpenInit message", + Args: cobra.ExactArgs(5), + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + portID := args[0] + channelID := args[1] + counterpartyPortID := args[2] + counterpartyChannelID := args[3] + hops := strings.Split(args[4], "/") + order := channelOrder() + version := viper.GetString(FlagIBCVersion) + + msg := types.NewMsgChannelOpenInit( + portID, channelID, version, order, hops, + counterpartyPortID, counterpartyChannelID, cliCtx.GetFromAddress(), + ) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } + + cmd.Flags().Bool(FlagOrdered, true, "Pass flag for opening ordered channels") + cmd.Flags().String(FlagIBCVersion, "1.0.0", "supported IBC version") + + return cmd +} + +// GetMsgChannelOpenTryCmd returns the command to create a MsgChannelOpenTry transaction +func GetMsgChannelOpenTryCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "open-try [port-id] [channel-id] [counterparty-port-id] [counterparty-channel-id] [connection-hops] [/path/to/proof-init.json] [proof-height]", + Short: "Creates and sends a ChannelOpenTry message", + Args: cobra.ExactArgs(7), + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + portID := args[0] + channelID := args[1] + counterpartyPortID := args[2] + counterpartyChannelID := args[3] + hops := strings.Split(args[4], "/") + order := channelOrder() + version := viper.GetString(FlagIBCVersion) // TODO: diferenciate between channel and counterparty versions + + var proof commitment.ProofI + if err := cdc.UnmarshalJSON([]byte(args[5]), &proof); err != nil { + fmt.Fprintf(os.Stderr, "failed to unmarshall input into struct, checking for file...") + contents, err := ioutil.ReadFile(args[5]) + if err != nil { + return fmt.Errorf("error opening proof file: %v", err) + } + if err := cdc.UnmarshalJSON(contents, &proof); err != nil { + return fmt.Errorf("error unmarshalling proof file: %v", err) + } + } + + proofHeight, err := strconv.ParseInt(args[6], 10, 64) + if err != nil { + return err + } + + msg := types.NewMsgChannelOpenTry( + portID, channelID, version, order, hops, + counterpartyPortID, counterpartyChannelID, version, + proof, uint64(proofHeight), cliCtx.GetFromAddress(), + ) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } + cmd.Flags().Bool(FlagOrdered, true, "Pass flag for opening ordered channels") + cmd.Flags().String(FlagIBCVersion, "1.0.0", "supported IBC version") + + return cmd +} + +// GetMsgChannelOpenAckCmd returns the command to create a MsgChannelOpenAck transaction +func GetMsgChannelOpenAckCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "open-ack [port-id] [channel-id] [/path/to/proof-try.json] [proof-height]", + Short: "Creates and sends a ChannelOpenAck message", + Args: cobra.ExactArgs(4), + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + portID := args[0] + channelID := args[1] + version := viper.GetString(FlagIBCVersion) // TODO: diferenciate between channel and counterparty versions + + var proof commitment.ProofI + if err := cdc.UnmarshalJSON([]byte(args[2]), &proof); err != nil { + fmt.Fprintf(os.Stderr, "failed to unmarshall input into struct, checking for file...") + contents, err := ioutil.ReadFile(args[2]) + if err != nil { + return fmt.Errorf("error opening proof file: %v", err) + } + if err := cdc.UnmarshalJSON(contents, &proof); err != nil { + return fmt.Errorf("error unmarshalling proof file: %v", err) + } + } + + proofHeight, err := strconv.ParseInt(args[3], 10, 64) + if err != nil { + return err + } + + msg := types.NewMsgChannelOpenAck( + portID, channelID, version, proof, uint64(proofHeight), cliCtx.GetFromAddress(), + ) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } + cmd.Flags().String(FlagIBCVersion, "1.0.0", "supported IBC version") + return cmd +} + +// GetMsgChannelOpenConfirmCmd returns the command to create a MsgChannelOpenConfirm transaction +func GetMsgChannelOpenConfirmCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "open-confirm [port-id] [channel-id] [/path/to/proof-ack.json] [proof-height]", + Short: "Creates and sends a ChannelOpenConfirm message", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + portID := args[0] + channelID := args[1] + + var proof commitment.ProofI + if err := cdc.UnmarshalJSON([]byte(args[2]), &proof); err != nil { + fmt.Fprintf(os.Stderr, "failed to unmarshall input into struct, checking for file...") + contents, err := ioutil.ReadFile(args[2]) + if err != nil { + return fmt.Errorf("error opening proof file: %v", err) + } + if err := cdc.UnmarshalJSON(contents, &proof); err != nil { + return fmt.Errorf("error unmarshalling proof file: %v", err) + } + } + + proofHeight, err := strconv.ParseInt(args[3], 10, 64) + if err != nil { + return err + } + + msg := types.NewMsgChannelOpenConfirm( + portID, channelID, proof, uint64(proofHeight), cliCtx.GetFromAddress(), + ) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } +} + +// GetMsgChannelCloseInitCmd returns the command to create a MsgChannelCloseInit transaction +func GetMsgChannelCloseInitCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "close-init [port-id] [channel-id]", + Short: "Creates and sends a ChannelCloseInit message", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + portID := args[0] + channelID := args[1] + + msg := types.NewMsgChannelCloseInit(portID, channelID, cliCtx.GetFromAddress()) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } +} + +// GetMsgChannelCloseConfirmCmd returns the command to create a MsgChannelCloseConfirm transaction +func GetMsgChannelCloseConfirmCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "close-confirm [port-id] [channel-id] [/path/to/proof-init.json] [proof-height]", + Short: "Creates and sends a ChannelCloseConfirm message", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContext().WithCodec(cdc) + + portID := args[0] + channelID := args[1] + + var proof commitment.ProofI + if err := cdc.UnmarshalJSON([]byte(args[2]), &proof); err != nil { + fmt.Fprintf(os.Stderr, "failed to unmarshall input into struct, checking for file...") + contents, err := ioutil.ReadFile(args[2]) + if err != nil { + return fmt.Errorf("error opening proof file: %v", err) + } + if err := cdc.UnmarshalJSON(contents, &proof); err != nil { + return fmt.Errorf("error unmarshalling proof file: %v", err) + } + } + + proofHeight, err := strconv.ParseInt(args[3], 10, 64) + if err != nil { + return err + } + + msg := types.NewMsgChannelCloseConfirm( + portID, channelID, proof, uint64(proofHeight), cliCtx.GetFromAddress(), + ) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } +} + +func GetCmdHandshake(storeKey string, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "handshake", + Short: "initiate connection handshake between two chains", + Long: strings.TrimSpace( + fmt.Sprintf(`initialize a connection on chain A with a given counterparty chain B: + +Example: +$ %s tx ibc channel handshake [client-id] [port-id] [chan-id] [conn-id] [cp-client-id] [cp-port-id] [cp-chain-id] [cp-conn-id] + `, version.ClientName)), + Args: cobra.ExactArgs(8), + // Args: []string{portid1, chanid1, connid1, portid2, chanid2, connid2} + RunE: func(cmd *cobra.Command, args []string) error { + // --chain-id values for each chain + cid1 := viper.GetString(flags.FlagChainID) + cid2 := viper.GetString(FlagChainID2) + + // --from values for each wallet + from1 := viper.GetString(FlagFrom1) + from2 := viper.GetString(FlagFrom2) + + // --node values for each RPC + node1 := viper.GetString(FlagNode1) + node2 := viper.GetString(FlagNode2) + + // client IDs + clientid1 := args[0] + clientid2 := args[4] + + // port IDs + portid1 := args[1] + portid2 := args[5] + + // channel IDs + chanid1 := args[2] + chanid2 := args[6] + + // connection IDs + connid1 := args[3] + connid2 := args[7] + + // Create txbldr, clictx, querier for cid1 + viper.Set(flags.FlagChainID, cid1) + txBldr1 := auth.NewTxBuilderFromCLI(). + WithTxEncoder(utils.GetTxEncoder(cdc)) + ctx1 := context.NewCLIContextIBC(from1, cid1, node1). + WithCodec(cdc). + WithBroadcastMode(flags.BroadcastBlock) + + // Create txbldr, clictx, querier for cid2 + viper.Set(flags.FlagChainID, cid2) + txBldr2 := auth.NewTxBuilderFromCLI(). + WithTxEncoder(utils.GetTxEncoder(cdc)) + ctx2 := context.NewCLIContextIBC(from2, cid2, node2). + WithCodec(cdc). + WithBroadcastMode(flags.BroadcastBlock) + + // // get passphrase for key from1 + // passphrase1, err := keys.GetPassphrase(from1) + // if err != nil { + // return err + // } + + // // get passphrase for key from2 + // passphrase2, err := keys.GetPassphrase(from2) + // if err != nil { + // return err + // } + + // TODO: check state and if not Idle continue existing process + viper.Set(flags.FlagChainID, cid1) + msgOpenInit := types.NewMsgChannelOpenInit(portid1, chanid1, "v1.0.0", channelOrder(), []string{connid1}, portid2, chanid2, ctx1.GetFromAddress()) + if err := msgOpenInit.ValidateBasic(); err != nil { + return err + } + + err := utils.CompleteAndBroadcastTxCLI(txBldr1, ctx1, []sdk.Msg{msgOpenInit}) + if err != nil { + return err + } + + // Another block has to be passed after msginit is committed + // to retrieve the correct proofs + time.Sleep(8 * time.Second) + + header, err := clientutils.GetTendermintHeader(ctx1) + if err != nil { + return err + } + + viper.Set(flags.FlagChainID, cid2) + msgUpdateClient := clienttypes.NewMsgUpdateClient(clientid2, header, ctx2.GetFromAddress()) + if err := msgUpdateClient.ValidateBasic(); err != nil { + return err + } + + err = utils.CompleteAndBroadcastTxCLI(txBldr2, ctx2, []sdk.Msg{msgUpdateClient}) + if err != nil { + return err + } + + viper.Set(flags.FlagChainID, cid1) + proofs, err := queryProofs(ctx1.WithHeight(header.Height-1), portid1, chanid1, storeKey) + if err != nil { + return err + } + + msgOpenTry := types.NewMsgChannelOpenTry(portid2, chanid2, "v1.0.0", channelOrder(), []string{connid2}, portid1, chanid1, "v1.0.0", proofs.Proof, uint64(header.Height), ctx2.GetFromAddress()) + if err := msgUpdateClient.ValidateBasic(); err != nil { + return err + } + + err = utils.CompleteAndBroadcastTxCLI(txBldr2, ctx2, []sdk.Msg{msgOpenTry}) + if err != nil { + return err + } + + // Another block has to be passed after msginit is committed + // to retrieve the correct proofs + time.Sleep(8 * time.Second) + + header, err = clientutils.GetTendermintHeader(ctx2) + if err != nil { + return err + } + + viper.Set(flags.FlagChainID, cid1) + msgUpdateClient = clienttypes.NewMsgUpdateClient(clientid1, header, ctx1.GetFromAddress()) + if err := msgUpdateClient.ValidateBasic(); err != nil { + return err + } + + err = utils.CompleteAndBroadcastTxCLI(txBldr1, ctx1, []sdk.Msg{msgUpdateClient}) + if err != nil { + return err + } + + viper.Set(flags.FlagChainID, cid2) + proofs, err = queryProofs(ctx2.WithHeight(header.Height-1), portid2, chanid2, storeKey) + if err != nil { + return err + } + + viper.Set(flags.FlagChainID, cid1) + msgOpenAck := types.NewMsgChannelOpenAck(portid1, chanid1, "v1.0.0", proofs.Proof, uint64(header.Height), ctx1.GetFromAddress()) + if err := msgOpenAck.ValidateBasic(); err != nil { + return err + } + + err = utils.CompleteAndBroadcastTxCLI(txBldr1, ctx1, []sdk.Msg{msgOpenAck}) + if err != nil { + return err + } + + // Another block has to be passed after msginit is committed + // to retrieve the correct proofs + time.Sleep(8 * time.Second) + + header, err = clientutils.GetTendermintHeader(ctx1) + if err != nil { + return err + } + + viper.Set(flags.FlagChainID, cid2) + msgUpdateClient = clienttypes.NewMsgUpdateClient(clientid2, header, ctx2.GetFromAddress()) + if err := msgUpdateClient.ValidateBasic(); err != nil { + return err + } + + err = utils.CompleteAndBroadcastTxCLI(txBldr2, ctx2, []sdk.Msg{msgUpdateClient}) + if err != nil { + return err + } + + viper.Set(flags.FlagChainID, cid1) + proofs, err = queryProofs(ctx1.WithHeight(header.Height-1), portid1, chanid1, storeKey) + if err != nil { + return err + } + + msgOpenConfirm := types.NewMsgChannelOpenConfirm(portid2, chanid2, proofs.Proof, uint64(header.Height), ctx2.GetFromAddress()) + if err := msgOpenConfirm.ValidateBasic(); err != nil { + return err + } + + err = utils.CompleteAndBroadcastTxCLI(txBldr2, ctx2, []sdk.Msg{msgOpenConfirm}) + if err != nil { + return err + } + + return nil + }, + } + + cmd.Flags().String(FlagNode1, "tcp://localhost:26657", "RPC port for the first chain") + cmd.Flags().String(FlagNode2, "tcp://localhost:26657", "RPC port for the second chain") + cmd.Flags().String(FlagFrom1, "", "key in local keystore for first chain") + cmd.Flags().String(FlagFrom2, "", "key in local keystore for second chain") + cmd.Flags().String(FlagChainID2, "", "chain-id for the second chain") + cmd.Flags().Bool(FlagOrdered, true, "Pass flag for opening ordered channels") + + cmd.MarkFlagRequired(FlagFrom1) + cmd.MarkFlagRequired(FlagFrom2) + + return cmd +} + +func queryProofs(ctx client.CLIContext, portID string, channelID string, queryRoute string) (types.ChannelResponse, error) { + var connRes types.ChannelResponse + + req := abci.RequestQuery{ + Path: "store/ibc/key", + Data: types.KeyChannel(portID, channelID), + Prove: true, + } + + res, err := ctx.QueryABCI(req) + if res.Value == nil || err != nil { + return connRes, err + } + + var channel types.Channel + if err := ctx.Codec.UnmarshalBinaryLengthPrefixed(res.Value, &channel); err != nil { + return connRes, err + } + return types.NewChannelResponse(portID, channelID, channel, res.Proof, res.Height), nil +} + +func channelOrder() types.Order { + if viper.GetBool(FlagOrdered) { + return types.ORDERED + } + return types.UNORDERED +} diff --git a/x/ibc/04-channel/client/utils/utils.go b/x/ibc/04-channel/client/utils/utils.go new file mode 100644 index 000000000000..580a2bf1fb83 --- /dev/null +++ b/x/ibc/04-channel/client/utils/utils.go @@ -0,0 +1,70 @@ +package utils + +import ( + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" +) + +// QueryPacket returns a packet from the store +func QueryPacket( + ctx client.CLIContext, portID, channelID string, + sequence, timeout uint64, queryRoute string, +) (types.PacketResponse, error) { + var packetRes types.PacketResponse + + req := abci.RequestQuery{ + Path: "store/ibc/key", + Data: types.KeyPacketCommitment(portID, channelID, sequence), + Prove: true, + } + + res, err := ctx.QueryABCI(req) + if err != nil { + return packetRes, err + } + + channel, err := QueryChannel(ctx, portID, channelID, queryRoute) + if err != nil { + return packetRes, err + } + + destPortID := channel.Channel.Counterparty.PortID + destChannelID := channel.Channel.Counterparty.ChannelID + + packet := types.NewPacket( + sequence, + timeout, + portID, + channelID, + destPortID, + destChannelID, + res.Value, + ) + + // FIXME: res.Height+1 is hack, fix later + return types.NewPacketResponse(portID, channelID, sequence, packet, res.Proof, res.Height+1), nil +} + +// QueryChannel returns a channel from the store +func QueryChannel(ctx client.CLIContext, portID string, channelID string, queryRoute string) (types.ChannelResponse, error) { + var connRes types.ChannelResponse + + req := abci.RequestQuery{ + Path: "store/ibc/key", + Data: types.KeyChannel(portID, channelID), + Prove: true, + } + + res, err := ctx.QueryABCI(req) + if res.Value == nil || err != nil { + return connRes, err + } + + var channel types.Channel + if err := ctx.Codec.UnmarshalBinaryLengthPrefixed(res.Value, &channel); err != nil { + return connRes, err + } + return types.NewChannelResponse(portID, channelID, channel, res.Proof, res.Height), nil +} diff --git a/x/ibc/04-channel/exported/exported.go b/x/ibc/04-channel/exported/exported.go new file mode 100644 index 000000000000..8fb84f7c1c01 --- /dev/null +++ b/x/ibc/04-channel/exported/exported.go @@ -0,0 +1,16 @@ +package exported + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type PacketI interface { + GetSequence() uint64 + GetTimeoutHeight() uint64 + GetSourcePort() string + GetSourceChannel() string + GetDestPort() string + GetDestChannel() string + GetData() []byte + ValidateBasic() sdk.Error +} diff --git a/x/ibc/04-channel/handler.go b/x/ibc/04-channel/handler.go new file mode 100644 index 000000000000..31c97b668e1a --- /dev/null +++ b/x/ibc/04-channel/handler.go @@ -0,0 +1,153 @@ +package channel + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/keeper" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" +) + +// HandleMsgChannelOpenInit defines the sdk.Handler for MsgChannelOpenInit +func HandleMsgChannelOpenInit(ctx sdk.Context, k keeper.Keeper, msg types.MsgChannelOpenInit) sdk.Result { + err := k.ChanOpenInit( + ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortID, msg.ChannelID, + msg.Channel.Counterparty, msg.Channel.Version, + ) + if err != nil { + return sdk.ResultFromError(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelOpenInit, + sdk.NewAttribute(types.AttributeKeySenderPort, msg.PortID), + sdk.NewAttribute(types.AttributeKeyChannelID, msg.ChannelID), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Signer.String()), + ), + }) + + return sdk.Result{Events: ctx.EventManager().Events()} +} + +// HandleMsgChannelOpenTry defines the sdk.Handler for MsgChannelOpenTry +func HandleMsgChannelOpenTry(ctx sdk.Context, k keeper.Keeper, msg types.MsgChannelOpenTry) sdk.Result { + err := k.ChanOpenTry(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortID, msg.ChannelID, + msg.Channel.Counterparty, msg.Channel.Version, msg.CounterpartyVersion, msg.ProofInit, msg.ProofHeight, + ) + if err != nil { + return sdk.ResultFromError(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelOpenTry, + sdk.NewAttribute(types.AttributeKeyChannelID, msg.ChannelID), + sdk.NewAttribute(types.AttributeKeySenderPort, msg.PortID), // TODO: double check sender and receiver + sdk.NewAttribute(types.AttributeKeyReceiverPort, msg.Channel.Counterparty.PortID), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Signer.String()), + ), + }) + + return sdk.Result{Events: ctx.EventManager().Events()} +} + +// HandleMsgChannelOpenAck defines the sdk.Handler for MsgChannelOpenAck +func HandleMsgChannelOpenAck(ctx sdk.Context, k keeper.Keeper, msg types.MsgChannelOpenAck) sdk.Result { + err := k.ChanOpenAck( + ctx, msg.PortID, msg.ChannelID, msg.CounterpartyVersion, msg.ProofTry, msg.ProofHeight, + ) + if err != nil { + return sdk.ResultFromError(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelOpenAck, + sdk.NewAttribute(types.AttributeKeySenderPort, msg.PortID), + sdk.NewAttribute(types.AttributeKeyChannelID, msg.ChannelID), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Signer.String()), + ), + }) + + return sdk.Result{Events: ctx.EventManager().Events()} +} + +// HandleMsgChannelOpenConfirm defines the sdk.Handler for MsgChannelOpenConfirm +func HandleMsgChannelOpenConfirm(ctx sdk.Context, k keeper.Keeper, msg types.MsgChannelOpenConfirm) sdk.Result { + err := k.ChanOpenConfirm(ctx, msg.PortID, msg.ChannelID, msg.ProofAck, msg.ProofHeight) + if err != nil { + return sdk.ResultFromError(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelOpenConfirm, + sdk.NewAttribute(types.AttributeKeySenderPort, msg.PortID), + sdk.NewAttribute(types.AttributeKeyChannelID, msg.ChannelID), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Signer.String()), + ), + }) + + return sdk.Result{Events: ctx.EventManager().Events()} +} + +// HandleMsgChannelCloseInit defines the sdk.Handler for MsgChannelCloseInit +func HandleMsgChannelCloseInit(ctx sdk.Context, k keeper.Keeper, msg types.MsgChannelCloseInit) sdk.Result { + err := k.ChanCloseInit(ctx, msg.PortID, msg.ChannelID) + if err != nil { + return sdk.ResultFromError(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelCloseInit, + sdk.NewAttribute(types.AttributeKeySenderPort, msg.PortID), + sdk.NewAttribute(types.AttributeKeyChannelID, msg.ChannelID), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Signer.String()), + ), + }) + + return sdk.Result{Events: ctx.EventManager().Events()} +} + +// HandleMsgChannelCloseConfirm defines the sdk.Handler for MsgChannelCloseConfirm +func HandleMsgChannelCloseConfirm(ctx sdk.Context, k keeper.Keeper, msg types.MsgChannelCloseConfirm) sdk.Result { + err := k.ChanCloseConfirm(ctx, msg.PortID, msg.ChannelID, msg.ProofInit, msg.ProofHeight) + if err != nil { + return sdk.ResultFromError(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelCloseConfirm, + sdk.NewAttribute(types.AttributeKeySenderPort, msg.PortID), + sdk.NewAttribute(types.AttributeKeyChannelID, msg.ChannelID), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Signer.String()), + ), + }) + + return sdk.Result{Events: ctx.EventManager().Events()} +} diff --git a/x/ibc/04-channel/keeper/handshake.go b/x/ibc/04-channel/keeper/handshake.go new file mode 100644 index 000000000000..f71115da4792 --- /dev/null +++ b/x/ibc/04-channel/keeper/handshake.go @@ -0,0 +1,410 @@ +package keeper + +import ( + "errors" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" +) + +// CounterpartyHops returns the connection hops of the counterparty channel. +// The counterparty hops are stored in the inverse order as the channel's. +func (k Keeper) CounterpartyHops(ctx sdk.Context, ch types.Channel) ([]string, bool) { + counterPartyHops := make([]string, len(ch.ConnectionHops)) + for i, hop := range ch.ConnectionHops { + connection, found := k.connectionKeeper.GetConnection(ctx, hop) + if !found { + return []string{}, false + } + counterPartyHops[len(counterPartyHops)-1-i] = connection.Counterparty.ConnectionID + } + return counterPartyHops, true +} + +// ChanOpenInit is called by a module to initiate a channel opening handshake with +// a module on another chain. +func (k Keeper) ChanOpenInit( + ctx sdk.Context, + order types.Order, + connectionHops []string, + portID, + channelID string, + counterparty types.Counterparty, + version string, +) error { + // TODO: abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier)) + _, found := k.GetChannel(ctx, portID, channelID) + if found { + return types.ErrChannelExists(k.codespace, channelID) + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, connectionHops[0]) + if !found { + return connection.ErrConnectionNotFound(k.codespace, connectionHops[0]) + } + + if connectionEnd.State == connection.NONE { + return connection.ErrInvalidConnectionState( + k.codespace, + fmt.Sprintf("connection state cannot be NONE"), + ) + } + + /* + // TODO: Maybe not right + key := sdk.NewKVStoreKey(portID) + + if !k.portKeeper.Authenticate(key, portID) { + return errors.New("port is not valid") + } + + */ + channel := types.NewChannel(types.INIT, order, counterparty, connectionHops, version) + k.SetChannel(ctx, portID, channelID, channel) + + // TODO: generate channel capability key and set it to store + k.SetNextSequenceSend(ctx, portID, channelID, 1) + k.SetNextSequenceRecv(ctx, portID, channelID, 1) + + return nil +} + +// ChanOpenTry is called by a module to accept the first step of a channel opening +// handshake initiated by a module on another chain. +func (k Keeper) ChanOpenTry( + ctx sdk.Context, + order types.Order, + connectionHops []string, + portID, + channelID string, + counterparty types.Counterparty, + version, + counterpartyVersion string, + proofInit commitment.ProofI, + proofHeight uint64, +) error { + _, found := k.GetChannel(ctx, portID, channelID) + if found { + return types.ErrChannelExists(k.codespace, channelID) + } + + // TODO: Maybe not right + key := sdk.NewKVStoreKey(portID) + + if !k.portKeeper.Authenticate(key, portID) { + return errors.New("port is not valid") + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, connectionHops[0]) + if !found { + return connection.ErrConnectionNotFound(k.codespace, connectionHops[0]) + } + + if connectionEnd.State != connection.OPEN { + return connection.ErrInvalidConnectionState( + k.codespace, + fmt.Sprintf("connection state is not OPEN (got %s)", connectionEnd.State.String()), + ) + } + + // NOTE: this step has been switched with the one below to reverse the connection + // hops + channel := types.NewChannel(types.OPENTRY, order, counterparty, connectionHops, version) + + counterpartyHops, found := k.CounterpartyHops(ctx, channel) + if !found { + // Should not reach here, connectionEnd was able to be retrieved above + panic("cannot find connection") + } + + // expectedCounterpaty is the counterparty of the counterparty's channel end + // (i.e self) + expectedCounterparty := types.NewCounterparty(portID, channelID) + expectedChannel := types.NewChannel( + types.INIT, channel.Ordering, expectedCounterparty, + counterpartyHops, channel.Version, + ) + + bz, err := k.cdc.MarshalBinaryLengthPrefixed(expectedChannel) + if err != nil { + return errors.New("failed to marshal expected channel") + } + + if !k.connectionKeeper.VerifyMembership( + ctx, connectionEnd, proofHeight, proofInit, + types.ChannelPath(counterparty.PortID, counterparty.ChannelID), + bz, + ) { + return types.ErrInvalidCounterpartyChannel(k.codespace, "channel membership verification failed") + } + + k.SetChannel(ctx, portID, channelID, channel) + + // TODO: generate channel capability key and set it to store + k.SetNextSequenceSend(ctx, portID, channelID, 1) + k.SetNextSequenceRecv(ctx, portID, channelID, 1) + + return nil +} + +// ChanOpenAck is called by the handshake-originating module to acknowledge the +// acceptance of the initial request by the counterparty module on the other chain. +func (k Keeper) ChanOpenAck( + ctx sdk.Context, + portID, + channelID, + counterpartyVersion string, + proofTry commitment.ProofI, + proofHeight uint64, +) error { + channel, found := k.GetChannel(ctx, portID, channelID) + if !found { + return types.ErrChannelNotFound(k.codespace, portID, channelID) + } + + if channel.State != types.INIT { + return types.ErrInvalidChannelState( + k.codespace, + fmt.Sprintf("channel state is not INIT (got %s)", channel.State.String()), + ) + } + + // TODO: Maybe not right + key := sdk.NewKVStoreKey(portID) + + if !k.portKeeper.Authenticate(key, portID) { + return errors.New("port is not valid") + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return connection.ErrConnectionNotFound(k.codespace, channel.ConnectionHops[0]) + } + + if connectionEnd.State != connection.OPEN { + return connection.ErrInvalidConnectionState( + k.codespace, + fmt.Sprintf("connection state is not OPEN (got %s)", connectionEnd.State.String()), + ) + } + + counterpartyHops, found := k.CounterpartyHops(ctx, channel) + if !found { + // Should not reach here, connectionEnd was able to be retrieved above + panic("cannot find connection") + } + + // counterparty of the counterparty channel end (i.e self) + counterparty := types.NewCounterparty(portID, channelID) + expectedChannel := types.NewChannel( + types.OPENTRY, channel.Ordering, counterparty, + counterpartyHops, channel.Version, + ) + + bz, err := k.cdc.MarshalBinaryLengthPrefixed(expectedChannel) + if err != nil { + return errors.New("failed to marshal expected channel") + } + + if !k.connectionKeeper.VerifyMembership( + ctx, connectionEnd, proofHeight, proofTry, + types.ChannelPath(channel.Counterparty.PortID, channel.Counterparty.ChannelID), + bz, + ) { + return types.ErrInvalidCounterpartyChannel(k.codespace, "channel membership verification failed") + } + + channel.State = types.OPEN + channel.Version = counterpartyVersion + k.SetChannel(ctx, portID, channelID, channel) + + return nil +} + +// ChanOpenConfirm is called by the counterparty module to close their end of the +// channel, since the other end has been closed. +func (k Keeper) ChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, + proofAck commitment.ProofI, + proofHeight uint64, +) error { + channel, found := k.GetChannel(ctx, portID, channelID) + if !found { + return types.ErrChannelNotFound(k.codespace, portID, channelID) + } + + if channel.State != types.OPENTRY { + return types.ErrInvalidChannelState( + k.codespace, + fmt.Sprintf("channel state is not OPENTRY (got %s)", channel.State.String()), + ) + } + + // TODO: Maybe not right + key := sdk.NewKVStoreKey(portID) + + if !k.portKeeper.Authenticate(key, portID) { + return errors.New("port is not valid") + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return connection.ErrConnectionNotFound(k.codespace, channel.ConnectionHops[0]) + } + + if connectionEnd.State != connection.OPEN { + return connection.ErrInvalidConnectionState( + k.codespace, + fmt.Sprintf("connection state is not OPEN (got %s)", connectionEnd.State.String()), + ) + } + + counterpartyHops, found := k.CounterpartyHops(ctx, channel) + if !found { + // Should not reach here, connectionEnd was able to be retrieved above + panic("cannot find connection") + } + + counterparty := types.NewCounterparty(portID, channelID) + expectedChannel := types.NewChannel( + types.OPEN, channel.Ordering, counterparty, + counterpartyHops, channel.Version, + ) + + bz, err := k.cdc.MarshalBinaryLengthPrefixed(expectedChannel) + if err != nil { + return errors.New("failed to marshal expected channel") + } + + if !k.connectionKeeper.VerifyMembership( + ctx, connectionEnd, proofHeight, proofAck, + types.ChannelPath(channel.Counterparty.PortID, channel.Counterparty.ChannelID), + bz, + ) { + return types.ErrInvalidCounterpartyChannel(k.codespace, "channel membership verification failed") + } + + channel.State = types.OPEN + k.SetChannel(ctx, portID, channelID, channel) + + return nil +} + +// Closing Handshake +// +// This section defines the set of functions required to close a channel handshake +// as defined in https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#closing-handshake + +// ChanCloseInit is called by either module to close their end of the channel. Once +// closed, channels cannot be reopened. +func (k Keeper) ChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // TODO: Maybe not right + key := sdk.NewKVStoreKey(portID) + + if !k.portKeeper.Authenticate(key, portID) { + return errors.New("port is not valid") + } + + channel, found := k.GetChannel(ctx, portID, channelID) + if !found { + return types.ErrChannelNotFound(k.codespace, portID, channelID) + } + + if channel.State == types.CLOSED { + return types.ErrInvalidChannelState(k.codespace, "channel is already CLOSED") + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return connection.ErrConnectionNotFound(k.codespace, channel.ConnectionHops[0]) + } + + if connectionEnd.State != connection.OPEN { + return connection.ErrInvalidConnectionState( + k.codespace, + fmt.Sprintf("connection state is not OPEN (got %s)", connectionEnd.State.String()), + ) + } + + channel.State = types.CLOSED + k.SetChannel(ctx, portID, channelID, channel) + + return nil +} + +// ChanCloseConfirm is called by the counterparty module to close their end of the +// channel, since the other end has been closed. +func (k Keeper) ChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, + proofInit commitment.ProofI, + proofHeight uint64, +) error { + // TODO: Maybe not right + key := sdk.NewKVStoreKey(portID) + + if !k.portKeeper.Authenticate(key, portID) { + return errors.New("port is not valid") + } + + channel, found := k.GetChannel(ctx, portID, channelID) + if !found { + return types.ErrChannelNotFound(k.codespace, portID, channelID) + } + + if channel.State == types.CLOSED { + return types.ErrInvalidChannelState(k.codespace, "channel is already CLOSED") + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return connection.ErrConnectionNotFound(k.codespace, channel.ConnectionHops[0]) + } + + if connectionEnd.State != connection.OPEN { + return connection.ErrInvalidConnectionState( + k.codespace, + fmt.Sprintf("connection state is not OPEN (got %s)", connectionEnd.State.String()), + ) + } + + counterpartyHops, found := k.CounterpartyHops(ctx, channel) + if !found { + // Should not reach here, connectionEnd was able to be retrieved above + panic("cannot find connection") + } + + counterparty := types.NewCounterparty(portID, channelID) + expectedChannel := types.NewChannel( + types.CLOSED, channel.Ordering, counterparty, + counterpartyHops, channel.Version, + ) + + bz, err := k.cdc.MarshalBinaryLengthPrefixed(expectedChannel) + if err != nil { + return errors.New("failed to marshal expected channel") + } + + if !k.connectionKeeper.VerifyMembership( + ctx, connectionEnd, proofHeight, proofInit, + types.ChannelPath(channel.Counterparty.PortID, channel.Counterparty.ChannelID), + bz, + ) { + return types.ErrInvalidCounterpartyChannel(k.codespace, "channel membership verification failed") + } + + channel.State = types.CLOSED + k.SetChannel(ctx, portID, channelID, channel) + + return nil +} diff --git a/x/ibc/04-channel/keeper/keeper.go b/x/ibc/04-channel/keeper/keeper.go new file mode 100644 index 000000000000..6169c72ffc3a --- /dev/null +++ b/x/ibc/04-channel/keeper/keeper.go @@ -0,0 +1,146 @@ +package keeper + +import ( + "encoding/binary" + "fmt" + + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" + ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +// Keeper defines the IBC channel keeper +type Keeper struct { + storeKey sdk.StoreKey + cdc *codec.Codec + codespace sdk.CodespaceType + prefix []byte // prefix bytes for accessing the store + + clientKeeper types.ClientKeeper + connectionKeeper types.ConnectionKeeper + portKeeper types.PortKeeper +} + +// NewKeeper creates a new IBC channel Keeper instance +func NewKeeper( + cdc *codec.Codec, key sdk.StoreKey, codespace sdk.CodespaceType, + clientKeeper types.ClientKeeper, connectionKeeper types.ConnectionKeeper, + portKeeper types.PortKeeper, +) Keeper { + return Keeper{ + storeKey: key, + cdc: cdc, + codespace: sdk.CodespaceType(fmt.Sprintf("%s/%s", codespace, types.DefaultCodespace)), // "ibc/channel", + prefix: []byte{}, + // prefix: []byte(types.SubModuleName + "/"), // "channel/" + clientKeeper: clientKeeper, + connectionKeeper: connectionKeeper, + portKeeper: portKeeper, + } +} + +// Logger returns a module-specific logger. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s/%s", ibctypes.ModuleName, types.SubModuleName)) +} + +// GetChannel returns a channel with a particular identifier binded to a specific port +func (k Keeper) GetChannel(ctx sdk.Context, portID, channelID string) (types.Channel, bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := store.Get(types.KeyChannel(portID, channelID)) + if bz == nil { + return types.Channel{}, false + } + + var channel types.Channel + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &channel) + return channel, true +} + +// SetChannel sets a channel to the store +func (k Keeper) SetChannel(ctx sdk.Context, portID, channelID string, channel types.Channel) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(channel) + store.Set(types.KeyChannel(portID, channelID), bz) +} + +// GetChannelCapability gets a channel's capability key from the store +func (k Keeper) GetChannelCapability(ctx sdk.Context, portID, channelID string) (string, bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := store.Get(types.KeyChannelCapabilityPath(portID, channelID)) + if bz == nil { + return "", false + } + + return string(bz), true +} + +// SetChannelCapability sets a channel's capability key to the store +func (k Keeper) SetChannelCapability(ctx sdk.Context, portID, channelID string, key string) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + store.Set(types.KeyChannelCapabilityPath(portID, channelID), []byte(key)) +} + +// GetNextSequenceSend gets a channel's next send sequence from the store +func (k Keeper) GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := store.Get(types.KeyNextSequenceSend(portID, channelID)) + if bz == nil { + return 0, false + } + + return binary.BigEndian.Uint64(bz), true +} + +// SetNextSequenceSend sets a channel's next send sequence to the store +func (k Keeper) SetNextSequenceSend(ctx sdk.Context, portID, channelID string, sequence uint64) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := sdk.Uint64ToBigEndian(sequence) + store.Set(types.KeyNextSequenceSend(portID, channelID), bz) +} + +// GetNextSequenceRecv gets a channel's next receive sequence from the store +func (k Keeper) GetNextSequenceRecv(ctx sdk.Context, portID, channelID string) (uint64, bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := store.Get(types.KeyNextSequenceRecv(portID, channelID)) + if bz == nil { + return 0, false + } + + return binary.BigEndian.Uint64(bz), true +} + +// SetNextSequenceRecv sets a channel's next receive sequence to the store +func (k Keeper) SetNextSequenceRecv(ctx sdk.Context, portID, channelID string, sequence uint64) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := sdk.Uint64ToBigEndian(sequence) + store.Set(types.KeyNextSequenceRecv(portID, channelID), bz) +} + +// GetPacketCommitment gets the packet commitment hash from the store +func (k Keeper) GetPacketCommitment(ctx sdk.Context, portID, channelID string, sequence uint64) []byte { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + bz := store.Get(types.KeyPacketCommitment(portID, channelID, sequence)) + return bz +} + +// SetPacketCommitment sets the packet commitment hash to the store +func (k Keeper) SetPacketCommitment(ctx sdk.Context, portID, channelID string, sequence uint64, commitmentHash []byte) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + store.Set(types.KeyPacketCommitment(portID, channelID, sequence), commitmentHash) +} + +func (k Keeper) deletePacketCommitment(ctx sdk.Context, portID, channelID string, sequence uint64) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + store.Delete(types.KeyPacketCommitment(portID, channelID, sequence)) +} + +// SetPacketAcknowledgement sets the packet ack hash to the store +func (k Keeper) SetPacketAcknowledgement(ctx sdk.Context, portID, channelID string, sequence uint64, ackHash []byte) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), k.prefix) + store.Set(types.KeyPacketAcknowledgement(portID, channelID, sequence), ackHash) +} diff --git a/x/ibc/04-channel/keeper/packet.go b/x/ibc/04-channel/keeper/packet.go new file mode 100644 index 000000000000..0590f3cfd37c --- /dev/null +++ b/x/ibc/04-channel/keeper/packet.go @@ -0,0 +1,357 @@ +package keeper + +import ( + "bytes" + "errors" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" + connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" +) + +// CleanupPacket is called by a module to remove a received packet commitment +// from storage. The receiving end must have already processed the packet +// (whether regularly or past timeout). +// +// In the ORDERED channel case, CleanupPacket cleans-up a packet on an ordered +// channel by proving that the packet has been received on the other end. +// +// In the UNORDERED channel case, CleanupPacket cleans-up a packet on an +// unordered channel by proving that the associated acknowledgement has been +//written. +func (k Keeper) CleanupPacket( + ctx sdk.Context, + packet exported.PacketI, + proof commitment.ProofI, + proofHeight, + nextSequenceRecv uint64, + acknowledgement []byte, + portCapability sdk.CapabilityKey, +) (exported.PacketI, error) { + channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return nil, types.ErrChannelNotFound(k.codespace, packet.GetSourcePort(), packet.GetSourceChannel()) + } + + if channel.State != types.OPEN { + return nil, types.ErrInvalidChannelState( + k.codespace, + fmt.Sprintf("channel state is not OPEN (got %s)", channel.State.String()), + ) + } + + _, found = k.GetChannelCapability(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return nil, types.ErrChannelCapabilityNotFound(k.codespace) + } + + if !k.portKeeper.Authenticate(portCapability, packet.GetSourcePort()) { + return nil, errors.New("port is not valid") + } + + if packet.GetDestChannel() != channel.Counterparty.ChannelID { + return nil, types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet destination channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetDestChannel(), channel.Counterparty.ChannelID), + ) + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return nil, connection.ErrConnectionNotFound(k.codespace, channel.ConnectionHops[0]) + } + + if packet.GetDestPort() != channel.Counterparty.PortID { + return nil, types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet destination port doesn't match the counterparty's port (%s ≠ %s)", packet.GetDestPort(), channel.Counterparty.PortID), + ) + } + + if nextSequenceRecv >= packet.GetSequence() { + return nil, types.ErrInvalidPacket(k.codespace, "packet already received") + } + + commitment := k.GetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + if !bytes.Equal(commitment, packet.GetData()) { // TODO: hash packet data + return nil, types.ErrInvalidPacket(k.codespace, "packet hasn't been sent") + } + + var ok bool + switch channel.Ordering { + case types.ORDERED: + ok = k.connectionKeeper.VerifyMembership( + ctx, connectionEnd, proofHeight, proof, + types.NextSequenceRecvPath(packet.GetDestPort(), packet.GetDestChannel()), + sdk.Uint64ToBigEndian(nextSequenceRecv), + ) + case types.UNORDERED: + ok = k.connectionKeeper.VerifyMembership( + ctx, connectionEnd, proofHeight, proof, + types.PacketAcknowledgementPath(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()), + acknowledgement, + ) + default: + panic(fmt.Sprintf("invalid channel ordering type %v", channel.Ordering)) + } + + if !ok { + return nil, types.ErrInvalidPacket(k.codespace, "packet verification failed") + } + + k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + return packet, nil +} + +// SendPacket is called by a module in order to send an IBC packet on a channel +// end owned by the calling module to the corresponding module on the counterparty +// chain. +func (k Keeper) SendPacket( + ctx sdk.Context, + packet exported.PacketI, + portCapability sdk.CapabilityKey, +) error { + if err := packet.ValidateBasic(); err != nil { + return err + } + + channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return types.ErrChannelNotFound(k.codespace, packet.GetSourcePort(), packet.GetSourceChannel()) + } + + if channel.State == types.CLOSED { + return types.ErrInvalidChannelState( + k.codespace, + fmt.Sprintf("channel is CLOSED (got %s)", channel.State.String()), + ) + } + + if !k.portKeeper.Authenticate(portCapability, packet.GetSourcePort()) { + return errors.New("port is not valid") + } + + if packet.GetDestPort() != channel.Counterparty.PortID { + return types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet destination port doesn't match the counterparty's port (%s ≠ %s)", packet.GetDestPort(), channel.Counterparty.PortID), + ) + } + + if packet.GetDestChannel() != channel.Counterparty.ChannelID { + return types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet destination channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetDestChannel(), channel.Counterparty.ChannelID), + ) + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return connection.ErrConnectionNotFound(k.codespace, channel.ConnectionHops[0]) + } + + if connectionEnd.State == connection.NONE { + return connection.ErrInvalidConnectionState( + k.codespace, + fmt.Sprintf("connection is closed (i.e NONE)"), + ) + } + + consensusState, found := k.clientKeeper.GetConsensusState(ctx, connectionEnd.ClientID) + if !found { + return client.ErrConsensusStateNotFound(k.codespace) + } + + if consensusState.GetHeight() >= packet.GetTimeoutHeight() { + return types.ErrPacketTimeout(k.codespace) + } + + nextSequenceSend, found := k.GetNextSequenceSend(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return types.ErrSequenceNotFound(k.codespace, "send") + } + + if packet.GetSequence() != nextSequenceSend { + return types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet sequence ≠ next send sequence (%d ≠ %d)", packet.GetSequence(), nextSequenceSend), + ) + } + + nextSequenceSend++ + k.SetNextSequenceSend(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), nextSequenceSend) + k.SetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), packet.GetData()) // TODO: hash packet data + + return nil +} + +// RecvPacket is called by a module in order to receive & process an IBC packet +// sent on the corresponding channel end on the counterparty chain. +func (k Keeper) RecvPacket( + ctx sdk.Context, + packet exported.PacketI, + proof commitment.ProofI, + proofHeight uint64, + acknowledgement []byte, + portCapability sdk.CapabilityKey, +) (exported.PacketI, error) { + + channel, found := k.GetChannel(ctx, packet.GetDestPort(), packet.GetDestChannel()) + if !found { + return nil, types.ErrChannelNotFound(k.codespace, packet.GetDestPort(), packet.GetDestChannel()) + } + + if channel.State != types.OPEN { + return nil, types.ErrInvalidChannelState( + k.codespace, + fmt.Sprintf("channel state is not OPEN (got %s)", channel.State.String()), + ) + } + + if !k.portKeeper.Authenticate(portCapability, packet.GetDestPort()) { + return nil, errors.New("port is not valid") + } + + // packet must come from the channel's counterparty + if packet.GetSourcePort() != channel.Counterparty.PortID { + return nil, types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet source port doesn't match the counterparty's port (%s ≠ %s)", packet.GetSourcePort(), channel.Counterparty.PortID), + ) + } + + if packet.GetSourceChannel() != channel.Counterparty.ChannelID { + return nil, types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet source channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetSourceChannel(), channel.Counterparty.ChannelID), + ) + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return nil, connection.ErrConnectionNotFound(k.codespace, channel.ConnectionHops[0]) + } + + if connectionEnd.State != connection.OPEN { + return nil, connection.ErrInvalidConnectionState( + k.codespace, + fmt.Sprintf("connection state is not OPEN (got %s)", connectionEnd.State.String()), + ) + } + + if uint64(ctx.BlockHeight()) >= packet.GetTimeoutHeight() { + return nil, types.ErrPacketTimeout(k.codespace) + } + + if !k.connectionKeeper.VerifyMembership( + ctx, connectionEnd, proofHeight, proof, + types.PacketCommitmentPath(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()), + packet.GetData(), // TODO: hash data + ) { + return nil, errors.New("couldn't verify counterparty packet commitment") + } + + if len(acknowledgement) > 0 || channel.Ordering == types.UNORDERED { + k.SetPacketAcknowledgement( + ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), + acknowledgement, // TODO: hash ACK + ) + } + + if channel.Ordering == types.ORDERED { + nextSequenceRecv, found := k.GetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel()) + if !found { + return nil, types.ErrSequenceNotFound(k.codespace, "receive") + } + + if packet.GetSequence() != nextSequenceRecv { + return nil, types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet sequence ≠ next receive sequence (%d ≠ %d)", packet.GetSequence(), nextSequenceRecv), + ) + } + + nextSequenceRecv++ + + k.SetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel(), nextSequenceRecv) + } + + return packet, nil +} + +// AcknowledgePacket is called by a module to process the acknowledgement of a +// packet previously sent by the calling module on a channel to a counterparty +// module on the counterparty chain. acknowledgePacket also cleans up the packet +// commitment, which is no longer necessary since the packet has been received +// and acted upon. +func (k Keeper) AcknowledgePacket( + ctx sdk.Context, + packet exported.PacketI, + acknowledgement []byte, + proof commitment.ProofI, + proofHeight uint64, + portCapability sdk.CapabilityKey, +) (exported.PacketI, error) { + channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return nil, types.ErrChannelNotFound(k.codespace, packet.GetSourcePort(), packet.GetSourceChannel()) + } + + if channel.State != types.OPEN { + return nil, types.ErrInvalidChannelState( + k.codespace, + fmt.Sprintf("channel state is not OPEN (got %s)", channel.State.String()), + ) + } + + if !k.portKeeper.Authenticate(portCapability, packet.GetSourcePort()) { + return nil, errors.New("invalid capability key") + } + + // packet must come from the channel's counterparty + if packet.GetSourcePort() != channel.Counterparty.PortID { + return nil, types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet source port doesn't match the counterparty's port (%s ≠ %s)", packet.GetSourcePort(), channel.Counterparty.PortID), + ) + } + + if packet.GetSourceChannel() != channel.Counterparty.ChannelID { + return nil, types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet source channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetSourceChannel(), channel.Counterparty.ChannelID), + ) + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return nil, connection.ErrConnectionNotFound(k.codespace, channel.ConnectionHops[0]) + } + + if connectionEnd.State != connection.OPEN { + return nil, connection.ErrInvalidConnectionState( + k.codespace, + fmt.Sprintf("connection state is not OPEN (got %s)", connectionEnd.State.String()), + ) + } + + commitment := k.GetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + if !bytes.Equal(commitment, packet.GetData()) { // TODO: hash packet data + return nil, types.ErrInvalidPacket(k.codespace, "packet hasn't been sent") + } + + if !k.connectionKeeper.VerifyMembership( + ctx, connectionEnd, proofHeight, proof, + types.PacketAcknowledgementPath(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()), + acknowledgement, // TODO: hash ACK + ) { + return nil, errors.New("invalid acknowledgement on counterparty chain") + } + + k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + return packet, nil +} diff --git a/x/ibc/04-channel/keeper/querier.go b/x/ibc/04-channel/keeper/querier.go new file mode 100644 index 000000000000..3618c5f29fd4 --- /dev/null +++ b/x/ibc/04-channel/keeper/querier.go @@ -0,0 +1,32 @@ +package keeper + +import ( + "fmt" + + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" +) + +// QuerierChannel defines the sdk.Querier to query a module's channel +func QuerierChannel(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params types.QueryChannelParams + + err := types.SubModuleCdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) + } + + channel, found := k.GetChannel(ctx, params.PortID, params.ChannelID) + if !found { + return nil, types.ErrChannelNotFound(k.codespace, params.PortID, params.ChannelID) + } + + bz, err := types.SubModuleCdc.MarshalJSON(channel) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + + return bz, nil +} diff --git a/x/ibc/04-channel/keeper/timeout.go b/x/ibc/04-channel/keeper/timeout.go new file mode 100644 index 000000000000..3ae481373de3 --- /dev/null +++ b/x/ibc/04-channel/keeper/timeout.go @@ -0,0 +1,195 @@ +package keeper + +import ( + "bytes" + "errors" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" +) + +// TimeoutPacket is called by a module which originally attempted to send a +// packet to a counterparty module, where the timeout height has passed on the +// counterparty chain without the packet being committed, to prove that the +// packet can no longer be executed and to allow the calling module to safely +// perform appropriate state transitions. +func (k Keeper) TimeoutPacket( + ctx sdk.Context, + packet exported.PacketI, + proof commitment.ProofI, + proofHeight uint64, + nextSequenceRecv uint64, + portCapability sdk.CapabilityKey, +) (exported.PacketI, error) { + channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return nil, types.ErrChannelNotFound(k.codespace, packet.GetSourcePort(), packet.GetSourceChannel()) + } + + if channel.State != types.OPEN { + return nil, types.ErrInvalidChannelState( + k.codespace, + fmt.Sprintf("channel state is not OPEN (got %s)", channel.State.String()), + ) + } + + _, found = k.GetChannelCapability(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return nil, types.ErrChannelCapabilityNotFound(k.codespace) + } + + if !k.portKeeper.Authenticate(portCapability, packet.GetSourcePort()) { + return nil, errors.New("port is not valid") + } + + if packet.GetDestChannel() != channel.Counterparty.ChannelID { + return nil, types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet destination channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetDestChannel(), channel.Counterparty.ChannelID), + ) + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return nil, connection.ErrConnectionNotFound(k.codespace, channel.ConnectionHops[0]) + } + + if packet.GetDestPort() != channel.Counterparty.PortID { + return nil, types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet destination port doesn't match the counterparty's port (%s ≠ %s)", packet.GetDestPort(), channel.Counterparty.PortID), + ) + } + + if proofHeight < packet.GetTimeoutHeight() { + return nil, types.ErrPacketTimeout(k.codespace) + } + + if nextSequenceRecv >= packet.GetSequence() { + return nil, types.ErrInvalidPacket(k.codespace, "packet already received") + } + + commitment := k.GetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + if !bytes.Equal(commitment, packet.GetData()) { // TODO: hash packet data + return nil, types.ErrInvalidPacket(k.codespace, "packet hasn't been sent") + } + + var ok bool + switch channel.Ordering { + case types.ORDERED: + ok = k.connectionKeeper.VerifyMembership( + ctx, connectionEnd, proofHeight, proof, + types.NextSequenceRecvPath(packet.GetDestPort(), packet.GetDestChannel()), + sdk.Uint64ToBigEndian(nextSequenceRecv), + ) + case types.UNORDERED: + ok = k.connectionKeeper.VerifyNonMembership( + ctx, connectionEnd, proofHeight, proof, + types.PacketAcknowledgementPath(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()), + ) + default: + panic(fmt.Sprintf("invalid channel ordering type %v", channel.Ordering)) + } + + if !ok { + return nil, types.ErrInvalidPacket(k.codespace, "packet verification failed") + } + + k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + + if channel.Ordering == types.ORDERED { + channel.State = types.CLOSED + k.SetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), channel) + } + + return packet, nil +} + +// TimeoutOnClose is called by a module in order to prove that the channel to +// which an unreceived packet was addressed has been closed, so the packet will +// never be received (even if the timeoutHeight has not yet been reached). +func (k Keeper) TimeoutOnClose( + ctx sdk.Context, + packet exported.PacketI, + proofNonMembership, + proofClosed commitment.ProofI, + proofHeight uint64, + portCapability sdk.CapabilityKey, +) (exported.PacketI, error) { + channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return nil, types.ErrChannelNotFound(k.codespace, packet.GetSourcePort(), packet.GetSourceChannel()) + } + + _, found = k.GetChannelCapability(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return nil, types.ErrChannelCapabilityNotFound(k.codespace) + } + + if !k.portKeeper.Authenticate(portCapability, packet.GetSourcePort()) { + return nil, errors.New("port is not valid") + } + + if packet.GetDestChannel() != channel.Counterparty.ChannelID { + return nil, types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet destination channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetDestChannel(), channel.Counterparty.ChannelID), + ) + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return nil, connection.ErrConnectionNotFound(k.codespace, channel.ConnectionHops[0]) + } + + if packet.GetDestPort() != channel.Counterparty.PortID { + return nil, types.ErrInvalidPacket( + k.codespace, + fmt.Sprintf("packet destination port doesn't match the counterparty's port (%s ≠ %s)", packet.GetDestPort(), channel.Counterparty.PortID), + ) + } + + commitment := k.GetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + if !bytes.Equal(commitment, packet.GetData()) { // TODO: hash packet data + return nil, types.ErrInvalidPacket(k.codespace, "packet hasn't been sent") + } + + counterpartyHops, found := k.CounterpartyHops(ctx, channel) + if !found { + // Should not reach here, connectionEnd was able to be retrieved above + panic("cannot find connection") + } + + counterparty := types.NewCounterparty(packet.GetSourcePort(), packet.GetSourceChannel()) + expectedChannel := types.NewChannel( + types.CLOSED, channel.Ordering, counterparty, counterpartyHops, channel.Version, + ) + + bz, err := k.cdc.MarshalBinaryLengthPrefixed(expectedChannel) + if err != nil { + return nil, errors.New("failed to marshal expected channel") + } + + if !k.connectionKeeper.VerifyMembership( + ctx, connectionEnd, proofHeight, proofClosed, + types.ChannelPath(channel.Counterparty.PortID, channel.Counterparty.ChannelID), + bz, + ) { + return nil, types.ErrInvalidCounterpartyChannel(k.codespace, "channel membership verification failed") + } + + if !k.connectionKeeper.VerifyNonMembership( + ctx, connectionEnd, proofHeight, proofNonMembership, + types.PacketAcknowledgementPath(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()), + ) { + return nil, errors.New("cannot verify absence of acknowledgement at packet index") + } + + k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + + return packet, nil +} diff --git a/x/ibc/04-channel/module.go b/x/ibc/04-channel/module.go new file mode 100644 index 000000000000..4f9b17b03144 --- /dev/null +++ b/x/ibc/04-channel/module.go @@ -0,0 +1,25 @@ +package channel + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/client/cli" +) + +// Name returns the IBC connection ICS name +func Name() string { + return SubModuleName +} + +// GetTxCmd returns the root tx command for the IBC connections. +func GetTxCmd(cdc *codec.Codec, storeKey string) *cobra.Command { + return cli.GetTxCmd(fmt.Sprintf("%s/%s", storeKey, SubModuleName), cdc) +} + +// GetQueryCmd returns no root query command for the IBC connections. +func GetQueryCmd(cdc *codec.Codec, queryRoute string) *cobra.Command { + return cli.GetQueryCmd(fmt.Sprintf("%s/%s", queryRoute, SubModuleName), cdc) +} diff --git a/x/ibc/04-channel/types/channel.go b/x/ibc/04-channel/types/channel.go new file mode 100644 index 000000000000..98bbb9b44f1e --- /dev/null +++ b/x/ibc/04-channel/types/channel.go @@ -0,0 +1,225 @@ +package types + +import ( + "encoding/json" + "fmt" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" + host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" +) + +type Channel struct { + State State `json:"state" yaml:"state"` + Ordering Order `json:"ordering" yaml:"ordering"` + Counterparty Counterparty `json:"counterparty" yaml:"counterparty"` + ConnectionHops []string `json:"connection_hops" yaml:"connection_hops"` + Version string `json:"version" yaml:"version "` +} + +// NewChannel creates a new Channel instance +func NewChannel( + state State, ordering Order, counterparty Counterparty, + hops []string, version string, +) Channel { + return Channel{ + State: state, + Ordering: ordering, + Counterparty: counterparty, + ConnectionHops: hops, + Version: version, + } +} + +// ValidateBasic performs a basic validation of the channel fields +func (ch Channel) ValidateBasic() sdk.Error { + if ch.State.String() == "" { + return ErrInvalidChannelState( + DefaultCodespace, + "channel order should be either 'ORDERED' or 'UNORDERED'", + ) + } + if ch.Ordering.String() == "" { + return ErrInvalidChannel( + DefaultCodespace, + "channel order should be either 'ORDERED' or 'UNORDERED'", + ) + } + if len(ch.ConnectionHops) != 1 { + return ErrInvalidChannel(DefaultCodespace, "IBC v1 only supports one connection hop") + } + if err := host.DefaultConnectionIdentifierValidator(ch.ConnectionHops[0]); err != nil { + return ErrInvalidChannel(DefaultCodespace, errors.Wrap(err, "invalid connection hop ID").Error()) + } + if strings.TrimSpace(ch.Version) == "" { + return ErrInvalidChannel(DefaultCodespace, "channel version can't be blank") + } + return ch.Counterparty.ValidateBasic() +} + +// Counterparty defines the counterparty chain's channel and port identifiers +type Counterparty struct { + PortID string `json:"port_id" yaml:"port_id"` + ChannelID string `json:"channel_id" yaml:"channel_id"` +} + +// NewCounterparty returns a new Counterparty instance +func NewCounterparty(portID, channelID string) Counterparty { + return Counterparty{ + PortID: portID, + ChannelID: channelID, + } +} + +// ValidateBasic performs a basic validation check of the identifiers +func (c Counterparty) ValidateBasic() sdk.Error { + if err := host.DefaultPortIdentifierValidator(c.PortID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid counterparty connection ID: %s", err.Error())) + } + if err := host.DefaultChannelIdentifierValidator(c.ChannelID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid counterparty client ID: %s", err.Error())) + } + return nil +} + +// Order defines if a channel is ORDERED or UNORDERED +type Order byte + +// string representation of the channel ordering +const ( + NONE Order = iota // zero-value for channel ordering + UNORDERED // packets can be delivered in any order, which may differ from the order in which they were sent. + ORDERED // packets are delivered exactly in the order which they were sent +) + +// channel order types +const ( + OrderNone string = "NONE" + OrderUnordered string = "UNORDERED" + OrderOrdered string = "ORDERED" +) + +// String implements the Stringer interface +func (o Order) String() string { + switch o { + case NONE: + return OrderNone + case UNORDERED: + return OrderUnordered + case ORDERED: + return OrderOrdered + default: + return "" + } +} + +// MarshalJSON marshal to JSON using string. +func (o Order) MarshalJSON() ([]byte, error) { + return json.Marshal(o.String()) +} + +// UnmarshalJSON decodes from JSON. +func (o *Order) UnmarshalJSON(data []byte) error { + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return err + } + + bz2, err := OrderFromString(s) + if err != nil { + return err + } + + *o = bz2 + return nil +} + +// OrderFromString parses a string into a channel order byte +func OrderFromString(order string) (Order, error) { + switch order { + case OrderNone: + return NONE, nil + case OrderUnordered: + return UNORDERED, nil + case OrderOrdered: + return ORDERED, nil + default: + return 0, fmt.Errorf("'%s' is not a valid channel ordering", order) + } +} + +// State defines if a channel is in one of the following states: +// CLOSED, INIT, OPENTRY or OPEN +type State byte + +// channel state types +const ( + CLOSED State = iota + 1 // A channel end has been closed and can no longer be used to send or receive packets. + INIT // A channel end has just started the opening handshake. + OPENTRY // A channel end has acknowledged the handshake step on the counterparty chain. + OPEN // A channel end has completed the handshake and is ready to send and receive packets. +) + +// string representation of the channel states +const ( + StateClosed string = "CLOSED" + StateInit string = "INIT" + StateOpenTry string = "OPENTRY" + StateOpen string = "OPEN" +) + +// String implements the Stringer interface +func (s State) String() string { + switch s { + case CLOSED: + return StateClosed + case INIT: + return StateInit + case OPENTRY: + return StateOpenTry + case OPEN: + return StateOpen + default: + return "" + } +} + +// MarshalJSON marshal to JSON using string. +func (s State) MarshalJSON() ([]byte, error) { + return json.Marshal(s.String()) +} + +// UnmarshalJSON decodes from JSON. +func (s *State) UnmarshalJSON(data []byte) error { + var stateStr string + err := json.Unmarshal(data, &stateStr) + if err != nil { + return err + } + + bz2, err := StateFromString(stateStr) + if err != nil { + return err + } + + *s = bz2 + return nil +} + +// StateFromString parses a string into a channel state byte +func StateFromString(state string) (State, error) { + switch state { + case StateClosed: + return CLOSED, nil + case StateInit: + return INIT, nil + case StateOpenTry: + return OPENTRY, nil + case StateOpen: + return OPEN, nil + default: + return CLOSED, fmt.Errorf("'%s' is not a valid channel state", state) + } +} diff --git a/x/ibc/04-channel/types/codec.go b/x/ibc/04-channel/types/codec.go new file mode 100644 index 000000000000..0839edede775 --- /dev/null +++ b/x/ibc/04-channel/types/codec.go @@ -0,0 +1,28 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" +) + +// SubModuleCdc defines the IBC channel codec. +var SubModuleCdc = codec.New() + +// RegisterCodec registers all the necessary types and interfaces for the +// IBC channel. +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterInterface((*exported.PacketI)(nil), nil) + cdc.RegisterConcrete(Packet{}, "ibc/channel/Packet", nil) + cdc.RegisterConcrete(OpaquePacket{}, "ibc/channel/OpaquePacket", nil) + + cdc.RegisterConcrete(MsgChannelOpenInit{}, "ibc/channel/MsgChannelOpenInit", nil) + cdc.RegisterConcrete(MsgChannelOpenTry{}, "ibc/channel/MsgChannelOpenTry", nil) + cdc.RegisterConcrete(MsgChannelOpenAck{}, "ibc/channel/MsgChannelOpenAck", nil) + cdc.RegisterConcrete(MsgChannelOpenConfirm{}, "ibc/channel/MsgChannelOpenConfirm", nil) + cdc.RegisterConcrete(MsgChannelCloseInit{}, "ibc/channel/MsgChannelCloseInit", nil) + cdc.RegisterConcrete(MsgChannelCloseConfirm{}, "ibc/channel/MsgChannelCloseConfirm", nil) +} + +func init() { + RegisterCodec(SubModuleCdc) +} diff --git a/x/ibc/04-channel/types/errors.go b/x/ibc/04-channel/types/errors.go new file mode 100644 index 000000000000..37d3925b513e --- /dev/null +++ b/x/ibc/04-channel/types/errors.go @@ -0,0 +1,73 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// channel error codes +const ( + DefaultCodespace sdk.CodespaceType = SubModuleName + + CodeChannelExists sdk.CodeType = 101 + CodeChannelNotFound sdk.CodeType = 102 + CodeInvalidCounterpartyChannel sdk.CodeType = 103 + CodeChannelCapabilityNotFound sdk.CodeType = 104 + CodeInvalidPacket sdk.CodeType = 105 + CodeSequenceNotFound sdk.CodeType = 106 + CodePacketTimeout sdk.CodeType = 107 + CodeInvalidChannel sdk.CodeType = 108 + CodeInvalidChannelState sdk.CodeType = 109 + CodeInvalidChannelProof sdk.CodeType = 110 +) + +// ErrChannelExists implements sdk.Error +func ErrChannelExists(codespace sdk.CodespaceType, channelID string) sdk.Error { + return sdk.NewError(codespace, CodeChannelExists, fmt.Sprintf("channel with ID %s already exists", channelID)) +} + +// ErrChannelNotFound implements sdk.Error +func ErrChannelNotFound(codespace sdk.CodespaceType, portID, channelID string) sdk.Error { + return sdk.NewError(codespace, CodeChannelNotFound, fmt.Sprintf("channel with ID %s on port %s not found", channelID, portID)) +} + +// ErrInvalidCounterpartyChannel implements sdk.Error +func ErrInvalidCounterpartyChannel(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidCounterpartyChannel, msg) +} + +// ErrChannelCapabilityNotFound implements sdk.Error +func ErrChannelCapabilityNotFound(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeChannelCapabilityNotFound, "channel capability key not found") +} + +// ErrInvalidPacket implements sdk.Error +func ErrInvalidPacket(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidPacket, msg) +} + +// ErrSequenceNotFound implements sdk.Error +func ErrSequenceNotFound(codespace sdk.CodespaceType, seqType string) sdk.Error { + return sdk.NewError(codespace, CodeSequenceNotFound, fmt.Sprintf("next %s sequence counter not found", seqType)) +} + +// ErrPacketTimeout implements sdk.Error +func ErrPacketTimeout(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodePacketTimeout, "packet timeout") +} + +// ErrInvalidChannel implements sdk.Error +func ErrInvalidChannel(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidChannel, msg) +} + +// ErrInvalidChannelState implements sdk.Error +func ErrInvalidChannelState(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidChannelState, msg) +} + +// ErrInvalidChannelProof implements sdk.Error +func ErrInvalidChannelProof(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidChannelProof, msg) +} diff --git a/x/ibc/04-channel/types/events.go b/x/ibc/04-channel/types/events.go new file mode 100644 index 000000000000..b82b3e8700cf --- /dev/null +++ b/x/ibc/04-channel/types/events.go @@ -0,0 +1,27 @@ +package types + +import ( + "fmt" + + ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +// IBC channel events +const ( + AttributeKeySenderPort = "sender_port" + AttributeKeyReceiverPort = "receiver_port" + AttributeKeyChannelID = "channel_id" + AttributeKeySequence = "sequence" +) + +// IBC channel events vars +var ( + EventTypeChannelOpenInit = MsgChannelOpenInit{}.Type() + EventTypeChannelOpenTry = MsgChannelOpenTry{}.Type() + EventTypeChannelOpenAck = MsgChannelOpenAck{}.Type() + EventTypeChannelOpenConfirm = MsgChannelOpenConfirm{}.Type() + EventTypeChannelCloseInit = MsgChannelCloseInit{}.Type() + EventTypeChannelCloseConfirm = MsgChannelCloseConfirm{}.Type() + + AttributeValueCategory = fmt.Sprintf("%s_%s", ibctypes.ModuleName, SubModuleName) +) diff --git a/x/ibc/04-channel/types/expected_keepers.go b/x/ibc/04-channel/types/expected_keepers.go new file mode 100644 index 000000000000..ab88a2284f27 --- /dev/null +++ b/x/ibc/04-channel/types/expected_keepers.go @@ -0,0 +1,31 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" +) + +// ClientKeeper expected account IBC client keeper +type ClientKeeper interface { + GetConsensusState(ctx sdk.Context, clientID string) (clientexported.ConsensusState, bool) +} + +// ConnectionKeeper expected account IBC connection keeper +type ConnectionKeeper interface { + GetConnection(ctx sdk.Context, connectionID string) (connection.ConnectionEnd, bool) + VerifyMembership( + ctx sdk.Context, connection connection.ConnectionEnd, height uint64, + proof commitment.ProofI, path string, value []byte, + ) bool + VerifyNonMembership( + ctx sdk.Context, connection connection.ConnectionEnd, height uint64, + proof commitment.ProofI, path string, + ) bool +} + +// PortKeeper expected account IBC port keeper +type PortKeeper interface { + Authenticate(key sdk.CapabilityKey, portID string) bool +} diff --git a/x/ibc/04-channel/types/keys.go b/x/ibc/04-channel/types/keys.go new file mode 100644 index 000000000000..f308640b2e32 --- /dev/null +++ b/x/ibc/04-channel/types/keys.go @@ -0,0 +1,85 @@ +package types + +import ( + "fmt" +) + +const ( + // SubModuleName defines the IBC channels name + SubModuleName = "channels" + + // StoreKey is the store key string for IBC channels + StoreKey = SubModuleName + + // RouterKey is the message route for IBC channels + RouterKey = SubModuleName + + // QuerierRoute is the querier route for IBC channels + QuerierRoute = SubModuleName +) + +// ChannelPath defines the path under which channels are stored +func ChannelPath(portID, channelID string) string { + return fmt.Sprintf("ports/%s/channels/%s", portID, channelID) +} + +// ChannelCapabilityPath defines the path under which capability keys associated +// with a channel are stored +func ChannelCapabilityPath(portID, channelID string) string { + return fmt.Sprintf("%s/key", ChannelPath(portID, channelID)) +} + +// NextSequenceSendPath defines the next send sequence counter store path +func NextSequenceSendPath(portID, channelID string) string { + return fmt.Sprintf("%s/nextSequenceSend", ChannelPath(portID, channelID)) +} + +// NextSequenceRecvPath defines the next receive sequence counter store path +func NextSequenceRecvPath(portID, channelID string) string { + return fmt.Sprintf("%s/nextSequenceRecv", ChannelPath(portID, channelID)) +} + +// PacketCommitmentPath defines the commitments to packet data fields store path +func PacketCommitmentPath(portID, channelID string, sequence uint64) string { + return fmt.Sprintf("%s/packets/%d", ChannelPath(portID, channelID), sequence) +} + +// PacketAcknowledgementPath defines the packet acknowledgement store path +func PacketAcknowledgementPath(portID, channelID string, sequence uint64) string { + return fmt.Sprintf("%s/acknowledgements/%d", ChannelPath(portID, channelID), sequence) +} + +// KeyChannel returns the store key for a particular channel +func KeyChannel(portID, channelID string) []byte { + return []byte(ChannelPath(portID, channelID)) +} + +// KeyChannelCapabilityPath returns the store key for the capability key of a +// particular channel binded to a specific port +func KeyChannelCapabilityPath(portID, channelID string) []byte { + return []byte(ChannelCapabilityPath(portID, channelID)) +} + +// KeyNextSequenceSend returns the store key for the send sequence of a particular +// channel binded to a specific port +func KeyNextSequenceSend(portID, channelID string) []byte { + return []byte(NextSequenceSendPath(portID, channelID)) +} + +// KeyNextSequenceRecv returns the store key for the receive sequence of a particular +// channel binded to a specific port +func KeyNextSequenceRecv(portID, channelID string) []byte { + return []byte(NextSequenceRecvPath(portID, channelID)) +} + +// KeyPacketCommitment returns the store key of under which a packet commitment +// is stored +func KeyPacketCommitment(portID, channelID string, sequence uint64) []byte { + return []byte(PacketCommitmentPath(portID, channelID, sequence)) +} + +// KeyPacketAcknowledgement returns the store key of under which a packet +// acknowledgement is stored +func KeyPacketAcknowledgement(portID, channelID string, sequence uint64) []byte { + return []byte(PacketAcknowledgementPath(portID, channelID, sequence)) +} diff --git a/x/ibc/04-channel/types/msgs.go b/x/ibc/04-channel/types/msgs.go new file mode 100644 index 000000000000..a6d7d856e34f --- /dev/null +++ b/x/ibc/04-channel/types/msgs.go @@ -0,0 +1,379 @@ +package types + +import ( + "fmt" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" + host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" + ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types" +) + +var _ sdk.Msg = MsgChannelOpenInit{} + +type MsgChannelOpenInit struct { + PortID string `json:"port_id"` + ChannelID string `json:"channel_id"` + Channel Channel `json:"channel"` + Signer sdk.AccAddress `json:"signer"` +} + +// NewMsgChannelOpenInit creates a new MsgChannelCloseInit MsgChannelOpenInit +func NewMsgChannelOpenInit( + portID, channelID string, version string, channelOrder Order, connectionHops []string, + counterpartyPortID, counterpartyChannelID string, signer sdk.AccAddress, +) MsgChannelOpenInit { + counterparty := NewCounterparty(counterpartyPortID, counterpartyChannelID) + channel := NewChannel(INIT, channelOrder, counterparty, connectionHops, version) + return MsgChannelOpenInit{ + PortID: portID, + ChannelID: channelID, + Channel: channel, + Signer: signer, + } +} + +// Route implements sdk.Msg +func (msg MsgChannelOpenInit) Route() string { + return ibctypes.RouterKey +} + +// Type implements sdk.Msg +func (msg MsgChannelOpenInit) Type() string { + return "channel_open_init" +} + +// ValidateBasic implements sdk.Msg +func (msg MsgChannelOpenInit) ValidateBasic() sdk.Error { + if err := host.DefaultPortIdentifierValidator(msg.PortID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid port ID: %s", err.Error())) + } + if err := host.DefaultChannelIdentifierValidator(msg.ChannelID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid channel ID: %s", err.Error())) + } + // Signer can be empty + return msg.Channel.ValidateBasic() +} + +// GetSignBytes implements sdk.Msg +func (msg MsgChannelOpenInit) GetSignBytes() []byte { + return sdk.MustSortJSON(SubModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgChannelOpenInit) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} + +var _ sdk.Msg = MsgChannelOpenTry{} + +type MsgChannelOpenTry struct { + PortID string `json:"port_id"` + ChannelID string `json:"channel_id"` + Channel Channel `json:"channel"` + CounterpartyVersion string `json:"counterparty_version"` + ProofInit commitment.ProofI `json:"proof_init"` + ProofHeight uint64 `json:"proof_height"` + Signer sdk.AccAddress `json:"signer"` +} + +// NewMsgChannelOpenTry creates a new MsgChannelOpenTry instance +func NewMsgChannelOpenTry( + portID, channelID, version string, channelOrder Order, connectionHops []string, + counterpartyPortID, counterpartyChannelID, counterpartyVersion string, + proofInit commitment.ProofI, proofHeight uint64, signer sdk.AccAddress, +) MsgChannelOpenTry { + counterparty := NewCounterparty(counterpartyPortID, counterpartyChannelID) + channel := NewChannel(INIT, channelOrder, counterparty, connectionHops, version) + return MsgChannelOpenTry{ + PortID: portID, + ChannelID: channelID, + Channel: channel, + CounterpartyVersion: counterpartyVersion, + ProofInit: proofInit, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// Route implements sdk.Msg +func (msg MsgChannelOpenTry) Route() string { + return ibctypes.RouterKey +} + +// Type implements sdk.Msg +func (msg MsgChannelOpenTry) Type() string { + return "channel_open_try" +} + +// ValidateBasic implements sdk.Msg +func (msg MsgChannelOpenTry) ValidateBasic() sdk.Error { + if err := host.DefaultPortIdentifierValidator(msg.PortID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid port ID: %s", err.Error())) + } + if err := host.DefaultChannelIdentifierValidator(msg.ChannelID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid channel ID: %s", err.Error())) + } + if strings.TrimSpace(msg.CounterpartyVersion) == "" { + return ErrInvalidCounterpartyChannel(DefaultCodespace, "counterparty version cannot be blank") + } + if msg.ProofInit == nil { + return ErrInvalidChannelProof(DefaultCodespace, "cannot submit an empty proof") + } + if msg.ProofHeight == 0 { + return ErrInvalidChannelProof(DefaultCodespace, "proof height must be > 0") + } + // Signer can be empty + return msg.Channel.ValidateBasic() +} + +// GetSignBytes implements sdk.Msg +func (msg MsgChannelOpenTry) GetSignBytes() []byte { + return sdk.MustSortJSON(SubModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgChannelOpenTry) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} + +var _ sdk.Msg = MsgChannelOpenAck{} + +type MsgChannelOpenAck struct { + PortID string `json:"port_id"` + ChannelID string `json:"channel_id"` + CounterpartyVersion string `json:"counterparty_version"` + ProofTry commitment.ProofI `json:"proof_try"` + ProofHeight uint64 `json:"proof_height"` + Signer sdk.AccAddress `json:"signer"` +} + +// NewMsgChannelOpenAck creates a new MsgChannelOpenAck instance +func NewMsgChannelOpenAck( + portID, channelID string, cpv string, proofTry commitment.ProofI, proofHeight uint64, + signer sdk.AccAddress, +) MsgChannelOpenAck { + return MsgChannelOpenAck{ + PortID: portID, + ChannelID: channelID, + CounterpartyVersion: cpv, + ProofTry: proofTry, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// Route implements sdk.Msg +func (msg MsgChannelOpenAck) Route() string { + return ibctypes.RouterKey +} + +// Type implements sdk.Msg +func (msg MsgChannelOpenAck) Type() string { + return "channel_open_ack" +} + +// ValidateBasic implements sdk.Msg +func (msg MsgChannelOpenAck) ValidateBasic() sdk.Error { + if err := host.DefaultPortIdentifierValidator(msg.PortID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid port ID: %s", err.Error())) + } + if err := host.DefaultChannelIdentifierValidator(msg.ChannelID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid channel ID: %s", err.Error())) + } + if strings.TrimSpace(msg.CounterpartyVersion) == "" { + return ErrInvalidCounterpartyChannel(DefaultCodespace, "counterparty version cannot be blank") + } + if msg.ProofTry == nil { + return ErrInvalidChannelProof(DefaultCodespace, "cannot submit an empty proof") + } + if msg.ProofHeight == 0 { + return ErrInvalidChannelProof(DefaultCodespace, "proof height must be > 0") + } + // Signer can be empty + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgChannelOpenAck) GetSignBytes() []byte { + return sdk.MustSortJSON(SubModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgChannelOpenAck) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} + +var _ sdk.Msg = MsgChannelOpenConfirm{} + +type MsgChannelOpenConfirm struct { + PortID string `json:"port_id"` + ChannelID string `json:"channel_id"` + ProofAck commitment.ProofI `json:"proof_ack"` + ProofHeight uint64 `json:"proof_height"` + Signer sdk.AccAddress `json:"signer"` +} + +// NewMsgChannelOpenConfirm creates a new MsgChannelOpenConfirm instance +func NewMsgChannelOpenConfirm( + portID, channelID string, proofAck commitment.ProofI, proofHeight uint64, + signer sdk.AccAddress, +) MsgChannelOpenConfirm { + return MsgChannelOpenConfirm{ + PortID: portID, + ChannelID: channelID, + ProofAck: proofAck, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// Route implements sdk.Msg +func (msg MsgChannelOpenConfirm) Route() string { + return ibctypes.RouterKey +} + +// Type implements sdk.Msg +func (msg MsgChannelOpenConfirm) Type() string { + return "channel_open_confirm" +} + +// ValidateBasic implements sdk.Msg +func (msg MsgChannelOpenConfirm) ValidateBasic() sdk.Error { + if err := host.DefaultPortIdentifierValidator(msg.PortID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid port ID: %s", err.Error())) + } + if err := host.DefaultChannelIdentifierValidator(msg.ChannelID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid channel ID: %s", err.Error())) + } + if msg.ProofAck == nil { + return ErrInvalidChannelProof(DefaultCodespace, "cannot submit an empty proof") + } + if msg.ProofHeight == 0 { + return ErrInvalidChannelProof(DefaultCodespace, "proof height must be > 0") + } + // Signer can be empty + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgChannelOpenConfirm) GetSignBytes() []byte { + return sdk.MustSortJSON(SubModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgChannelOpenConfirm) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} + +var _ sdk.Msg = MsgChannelCloseInit{} + +type MsgChannelCloseInit struct { + PortID string `json:"port_id"` + ChannelID string `json:"channel_id"` + Signer sdk.AccAddress `json:"signer"` +} + +// NewMsgChannelCloseInit creates a new MsgChannelCloseInit instance +func NewMsgChannelCloseInit(portID string, channelID string, signer sdk.AccAddress) MsgChannelCloseInit { + return MsgChannelCloseInit{ + PortID: portID, + ChannelID: channelID, + Signer: signer, + } +} + +// Route implements sdk.Msg +func (msg MsgChannelCloseInit) Route() string { + return ibctypes.RouterKey +} + +// Type implements sdk.Msg +func (msg MsgChannelCloseInit) Type() string { + return "channel_close_init" +} + +// ValidateBasic implements sdk.Msg +func (msg MsgChannelCloseInit) ValidateBasic() sdk.Error { + if err := host.DefaultPortIdentifierValidator(msg.PortID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid port ID: %s", err.Error())) + } + if err := host.DefaultChannelIdentifierValidator(msg.ChannelID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid channel ID: %s", err.Error())) + } + // Signer can be empty + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgChannelCloseInit) GetSignBytes() []byte { + return sdk.MustSortJSON(SubModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgChannelCloseInit) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} + +var _ sdk.Msg = MsgChannelCloseConfirm{} + +type MsgChannelCloseConfirm struct { + PortID string `json:"port_id"` + ChannelID string `json:"channel_id"` + ProofInit commitment.ProofI `json:"proof_init"` + ProofHeight uint64 `json:"proof_height"` + Signer sdk.AccAddress `json:"signer"` +} + +// NewMsgChannelCloseConfirm creates a new MsgChannelCloseConfirm instance +func NewMsgChannelCloseConfirm( + portID, channelID string, proofInit commitment.ProofI, proofHeight uint64, + signer sdk.AccAddress, +) MsgChannelCloseConfirm { + return MsgChannelCloseConfirm{ + PortID: portID, + ChannelID: channelID, + ProofInit: proofInit, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// Route implements sdk.Msg +func (msg MsgChannelCloseConfirm) Route() string { + return ibctypes.RouterKey +} + +// Type implements sdk.Msg +func (msg MsgChannelCloseConfirm) Type() string { + return "channel_close_confirm" +} + +// ValidateBasic implements sdk.Msg +func (msg MsgChannelCloseConfirm) ValidateBasic() sdk.Error { + if err := host.DefaultPortIdentifierValidator(msg.PortID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid port ID: %s", err.Error())) + } + if err := host.DefaultChannelIdentifierValidator(msg.ChannelID); err != nil { + return sdk.NewError(host.IBCCodeSpace, 1, fmt.Sprintf("invalid channel ID: %s", err.Error())) + } + if msg.ProofInit == nil { + return ErrInvalidChannelProof(DefaultCodespace, "cannot submit an empty proof") + } + if msg.ProofHeight == 0 { + return ErrInvalidChannelProof(DefaultCodespace, "proof height must be > 0") + } + // Signer can be empty + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgChannelCloseConfirm) GetSignBytes() []byte { + return sdk.MustSortJSON(SubModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgChannelCloseConfirm) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Signer} +} diff --git a/x/ibc/04-channel/types/packet.go b/x/ibc/04-channel/types/packet.go new file mode 100644 index 000000000000..8d170cc62a9d --- /dev/null +++ b/x/ibc/04-channel/types/packet.go @@ -0,0 +1,108 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" + host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" +) + +var _ exported.PacketI = Packet{} + +// Packet defines a type that carries data across different chains through IBC +type Packet struct { + Sequence uint64 `json:"sequence"` // number corresponds to the order of sends and receives, where a Packet with an earlier sequence number must be sent and received before a Packet with a later sequence number. + Timeout uint64 `json:"timeout"` // indicates a consensus height on the destination chain after which the Packet will no longer be processed, and will instead count as having timed-out. + SourcePort string `json:"source_port"` // identifies the port on the sending chain. + SourceChannel string `json:"source_channel"` // identifies the channel end on the sending chain. + DestinationPort string `json:"destination_port"` // identifies the port on the receiving chain. + DestinationChannel string `json:"destination_channel"` // identifies the channel end on the receiving chain. + Data []byte `json:"data"` // opaque value which can be defined by the application logic of the associated modules. +} + +// NewPacket creates a new Packet instance +func NewPacket( + sequence, timeout uint64, sourcePort, sourceChannel, + destinationPort, destinationChannel string, data []byte, +) Packet { + return Packet{ + sequence, + timeout, + sourcePort, + sourceChannel, + destinationPort, + destinationChannel, + data, + } +} + +// Sequence implements PacketI interface +func (p Packet) GetSequence() uint64 { return p.Sequence } + +// TimeoutHeight implements PacketI interface +func (p Packet) GetTimeoutHeight() uint64 { return p.Timeout } + +// SourcePort implements PacketI interface +func (p Packet) GetSourcePort() string { return p.SourcePort } + +// SourceChannel implements PacketI interface +func (p Packet) GetSourceChannel() string { return p.SourceChannel } + +// DestPort implements PacketI interface +func (p Packet) GetDestPort() string { return p.DestinationPort } + +// DestChannel implements PacketI interface +func (p Packet) GetDestChannel() string { return p.DestinationChannel } + +// Data implements PacketI interface +func (p Packet) GetData() []byte { return p.Data } + +// ValidateBasic implements PacketI interface +func (p Packet) ValidateBasic() sdk.Error { + if err := host.DefaultPortIdentifierValidator(p.SourcePort); err != nil { + return ErrInvalidPacket(DefaultCodespace, fmt.Sprintf("invalid source port ID: %s", err.Error())) + } + if err := host.DefaultPortIdentifierValidator(p.DestinationPort); err != nil { + return ErrInvalidPacket(DefaultCodespace, fmt.Sprintf("invalid destination port ID: %s", err.Error())) + } + if err := host.DefaultChannelIdentifierValidator(p.SourceChannel); err != nil { + return ErrInvalidPacket(DefaultCodespace, fmt.Sprintf("invalid source channel ID: %s", err.Error())) + } + if err := host.DefaultChannelIdentifierValidator(p.DestinationChannel); err != nil { + return ErrInvalidPacket(DefaultCodespace, fmt.Sprintf("invalid destination channel ID: %s", err.Error())) + } + if p.Sequence == 0 { + return ErrInvalidPacket(DefaultCodespace, "packet sequence cannot be 0") + } + if p.Timeout == 0 { + return ErrPacketTimeout(DefaultCodespace) + } + if len(p.Data) == 0 { + return ErrInvalidPacket(DefaultCodespace, "packet data cannot be empty") + } + return nil +} + +var _ exported.PacketI = OpaquePacket{} + +// OpaquePacket is a Packet, but cloaked in an obscuring data type by the host +// state machine, such that a module cannot act upon it other than to pass it to +// the IBC handler +type OpaquePacket struct { + *Packet +} + +// NewOpaquePacket creates a new OpaquePacket instance +func NewOpaquePacket(sequence, timeout uint64, sourcePort, sourceChannel, + destinationPort, destinationChannel string, data []byte, +) OpaquePacket { + Packet := NewPacket( + sequence, timeout, sourcePort, sourceChannel, destinationPort, + destinationChannel, data, + ) + return OpaquePacket{&Packet} +} + +// Data implements PacketI interface +func (op OpaquePacket) GetData() []byte { return nil } diff --git a/x/ibc/04-channel/types/querier.go b/x/ibc/04-channel/types/querier.go new file mode 100644 index 000000000000..707e9076ca39 --- /dev/null +++ b/x/ibc/04-channel/types/querier.go @@ -0,0 +1,71 @@ +package types + +import ( + "strings" + + "github.com/tendermint/tendermint/crypto/merkle" + + commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" +) + +// query routes supported by the IBC channel Querier +const ( + QueryChannel = "channel" +) + +// ChannelResponse defines the client query response for a channel which also +// includes a proof,its path and the height from which the proof was retrieved. +type ChannelResponse struct { + Channel Channel `json:"channel" yaml:"channel"` + Proof commitment.Proof `json:"proof,omitempty" yaml:"proof,omitempty"` + ProofPath commitment.Path `json:"proof_path,omitempty" yaml:"proof_path,omitempty"` + ProofHeight uint64 `json:"proof_height,omitempty" yaml:"proof_height,omitempty"` +} + +// NewChannelResponse creates a new ChannelResponse instance +func NewChannelResponse( + portID, channelID string, channel Channel, proof *merkle.Proof, height int64, +) ChannelResponse { + return ChannelResponse{ + Channel: channel, + Proof: commitment.Proof{Proof: proof}, + ProofPath: commitment.NewPath(strings.Split(ChannelPath(portID, channelID), "/")), + ProofHeight: uint64(height), + } +} + +// QueryChannelParams defines the params for the following queries: +// - 'custom/ibc/channel' +type QueryChannelParams struct { + PortID string + ChannelID string +} + +// NewQueryChannelParams creates a new QueryChannelParams instance +func NewQueryChannelParams(portID, channelID string) QueryChannelParams { + return QueryChannelParams{ + PortID: portID, + ChannelID: channelID, + } +} + +// PacketResponse defines the client query response for a packet which also +// includes a proof, its path and the height form which the proof was retrieved +type PacketResponse struct { + Packet Packet `json:"packet" yaml:"packet"` + Proof commitment.Proof `json:"proof,omitempty" yaml:"proof,omitempty"` + ProofPath commitment.Path `json:"proof_path,omitempty" yaml:"proof_path,omitempty"` + ProofHeight uint64 `json:"proof_height,omitempty" yaml:"proof_height,omitempty"` +} + +// NewPacketResponse creates a new PacketResponswe instance +func NewPacketResponse( + portID, channelID string, sequence uint64, packet Packet, proof *merkle.Proof, height int64, +) PacketResponse { + return PacketResponse{ + Packet: packet, + Proof: commitment.Proof{Proof: proof}, + ProofPath: commitment.NewPath(strings.Split(PacketCommitmentPath(portID, channelID, sequence), "/")), + ProofHeight: uint64(height), + } +} diff --git a/x/ibc/handler.go b/x/ibc/handler.go index ed740f39183e..b80267dd7d92 100644 --- a/x/ibc/handler.go +++ b/x/ibc/handler.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" ) // NewHandler defines the IBC handler @@ -14,6 +15,7 @@ func NewHandler(k Keeper) sdk.Handler { ctx = ctx.WithEventManager(sdk.NewEventManager()) switch msg := msg.(type) { + // IBC client msgs case client.MsgCreateClient: return client.HandleMsgCreateClient(ctx, k.ClientKeeper, msg) @@ -23,6 +25,7 @@ func NewHandler(k Keeper) sdk.Handler { case client.MsgSubmitMisbehaviour: return client.HandleMsgSubmitMisbehaviour(ctx, k.ClientKeeper, msg) + // IBC connection msgs case connection.MsgConnectionOpenInit: return connection.HandleMsgConnectionOpenInit(ctx, k.ConnectionKeeper, msg) @@ -35,6 +38,25 @@ func NewHandler(k Keeper) sdk.Handler { case connection.MsgConnectionOpenConfirm: return connection.HandleMsgConnectionOpenConfirm(ctx, k.ConnectionKeeper, msg) + // IBC channel msgs + case channel.MsgChannelOpenInit: + return channel.HandleMsgChannelOpenInit(ctx, k.ChannelKeeper, msg) + + case channel.MsgChannelOpenTry: + return channel.HandleMsgChannelOpenTry(ctx, k.ChannelKeeper, msg) + + case channel.MsgChannelOpenAck: + return channel.HandleMsgChannelOpenAck(ctx, k.ChannelKeeper, msg) + + case channel.MsgChannelOpenConfirm: + return channel.HandleMsgChannelOpenConfirm(ctx, k.ChannelKeeper, msg) + + case channel.MsgChannelCloseInit: + return channel.HandleMsgChannelCloseInit(ctx, k.ChannelKeeper, msg) + + case channel.MsgChannelCloseConfirm: + return channel.HandleMsgChannelCloseConfirm(ctx, k.ChannelKeeper, msg) + default: errMsg := fmt.Sprintf("unrecognized IBC message type: %T", msg) return sdk.ErrUnknownRequest(errMsg).Result() diff --git a/x/ibc/keeper/keeper.go b/x/ibc/keeper/keeper.go index c9708a6e79a1..52158b48b140 100644 --- a/x/ibc/keeper/keeper.go +++ b/x/ibc/keeper/keeper.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" port "github.com/cosmos/cosmos-sdk/x/ibc/05-port" ) @@ -12,6 +13,7 @@ import ( type Keeper struct { ClientKeeper client.Keeper ConnectionKeeper connection.Keeper + ChannelKeeper channel.Keeper PortKeeper port.Keeper } @@ -20,10 +22,12 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, codespace sdk.CodespaceType) clientKeeper := client.NewKeeper(cdc, key, codespace) connectionKeeper := connection.NewKeeper(cdc, key, codespace, clientKeeper) portKeeper := port.NewKeeper(cdc, key, codespace) + channelKeeper := channel.NewKeeper(cdc, key, codespace, clientKeeper, connectionKeeper, portKeeper) return Keeper{ ClientKeeper: clientKeeper, ConnectionKeeper: connectionKeeper, + ChannelKeeper: channelKeeper, PortKeeper: portKeeper, } } diff --git a/x/ibc/keeper/querier.go b/x/ibc/keeper/querier.go index 43db6f574dc0..fc2527df104c 100644 --- a/x/ibc/keeper/querier.go +++ b/x/ibc/keeper/querier.go @@ -8,6 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" ) // NewQuerier creates a querier for the IBC module @@ -36,6 +37,14 @@ func NewQuerier(k Keeper) sdk.Querier { return nil, sdk.ErrUnknownRequest(fmt.Sprintf("unknown IBC %s query endpoint", connection.SubModuleName)) } + case channel.SubModuleName: + switch path[1] { + case channel.QueryChannel: + return channel.QuerierChannel(ctx, req, k.ChannelKeeper) + default: + return nil, sdk.ErrUnknownRequest(fmt.Sprintf("unknown IBC %s query endpoint", channel.SubModuleName)) + } + default: return nil, sdk.ErrUnknownRequest("unknown IBC query endpoint") } diff --git a/x/ibc/module.go b/x/ibc/module.go index 21ba001210de..791517826f03 100644 --- a/x/ibc/module.go +++ b/x/ibc/module.go @@ -14,6 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" client "github.com/cosmos/cosmos-sdk/x/ibc/02-client" connection "github.com/cosmos/cosmos-sdk/x/ibc/03-connection" + channel "github.com/cosmos/cosmos-sdk/x/ibc/04-channel" commitment "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment" "github.com/cosmos/cosmos-sdk/x/ibc/client/cli" "github.com/cosmos/cosmos-sdk/x/ibc/types" @@ -39,6 +40,7 @@ func (AppModuleBasic) Name() string { func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { client.RegisterCodec(cdc) connection.RegisterCodec(cdc) + channel.RegisterCodec(cdc) commitment.RegisterCodec(cdc) }