diff --git a/academy/ibc-dev/custom-ibc-application.md b/academy/ibc-dev/custom-ibc-application.md index 2742fc9bb3..585a82736d 100644 --- a/academy/ibc-dev/custom-ibc-application.md +++ b/academy/ibc-dev/custom-ibc-application.md @@ -5,6 +5,8 @@ description: Take a closer look at applications tag: deep-dive --- + + # Implement a Custom IBC Application diff --git a/academy/ibc-dev/ibcapp_app-vs-ica.md b/academy/ibc-dev/ibcapp_app-vs-ica.md new file mode 100644 index 0000000000..f3c246cbf7 --- /dev/null +++ b/academy/ibc-dev/ibcapp_app-vs-ica.md @@ -0,0 +1,5 @@ + + +# Custom IBC applications versus Interchain Accounts + + \ No newline at end of file diff --git a/academy/ibc-dev/ibcapp_intro.md b/academy/ibc-dev/ibcapp_intro.md new file mode 100644 index 0000000000..f488929a1e --- /dev/null +++ b/academy/ibc-dev/ibcapp_intro.md @@ -0,0 +1,39 @@ +--- +title: "IBC Application Developer Introduction" +order: 6 +description: +tags: + - ibc + - cosmos-sdk + - concepts +--- + +# IBC Application Developer Introduction + +You might have already dived into [how to create custom SDK modules](tutorials/7-understand-sdk-modules/index.md). Additionally, you had an [introduction to IBC](/academy/3-ibc/what-is-ibc.md), the ibc-go module in the SDK, and how to [spin up a relayer to send IBC packets](/tutorials/4-ibc-dev/relayer-intro.md). + +Remember the separation of concerns in the Inter-Blockchain Communication Protocol (IBC) between the transport layer (IBC/TAO) and the application layer (IBC/APP). The transport layer provides the basic infrastructure layer to _transport_, _authenticate_, and _order_ arbitrary packets of data. The encoding, decoding, and interpretation of the data to trigger the custom application logic is then up to the application layer. The examples of token transfer sent over IBC implicitly used the ICS-20 or _transfer_ IBC application module provided by the **ibc-go** SDK module (which also provides the core transport layer functionality). + +In the following sections, you will learn how to develop your custom IBC application modules, either by upgrading an existing module or starting from scratch using Ignite CLI. + + + +In the [integration](https://ibc.cosmos.network/v3.0.0/ibc/integration.html) section of the IBC documentation, the necessary steps to integrate IBC in a Cosmos SDK chain are outlined. + +Note that this does not mean that the main application modules turn into IBC modules, it only means IBC is enabled for the chain. The IBC module has come out-of-the-box in Comsos SDK chains since the 0.40.x version of the SDK, so it is unlikely you'll have to implement these steps manually when developing a chain. + +For example, the checkers blockchain you developed in the previous section **is IBC-enabled**. This is revealed when trying to send IBC denoms from other chains in order to set a wager. However, this does not make the `x/checkers` module an IBC-enabled module. you will investigate all the additions required to make the module IBC-enabled in what follows. + + + +## Structure of the sections to come + +In this part of the chapter, you will first investigate the code you have to add to make a module IBC-enabled. For this conceptual example, you will build a simple chain from scratch with Ignite CLI. Ignite CLI provides the option to scaffold an IBC module, which does all of the hard work in terms of boilerplate code. Still, it makes sense to take a look at what exactly has changed. Therefore, you will compare the code with a _git diff_ when scaffolding a chain with a regular module and when you scaffold an IBC module. + +A similar approach will be taken to check what Ignite CLI implements when scaffolding an IBC packet. + +After finishing the conceptual part, you are going to expand the checkers blockchain you created to turn it into an IBC module, and will create an additional leaderboard blockchain to act as a separate appchain that can interact via IBC with the checkers blockchain. + + + +Let's dive into it! diff --git a/academy/ibc-dev/ibcapp_packet.md b/academy/ibc-dev/ibcapp_packet.md new file mode 100644 index 0000000000..97d5a3f345 --- /dev/null +++ b/academy/ibc-dev/ibcapp_packet.md @@ -0,0 +1,425 @@ +--- +title: "Adding Packet and Acknowledgment Data" +order: 8 +description: +tags: + - guided-coding + - dev-ops + - ibc +--- + +# Adding Packet and Acknowledgment Data + +This section demonstrates how to define packets and acks (acknowledgments) for the leaderboard blockchain. Remember that this blockchain will mostly be receiving packets from the checkers blockchain or other gaming chains. This will be handled in the checkers blockchain extension tutorial. In this section, you will add an additional packet definition that will enable the Leaderboard chain to send a packet to connected game chains when a player has entered the top of the rankings. + +The documentation on how to define packets and acks in the Inter-Blockchain Communication Protocol (IBC) can be found in [the ibc-go docs](https://ibc.cosmos.network/main/ibc/apps/packets_acks.html). + +## Scaffold a packet with Ignite CLI + +You are now going to scaffold the IBC packet data with Ignite CLI, and compare it once more with _git diff_: + +```bash +$ ignite scaffold packet ibcTopRank playerId rank:uint score:uint --ack playerId --module leaderboard +``` + + + +The packet is called `ibcTopRank`, which includes the fields `playerId`, `rank`, and `score`. Additionally, you send back the `playerId` of the player who entered the top of the rankings through the `Acknowledgement`. + + + +The output on the terminal gives an overview of the changes made: + +```bash +modify proto/leaderboard/packet.proto +modify proto/leaderboard/tx.proto +modify x/leaderboard/client/cli/tx.go +create x/leaderboard/client/cli/tx_ibc_top_rank.go +modify x/leaderboard/handler.go +create x/leaderboard/keeper/ibc_top_rank.go +create x/leaderboard/keeper/msg_server_ibc_top_rank.go +modify x/leaderboard/module_ibc.go +modify x/leaderboard/types/codec.go +modify x/leaderboard/types/events_ibc.go +create x/leaderboard/types/messages_ibc_top_rank.go +create x/leaderboard/types/messages_ibc_top_rank_test.go +create x/leaderboard/types/packet_ibc_top_rank.go + +🎉 Created a packet `ibcTopRank`. +``` + +In the next paragraphs, you will investigate each of the most important additions to the code. + +## Proto definitions + +The first additions are to the proto definitions in the `packet.proto` and `tx.proto` files: + +```diff +@@ message LeaderboardPacketData in proto/leaderboard/packet.proto { + oneof packet { + NoData noData = 1; + // this line is used by starport scaffolding # ibc/packet/proto/field ++ IbcTopRankPacketData ibcTopRankPacket = 2; + // this line is used by starport scaffolding # ibc/packet/proto/field/number + } +} +``` + +One addition is `IbcTopRankPacketData`: + +```protobuf +// IbcTopRankPacketData defines a struct for the packet payload +message IbcTopRankPacketData { + string playerId = 1; + uint64 rank = 2; + uint64 score = 3; +} +``` + +The next addition is the ack: + +```protobuf +// IbcTopRankPacketAck defines a struct for the packet acknowledgment +message IbcTopRankPacketAck { + string playerId = 1; +} +``` + +And in `tx.proto` a Message service is added: + +```diff +// Msg defines the Msg service. +service Msg { ++ rpc SendIbcTopRank(MsgSendIbcTopRank) returns (MsgSendIbcTopRankResponse); + // this line is used by starport scaffolding # proto/tx/rpc +} +``` + +Where: + +```protobuf +message MsgSendIbcTopRank { + string creator = 1; + string port = 2; + string channelID = 3; + uint64 timeoutTimestamp = 4; + string playerId = 5; + uint64 rank = 6; + uint64 score = 7; +} + +message MsgSendIbcTopRankResponse { +} +``` + + + +The proto message `MsgSendIbcTopRank` includes the field `timeoutTimestamp`, which is added by Ignite CLI when scaffolding an IBC packet. This is an IBC channel parameter that is important in IBC and Ignite CLI abstracts this away, removing the need for the user to add this manually. + + + + + +The proto definitions will be compiled into `types/packet.pb.go` and `types/tx.pb.go`. + + + +## CLI commands + +Ignite CLI also creates CLI commands to send packets and adds them to the `client/cli/` folder. + +Packets can be sent from the CLI with the following command: + +```bash +$ leaderboardd tx leaderboard send-ibc-top-rank [portID] [channelID] [playerId] [rank] [score] +``` + +## `SendPacket` and packet callback logic + +When scaffolding an IBC module with Ignite CLI, you already saw the implementation of the `IBCModule` interface, including a bare-bones packet callbacks structure. Now that you've also scaffolded a packet (and ack), the callbacks have been added with logic to handle the receive, ack, and timeout scenarios. + +Additionally, for the sending of a packet, a message server has been added that handles a SendPacket message, in this case `MsgSendIbcTopRank`. + + + +IBC allows some freedom to the developers regarding how to implement the custom logic, decoding and encoding packets, and processing acks. The provided structure is but one example of how to tackle this. Therefore, it makes sense to focus on the general flow to handle user messages or IBC callbacks rather than the specific implementation by Ignite CLI. + + + +### Sending packets + +To handle a user submitting a message to send an IBC packet, a message server is added to the handler: + +```diff + @@ x/leaderboard/handler.go + func NewHandler(k keeper.Keeper) sdk.Handler { ++ msgServer := keeper.NewMsgServerImpl(k) + + return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + + switch msg := msg.(type) { ++ case *types.MsgSendIbcTopRank: ++ res, err := msgServer.SendIbcTopRank(sdk.WrapSDKContext(ctx), msg) ++ return sdk.WrapServiceResult(ctx, res, err) + // this line is used by starport scaffolding # 1 + default: + errMsg := fmt.Sprintf("unrecognized %s message type: %T", types.ModuleName, msg) + return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg) + } + } + } +``` + +It calls the `SendIbcTopRank` method on the message server, defined as: + +```go +// x/leaderboard/keeper/msg_server_ibc_top_rank.go but up to dev +func (k msgServer) SendIbcTopRank(goCtx context.Context, msg *types.MsgSendIbcTopRank) (*types.MsgSendIbcTopRankResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // TODO: logic before transmitting the packet + + // Construct the packet + var packet types.IbcTopRankPacketData + + packet.PlayerId = msg.PlayerId + packet.Rank = msg.Rank + packet.Score = msg.Score + + // Transmit the packet + err := k.TransmitIbcTopRankPacket( + ctx, + packet, + msg.Port, + msg.ChannelID, + clienttypes.ZeroHeight(), + msg.TimeoutTimestamp, + ) + if err != nil { + return nil, err + } + + return &types.MsgSendIbcTopRankResponse{}, nil +} +``` + +This in turn calls the `TransmitIbcTopRankPacket` method on the module's keeper, defined in `x/leaderboard/keeper/ibc_top_rank.go`. This method gets all of the required metadata from core IBC before sending the packet using the ChannelKeeper's `SendPacket` function: + +```go +func (k Keeper) TransmitIbcTopRankPacket( + ctx sdk.Context, + packetData types.IbcTopRankPacketData, + sourcePort, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, +) error { + + sourceChannelEnd, found := k.ChannelKeeper.GetChannel(ctx, sourcePort, sourceChannel) + ... // error validation + + destinationPort := sourceChannelEnd.GetCounterparty().GetPortID() + destinationChannel := sourceChannelEnd.GetCounterparty().GetChannelID() + + // get the next sequence + sequence, found := k.ChannelKeeper.GetNextSequenceSend(ctx, sourcePort, sourceChannel) + ... // error validation + + channelCap, ok := k.ScopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(sourcePort, sourceChannel)) + ... // error validation + + packetBytes, err := packetData.GetBytes() + ... // error validation + + packet := channeltypes.NewPacket( + packetBytes, + sequence, + sourcePort, + sourceChannel, + destinationPort, + destinationChannel, + timeoutHeight, + timeoutTimestamp, + ) + + if err := k.ChannelKeeper.SendPacket(ctx, channelCap, packet); err != nil { + return err + } + + return nil +} +``` + + + +When you want to add additional custom logic before transmitting the packet, you do this in the `SendIbcTopRank` method on the message server. + + + +### Receiving packets + +In a previous section you examined the `OnRecvPacket` callback in the `x/leaderboard/module_ibc.go` file. There, Ignite CLI had set up a structure to dispatch the packet depending on packet type through a switch statement. Now by adding the `IbcTopRank` packet, a case has been added: + +```go +// @ switch packet := modulePacketData.Packet.(type) in OnRecvPacket +case *types.LeaderboardPacketData_IbcTopRankPacket: + packetAck, err := am.keeper.OnRecvIbcTopRankPacket(ctx, modulePacket, *packet.IbcTopRankPacket) + if err != nil { + ack = channeltypes.NewErrorAcknowledgement(err.Error()) + } else { + // Encode packet acknowledgment + packetAckBytes, err := types.ModuleCdc.MarshalJSON(&packetAck) + if err != nil { + return channeltypes.NewErrorAcknowledgement(sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()).Error()) + } + ack = channeltypes.NewResultAcknowledgement(sdk.MustSortJSON(packetAckBytes)) + } + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeIbcTopRankPacket, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyAckSuccess, fmt.Sprintf("%t", err != nil)), + ), + ) +``` + +The first line of code in the case statement calls the application's `OnRecvIbcTopRankPacket` callback on the keeper to process the reception of the packet: + +```go +// OnRecvIbcTopRankPacket processes packet reception +func (k Keeper) OnRecvIbcTopRankPacket(ctx sdk.Context, packet channeltypes.Packet, data types.IbcTopRankPacketData) (packetAck types.IbcTopRankPacketAck, err error) { + // validate packet data upon receiving + if err := data.ValidateBasic(); err != nil { + return packetAck, err + } + + // TODO: packet reception logic + + return packetAck, nil +} +``` + + + +Remember that the `OnRecvPacket` callback writes an acknowledgment as well (this course covers the synchronous write ack case). + + + +### Acknowledging packets + +Similarly to the `OnRecvPacket` case before, Ignite CLI has already prepared the structure of the `OnAcknowledgementPacket` with the switch statement. Again, scaffolding the packet adds a case to the switch: + +```go +// @ switch packet := modulePacketData.Packet.(type) in OnAcknowledgmentPacket +case *types.LeaderboardPacketData_IbcTopRankPacket: + err := am.keeper.OnAcknowledgementIbcTopRankPacket(ctx, modulePacket, *packet.IbcTopRankPacket, ack) + if err != nil { + return err + } + eventType = types.EventTypeIbcTopRankPacket +``` + +This calls into the newly created application keeper's ack packet callback: + +```go +func (k Keeper) OnAcknowledgementIbcTopRankPacket(ctx sdk.Context, packet channeltypes.Packet, data types.IbcTopRankPacketData, ack channeltypes.Acknowledgement) error { + switch dispatchedAck := ack.Response.(type) { + case *channeltypes.Acknowledgement_Error: + + // TODO: failed acknowledgment logic + _ = dispatchedAck.Error + + return nil + case *channeltypes.Acknowledgement_Result: + // Decode the packet acknowledgment + var packetAck types.IbcTopRankPacketAck + + if err := types.ModuleCdc.UnmarshalJSON(dispatchedAck.Result, &packetAck); err != nil { + // The counter-party module doesn't implement the correct acknowledgment format + return errors.New("cannot unmarshal acknowledgment") + } + + // TODO: successful acknowledgment logic + + return nil + default: + // The counter-party module doesn't implement the correct acknowledgment format + return errors.New("invalid acknowledgment format") + } +} +``` + +This allows us to add custom application logic for both failed and successful acks. + +### Timing out packets + +Timing out the packets follows the same flow, adding a case to the switch statement in `OnTimeoutPacket`, calling into the keeper's timeout packet callback where the custom logic can be implemented. It is left to the reader to investigate this independently. + +### Extra details + +Next to the above, some additions have also been made to the `types` package. These include `codec.go`, `events_ibc.go`, and `messages_ibc_top_rank.go`. + +Again, the reader is invited to check these out independently. + + + +Events in IBC are important because relayers process events to check if there are packets (or acknowledgments) to relay. + +Ignite CLI has scaffolded some events in `x/leaderboard/types/events_ibc.go` for timeout and the `ibcTopRank` packet which you have defined: + +```go +package types + +// IBC events +const ( + EventTypeTimeout = "timeout" + EventTypeIbcTopRankPacket = "ibcTopRank_packet" + // this line is used by starport scaffolding # ibc/packet/event + + AttributeKeyAckSuccess = "success" + AttributeKeyAck = "acknowledgement" + AttributeKeyAckError = "error" +) +``` + +Here are found both the `Event` type and the attributes it contains. + +These are not the only relevant events for IBC, though, the others can be found in the core IBC source code: + +* [Client events](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client/types/events.go) +* [Connection events](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection/types/events.go) +* [Channel events](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/types/events.go) + +You can go back to the code examined so far to take note of the events emitted. + + + +## Summary + + + +To summarize, this section has explored: + +* Scaffolding a chain with an IBC-enabled module, with both chain and module called leaderboard. +* How Ignite CLI made sure to implement the `IBCModule` interface, including channel handshake and packet callbacks. +* How Ignite CLI has bound our IBC module to a port and added a route to the IBC router. +* Scaffolding an IBC packet, `IbcTopRankPacket`. +* How Ignite CLI defined the packet and ack data. +* How Ignite CLI sets up the basic message handling and packet handling to send, receive, acknowledge, and timeout packets. + + + + + +Even though the ability to send and receive packets is now enabled, no application logic to execute has yet been implemented. This is outside the scope of this section. The reader is invited to follow the checkers blockchain extension tutorial [insert link]. + + + + + +When scaffolding a packet, Ignite CLI will ensure the chain can act both as the sender or receiver of a packet by default. This is a symmetrical setup which makes sense for some applications, like ICS20. + +However, it's also possible to have an asymmetrical setup where one chain will always be the source _or_ destination chain for a given packet, not both. In this case, the message server and packet callbacks can be updated to error when, for example, a chain receives a packet though it is supposed to exclusively be the destination chain. Interchain Accounts or ICS27 is an example of this asymmetrical situation, as is the checkers extension tutorial. + + diff --git a/academy/ibc-dev/ibcapp_steps.md b/academy/ibc-dev/ibcapp_steps.md new file mode 100644 index 0000000000..7a2b8e9195 --- /dev/null +++ b/academy/ibc-dev/ibcapp_steps.md @@ -0,0 +1,409 @@ +--- +title: "Make a Module IBC-Enabled" +order: 7 +description: +tags: + - guided-coding + - dev-ops + - ibc +--- + +# Make a Module IBC-Enabled + +In this section, you'll build a conceptual SDK blockchain with one module: first as a regular module, and second as an IBC module. This will introduce you to what makes a module IBC-enabled. + +## Scaffold a leaderboard chain + +By now you should be familiar with scaffolding a chain with Ignite CLI. If not, check out the [Create Your Own Chain](/hands-on-exercise/1-ignite-cli/index.md) chapter. + +To begin, scaffold a `leaderboard` chain: + +```bash +$ ignite scaffold chain github.com/cosmonaut/leaderboard +``` + +This creates a chain with `x/leaderboard` a regular SDK module. + +Next, scaffold another chain (for example in another git branch), but this time add the `--no-module` flag: + +```bash +$ ignite scaffold chain github.com/cosmonaut/leaderboard --no-module +``` + +Now add the `x/leaderboard` module as an IBC module with the `--ibc` flag: + +```bash +$ ignite scaffold module leaderboard --ibc +``` + +The output you see on the terminal when the module has finished scaffolding already gives a sense of what has to be implemented to create an IBC module: + +```bash +modify app/app.go +modify proto/leaderboard/genesis.proto +create proto/leaderboard/packet.proto +modify testutil/keeper/leaderboard.go +modify x/leaderboard/genesis.go +create x/leaderboard/module_ibc.go +create x/leaderboard/types/events_ibc.go +modify x/leaderboard/types/genesis.go +modify x/leaderboard/types/keys.go +``` + + + +The code in this section was scaffolded with Ignite CLI v0.22. This includes ibc-go v3 as a dependency. The latest version of ibc-go is already past v3 so there may be some differences compared to the code in this section. For documentation on the latest version of ibc-go, please refer to the [ibc-go docs](https://ibc.cosmos.network/main/ibc/apps/apps.html). + + + +For a more detailed view, you can now compare both versions with a `git diff`. + + + +To make use of `git diff`s to check the changes, be sure to commit between different (scaffolding) actions. + +```sh +$ git diff +``` + +You can use git or GitHub to visualize the `git diff`s or alternatively use [diffy.org](https://diffy.org/). + + + +## IBC application module requirements + +What does Ignite CLI do behind the scenes when creating an IBC module for us? What do you need to implement if you want to upgrade a regular custom application module to an IBC-enabled module? + +The required steps to implement can be found in the [ibc-go docs](https://ibc.cosmos.network/main/ibc/apps/apps.html). There you will find: + + + +**To have your module interact over IBC you must:** + +* Implement the `IBCModule` interface: + * Channel (opening) handshake callbacks + * Channel closing handshake callbacks + * Packet callbacks +* Bind to a port(s). +* Add keeper methods. +* Define your packet data and acknowledgment structs as well as how to encode/decode them. +* Add a route to the IBC router. + + + +Now take a look at the `git diff` and see if you can recognize the steps listed above. + +### Implementing the `IBCModule` interface + + + +For a full explanation, visit the [ibc-go docs](https://ibc.cosmos.network/main/ibc/apps/ibcmodule.html). + + + +The Cosmos SDK expects all IBC modules to implement the [`IBCModule` interface](https://github.com/cosmos/ibc-go/tree/main/modules/core/05-port/types/module.go). This interface contains all of the callbacks IBC expects modules to implement. This includes callbacks related to: + +* Channel handshake (`OnChanOpenInit`, `OnChanOpenTry`, `OncChanOpenAck`, and `OnChanOpenConfirm`) +* Channel closing (`OnChanCloseInit` and `OnChanCloseConfirm`) +* Packets (`OnRecvPacket`, `OnAcknowledgementPacket`, and `OnTimeoutPacket`). + +Ignite CLI implements this in the file `x/leaderboard/module_ibc.go`. + + + +```go +// OnChanOpenInit implements the IBCModule interface +func (am AppModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) error { + + // Require portID is the portID module is bound to + boundPort := am.keeper.GetPort(ctx) + if boundPort != portID { + return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", portID, boundPort) + } + + if version != types.Version { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version) + } + + // Claim channel capability passed back by IBC module + if err := am.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return err + } + + return nil + +} + +// OnChanOpenTry implements the IBCModule interface +func (am AppModule) OnChanOpenTry( +ctx sdk.Context, +order channeltypes.Order, +connectionHops []string, +portID, +channelID string, +chanCap \*capabilitytypes.Capability, +counterparty channeltypes.Counterparty, +counterpartyVersion string, +) (string, error) { + + // Require portID is the portID module is bound to + boundPort := am.keeper.GetPort(ctx) + if boundPort != portID { + return "", sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", portID, boundPort) + } + + if counterpartyVersion != types.Version { + return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: got: %s, expected %s", counterpartyVersion, types.Version) + } + + // Module may have already claimed capability in OnChanOpenInit in the case of crossing hellos + // (ie chainA and chainB both call ChanOpenInit before one of them calls ChanOpenTry) + // If the module can already authenticate the capability then the module already owns it so we don't need to claim + // Otherwise, the module does not have channel capability and we must claim it from IBC + if !am.keeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) { + // Only claim channel capability passed back by IBC module if we do not already own it + if err := am.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return "", err + } + } + + return types.Version, nil + +} + +// OnChanOpenAck implements the IBCModule interface +func (am AppModule) OnChanOpenAck( +ctx sdk.Context, +portID, +channelID string, +\_, +counterpartyVersion string, +) error { +if counterpartyVersion != types.Version { +return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: %s, expected %s", counterpartyVersion, types.Version) +} +return nil +} + +// OnChanOpenConfirm implements the IBCModule interface +func (am AppModule) OnChanOpenConfirm( +ctx sdk.Context, +portID, +channelID string, +) error { +return nil +} + +// OnChanCloseInit implements the IBCModule interface +func (am AppModule) OnChanCloseInit( +ctx sdk.Context, +portID, +channelID string, +) error { +// Disallow user-initiated channel closing for channels +return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "user cannot close channel") +} + +// OnChanCloseConfirm implements the IBCModule interface +func (am AppModule) OnChanCloseConfirm( +ctx sdk.Context, +portID, +channelID string, +) error { +return nil +} + +// OnRecvPacket implements the IBCModule interface +func (am AppModule) OnRecvPacket( +ctx sdk.Context, +modulePacket channeltypes.Packet, +relayer sdk.AccAddress, +) ibcexported.Acknowledgement { +var ack channeltypes.Acknowledgement + + // this line is used by starport scaffolding # oracle/packet/module/recv + + var modulePacketData types.LeaderboardPacketData + if err := modulePacketData.Unmarshal(modulePacket.GetData()); err != nil { + return channeltypes.NewErrorAcknowledgement(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal packet data: %s", err.Error()).Error()) + } + + // Dispatch packet + switch packet := modulePacketData.Packet.(type) { + // this line is used by starport scaffolding # ibc/packet/module/recv + default: + errMsg := fmt.Sprintf("unrecognized %s packet type: %T", types.ModuleName, packet) + return channeltypes.NewErrorAcknowledgement(errMsg) + } + + // NOTE: acknowledgment will be written synchronously during IBC handler execution. + return ack + +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (am AppModule) OnAcknowledgementPacket( +ctx sdk.Context, +modulePacket channeltypes.Packet, +acknowledgement []byte, +relayer sdk.AccAddress, +) error { +var ack channeltypes.Acknowledgement +if err := types.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { +return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal packet acknowledgement: %v", err) +} + + // this line is used by starport scaffolding # oracle/packet/module/ack + + var modulePacketData types.LeaderboardPacketData + if err := modulePacketData.Unmarshal(modulePacket.GetData()); err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal packet data: %s", err.Error()) + } + + var eventType string + + // Dispatch packet + switch packet := modulePacketData.Packet.(type) { + // this line is used by starport scaffolding # ibc/packet/module/ack + default: + errMsg := fmt.Sprintf("unrecognized %s packet type: %T", types.ModuleName, packet) + return sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg) + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + eventType, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyAck, fmt.Sprintf("%v", ack)), + ), + ) + + switch resp := ack.Response.(type) { + case *channeltypes.Acknowledgement_Result: + ctx.EventManager().EmitEvent( + sdk.NewEvent( + eventType, + sdk.NewAttribute(types.AttributeKeyAckSuccess, string(resp.Result)), + ), + ) + case *channeltypes.Acknowledgement_Error: + ctx.EventManager().EmitEvent( + sdk.NewEvent( + eventType, + sdk.NewAttribute(types.AttributeKeyAckError, resp.Error), + ), + ) + } + + return nil + +} + +// OnTimeoutPacket implements the IBCModule interface +func (am AppModule) OnTimeoutPacket( +ctx sdk.Context, +modulePacket channeltypes.Packet, +relayer sdk.AccAddress, +) error { +var modulePacketData types.LeaderboardPacketData +if err := modulePacketData.Unmarshal(modulePacket.GetData()); err != nil { +return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal packet data: %s", err.Error()) +} + + // Dispatch packet + switch packet := modulePacketData.Packet.(type) { + // this line is used by starport scaffolding # ibc/packet/module/timeout + default: + errMsg := fmt.Sprintf("unrecognized %s packet type: %T", types.ModuleName, packet) + return sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg) + } + + return nil + +} + +```` + + + +Additionally, in the `module.go` file, add the following line (and the corresponding import): + +```diff +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // Add this line ++ _ porttypes.IBCModule = IBCModule{} +) +``` + +#### Channel handshake version negotiation + +Application modules are expected to verify the versioning used during the channel handshake procedure: + +* `OnChanOpenInit` will verify that the relayer-chosen parameters are valid and perform any custom `INIT` logic. + * It may return an error if the chosen parameters are invalid, in which case the handshake is aborted. If the provided version string is non-empty, `OnChanOpenInit` should return the version string if valid or an error if the provided version is invalid. + * **If the version string is empty, `OnChanOpenInit` is expected to return a default version string representing the version(s) it supports.** If there is no default version string for the application, it should return an error if the provided version is an empty string. +* `OnChanOpenTry` will verify the relayer-chosen parameters along with the counterparty-chosen version string and perform custom `TRY` logic. + * If the relayer-chosen parameters are invalid, the callback must return an error to abort the handshake. If the counterparty-chosen version is not compatible with this module's supported versions, the callback must return an error to abort the handshake. + * If the versions are compatible, the try callback must select the final version string and return it to core IBC.`OnChanOpenTry` may also perform custom initialization logic. +* `OnChanOpenAck` will error if the counterparty selected version string is invalid and abort the handshake. It may also perform custom `ACK` logic. + + + +Versions must be strings but can implement any versioning structure. Often a simple template is used that combines the name of the application and an iteration number, like `leaderboard-1` for the leaderboard IBC module. + +However, the version string can also include metadata to indicate attributes of the channel you are supporting, like applicable middleware and the underlying app version. An example of this is the version string for middleware, which is discussed in a [later section](insert-link.com). + + + +#### Packet callbacks + +The general application packet flow was discussed in [a previous section](https://tutorials.cosmos.network/academy/4-ibc/channels.html#application-packet-flow). As a refresher, let's take a look at the diagram: + +![Packet flow](/academy/ibc-dev/images/packetflow.png) + +The packet callbacks in the packet flow can now be identified by investigating the `IBCModule` interface. + +##### Sending packets + +Modules **do not send packets through callbacks**, since modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where messages sent to the IBC module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. + + + +In order to prevent modules from sending packets on channels they do not own, IBC expects modules to pass in the correct channel capability for the packet's source channel. + + + + + +For advanced readers, more on capabilities can be found in the [ibc-go docs](https://ibc.cosmos.network/main/ibc/overview.html#capabilities) or the [ADR on the Dynamic Capability Store](https://github.com/cosmos/cosmos-sdk/blob/6aaf83c894e917836a047b0399dd70a95fd2710d/docs/architecture/adr-003-dynamic-capability-store.md). + + + +##### Receiving packets + +To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state changes given the packet data without worrying about whether the packet is valid or not. + +Modules may return to the IBC handler an acknowledgment which implements the `Acknowledgement` interface. The IBC handler will then commit this acknowledgment of the packet so that a relayer may relay the acknowledgment back to the sender module. + +The state changes that occurred during this callback will only be written if: + +* The acknowledgment was successful as indicated by the `Success()` function of the acknowledgement. +* The acknowledgment returned is nil, indicating that an asynchronous process is occurring. + + + +Applications that process asynchronous acknowledgments must handle reverting state changes when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written for asynchronous acknowledgments. + + + diff --git a/academy/ibc-dev/images/packetflow.png b/academy/ibc-dev/images/packetflow.png new file mode 100644 index 0000000000..cba71a87fa Binary files /dev/null and b/academy/ibc-dev/images/packetflow.png differ