From a2a0f471ea2bbf5c3b9325dfcc313e01b164cf76 Mon Sep 17 00:00:00 2001 From: Thomas Dekeyser Date: Thu, 7 Jul 2022 10:45:47 +0200 Subject: [PATCH 1/9] restructure content according to outline, fix image and syntax highlighting, fix titles and prepare for content updates --- docs/ibc/apps.md | 358 ++++++++++++++++++++++++----------------------- 1 file changed, 185 insertions(+), 173 deletions(-) diff --git a/docs/ibc/apps.md b/docs/ibc/apps.md index 267a2e5ca55..c8327466689 100644 --- a/docs/ibc/apps.md +++ b/docs/ibc/apps.md @@ -4,21 +4,24 @@ order: 3 # IBC Applications -Learn how to configure your application to use IBC and send data packets to other chains. {synopsis} +Learn how to build custom IBC application modules that enable packets to be sent to and received from other IBC-enabled chains. {synopsis} -This document serves as a guide for developers who want to write their own Inter-blockchain -Communication Protocol (IBC) applications for custom use cases. +This document serves as a guide for developers who want to write their own Inter-blockchain Communication Protocol (IBC) applications for custom use cases. -Due to the modular design of the IBC protocol, IBC -application developers do not need to concern themselves with the low-level details of clients, -connections, and proof verification. Nevertheless a brief explanation of the lower levels of the -stack is given so that application developers may have a high-level understanding of the IBC -protocol. Then the document goes into detail on the abstraction layer most relevant for application -developers (channels and ports), and describes how to define your own custom packets, and -`IBCModule` callbacks. +Due to the modular design of the IBC protocol, IBC application developers do not need to concern themselves with the low-level details of clients, connections, and proof verification. Nevertheless, an overview of these low-level concepts can be found in [a previous section](./overview.md). +The document goes into detail on the abstraction layer most relevant for application developers (channels and ports), and describes how to define your own custom packets, `IBCModule` callbacks and more to make an application module IBC ready. -To have your module interact over IBC you must: bind to a port(s), define your own packet data and acknowledgement structs as well as how to encode/decode them, and implement the -`IBCModule` interface. Below is a more detailed explanation of how to write an IBC application +**To have your module interact over IBC you must:** + +- implement the `IBCModule` interface, i.e.: + - channel (opening) handshake callbacks + - channel closing handshake callbacks + - packet callbacks +- bind to a port(s) +- define your own packet data and acknowledgement structs as well as how to encode/decode them +- add a route to the IBC router + +Below is a more detailed explanation of how to write an IBC application module correctly. ## Pre-requisites Readings @@ -26,17 +29,19 @@ module correctly. - [IBC Overview](./overview.md)) {prereq} - [IBC default integration](./integration.md) {prereq} -## Create a custom IBC application module - -### Implement `IBCModule` Interface and callbacks +## Implement `IBCModule` interface and callbacks 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 section will describe -the callbacks that are called during channel handshake execution. +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. The include callbacks related to channel handshake, opening and closing and packet callbacks (`OnRecvPacket`, `OnAcknowledgementPacket` and `OnTimeoutPacket`). + +### Channel handshake callbacks + +This section will describe the callbacks that are called during channel handshake execution. Here are the channel handshake callbacks that modules are expected to implement: +> Note that some of the code below is _pseudo code_, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `checkArguments(args)` function. + ```go // Called by IBC Handler on MsgOpenInit func (k Keeper) OnChanOpenInit(ctx sdk.Context, @@ -63,7 +68,7 @@ func (k Keeper) OnChanOpenInit(ctx sdk.Context, } // Called by IBC Handler on MsgOpenTry -OnChanOpenTry( +func (k Keeper) OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, connectionHops []string, @@ -83,7 +88,7 @@ OnChanOpenTry( return err } } - + // ... do custom initialization logic // Use above arguments to determine if we want to abort handshake @@ -91,18 +96,18 @@ OnChanOpenTry( return err } - // Construct application version + // Construct application version // IBC applications must return the appropriate application version // This can be a simple string or it can be a complex version constructed - // from the counterpartyVersion and other arguments. - // The version returned will be the channel version used for both channel ends. + // from the counterpartyVersion and other arguments. + // The version returned will be the channel version used for both channel ends. appVersion := negotiateAppVersion(counterpartyVersion, args) - + return appVersion, nil } // Called by IBC Handler on MsgOpenAck -OnChanOpenAck( +func (k Keeper) OnChanOpenAck( ctx sdk.Context, portID, channelID string, @@ -116,7 +121,7 @@ OnChanOpenAck( } // Called by IBC Handler on MsgOpenConfirm -OnChanOpenConfirm( +func (k Keeper) OnChanOpenConfirm( ctx sdk.Context, portID, channelID string, @@ -129,13 +134,11 @@ OnChanOpenConfirm( } ``` -The channel closing handshake will also invoke module callbacks that can return errors to abort the -closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls -`ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. +The channel closing handshake will also invoke module callbacks that can return errors to abort the closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls `ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. ```go // Called by IBC Handler on MsgCloseInit -OnChanCloseInit( +func (k Keeper) OnChanCloseInit( ctx sdk.Context, portID, channelID string, @@ -148,7 +151,7 @@ OnChanCloseInit( } // Called by IBC Handler on MsgCloseConfirm -OnChanCloseConfirm( +func (k Keeper) OnChanCloseConfirm( ctx sdk.Context, portID, channelID string, @@ -161,13 +164,13 @@ OnChanCloseConfirm( } ``` -#### Channel Handshake Version Negotiation +#### Channel handshake version negotiation Application modules are expected to verify versioning used during the channel handshake procedure. -* `ChanOpenInit` callback should verify that the `MsgChanOpenInit.Version` is valid -* `ChanOpenTry` callback should construct the application version used for both channel ends. If no application version can be constructed, it must return an error. -* `ChanOpenAck` callback should verify that the `MsgChanOpenAck.CounterpartyVersion` is valid and supported. +- `ChanOpenInit` callback should verify that the `MsgChanOpenInit.Version` is valid +- `ChanOpenTry` callback should construct the application version used for both channel ends. If no application version can be constructed, it must return an error. +- `ChanOpenAck` callback should verify that the `MsgChanOpenAck.CounterpartyVersion` is valid and supported. IBC expects application modules to perform application version negotiation in `OnChanOpenTry`. The negotiated version must be returned to core IBC. If the version cannot be negotiated, an error should be returned. @@ -186,88 +189,13 @@ encoded version into each handhshake call as necessary. ICS20 currently implements basic string matching with a single supported version. -### Bind Ports - -Currently, ports must be bound on app initialization. A module may bind to ports in `InitGenesis` -like so: - -```go -func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, state types.GenesisState) { - // ... other initialization logic +### Packet callbacks - // Only try to bind to port if it is not already bound, since we may already own - // port capability from capability InitGenesis - if !isBound(ctx, state.PortID) { - // module binds to desired ports on InitChain - // and claims returned capabilities - cap1 := keeper.IBCPortKeeper.BindPort(ctx, port1) - cap2 := keeper.IBCPortKeeper.BindPort(ctx, port2) - cap3 := keeper.IBCPortKeeper.BindPort(ctx, port3) +Just as IBC expected modules to implement callbacks for channel handshakes, IBC also expects modules to implement callbacks for handling the packet flow through a channel, as defined in the `IBCModule` interface. - // NOTE: The module's scoped capability keeper must be private - keeper.scopedKeeper.ClaimCapability(cap1) - keeper.scopedKeeper.ClaimCapability(cap2) - keeper.scopedKeeper.ClaimCapability(cap3) - } +Once a module A and module B are connected to each other, relayers can start relaying packets and acknowledgements back and forth on the channel. - // ... more initialization logic -} -``` - -### Custom Packets - -Modules connected by a channel must agree on what application data they are sending over the -channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up -to each application module to determine how to implement this agreement. However, for most -applications this will happen as a version negotiation during the channel handshake. While more -complex version negotiation is possible to implement inside the channel opening handshake, a very -simple version negotation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). - -Thus, a module must define its a custom packet data structure, along with a well-defined way to -encode and decode it to and from `[]byte`. - -```go -// Custom packet data defined in application module -type CustomPacketData struct { - // Custom fields ... -} - -EncodePacketData(packetData CustomPacketData) []byte { - // encode packetData to bytes -} - -DecodePacketData(encoded []byte) (CustomPacketData) { - // decode from bytes to packet data -} -``` - -Then a module must encode its packet data before sending it through IBC. - -```go -// Sending custom application packet data -data := EncodePacketData(customPacketData) -packet.Data = data -IBCChannelKeeper.SendPacket(ctx, packet) -``` - -A module receiving a packet must decode the `PacketData` into a structure it expects so that it can -act on it. - -```go -// Receiving custom application packet data (in OnRecvPacket) -packetData := DecodePacketData(packet.Data) -// handle received custom packet data -``` - -#### Packet Flow Handling - -Just as IBC expected modules to implement callbacks for channel handshakes, IBC also expects modules -to implement callbacks for handling the packet flow through a channel. - -Once a module A and module B are connected to each other, relayers can start relaying packets and -acknowledgements back and forth on the channel. - -![IBC packet flow diagram](https://media.githubusercontent.com/media/cosmos/ibc/old/spec/ics-004-channel-and-packet-semantics/channel-state-machine.png) +![IBC packet flow diagram](https://ibcprotocol.org/_nuxt/img/packet_flow.1d89ee0.png) Briefly, a successful packet flow works as follows: @@ -278,12 +206,12 @@ Briefly, a successful packet flow works as follows: 4. if the packet is not successfully received before the timeout, then module A processes the packet's timeout. -##### Sending Packets +#### Sending packets + +Modules **do not send packets through callbacks**, since the modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where msgs 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`. -Modules do not send packets through callbacks, since the modules initiate the action of sending -packets to the IBC module, as opposed to other parts of the packet flow where msgs 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`. +> Note that some of the code below is _pseudo code_, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `EncodePacketData(customPacketData)` function. ```go // retrieve the dynamic capability for this channel @@ -300,7 +228,7 @@ In order to prevent modules from sending packets on channels they do not own, IB modules to pass in the correct channel capability for the packet's source channel. ::: -##### Receiving Packets +#### 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 @@ -312,15 +240,18 @@ The IBC handler will then commit this acknowledgement of the packet so that a re acknowledgement back to the sender module. The state changes that occurred during this callback will only be written if: + - the acknowledgement was successful as indicated by the `Success()` function of the acknowledgement - if the acknowledgement returned is nil indicating that an asynchronous process is occurring NOTE: Applications which process asynchronous acknowledgements must handle reverting state changes -when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written -for asynchronous acknowledgements. +when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written +for asynchronous acknowledgements. + +> Note that some of the code below is _pseudo code_, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodePacketData(packet.Data)` function. ```go -OnRecvPacket( +func (k Keeper) OnRecvPacket( ctx sdk.Context, packet channeltypes.Packet, ) ibcexported.Acknowledgement { @@ -328,7 +259,7 @@ OnRecvPacket( packetData := DecodePacketData(packet.Data) // do application state changes based on packet data and return the acknowledgement - // NOTE: The acknowledgement will indicate to the IBC handler if the application + // NOTE: The acknowledgement will indicate to the IBC handler if the application // state changes should be written via the `Success()` function. Application state // changes are only written if the acknowledgement is successful or the acknowledgement // returned is nil indicating that an asynchronous acknowledgement will occur. @@ -338,7 +269,8 @@ OnRecvPacket( } ``` -The Acknowledgement interface: +Reminder, the Acknowledgement interface: + ```go // Acknowledgement defines the interface used to return // acknowledgements in the OnRecvPacket callback. @@ -348,47 +280,7 @@ type Acknowledgement interface { } ``` -### Acknowledgements - -Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. -In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement -will be written once the packet has been processed by the application which may be well after the packet receipt. - -NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement -for a packet as soon as it has been received from the IBC module. - -This acknowledgement can then be relayed back to the original sender chain, which can take action -depending on the contents of the acknowledgement. - -Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and -receive acknowledegments with the IBC modules as byte strings. - -Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an -acknowledgement struct along with encoding and decoding it, is very similar to the packet data -example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) -specifies a recommended format for acknowledgements. This acknowledgement type can be imported from -[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). - -While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): - -```proto -// Acknowledgement is the recommended acknowledgement format to be used by -// app-specific protocols. -// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental -// conflicts with other protobuf message formats used for acknowledgements. -// The first byte of any message with this format will be the non-ASCII values -// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: -// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope -message Acknowledgement { - // response contains either a result or an error and must be non-empty - oneof response { - bytes result = 21; - string error = 22; - } -} -``` - -#### Acknowledging Packets +#### Acknowledging packets After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the @@ -400,8 +292,10 @@ Since the modules are responsible for agreeing on an encoding/decoding standard acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback is responsible for decoding the acknowledgement and processing it. +> Note that some of the code below is _pseudo code_, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodeAcknowledgement(acknowledgments)` and `processAck(ack)` functions. + ```go -OnAcknowledgementPacket( +func (k Keeper) OnAcknowledgementPacket( ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, @@ -415,9 +309,9 @@ OnAcknowledgementPacket( } ``` -#### Timeout Packets +#### Timeout packets -If the timeout for a packet is reached before the packet is successfully received or the +If the timeout for a packet is reached before the packet is successfully received or the counterparty channel end is closed before the packet is successfully received, then the receiving chain can no longer process it. Thus, the sending chain must process the timeout using `OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is @@ -425,7 +319,7 @@ indeed valid, so our module only needs to implement the state machine logic for timeout is reached and the packet can no longer be received. ```go -OnTimeoutPacket( +func (k Keeper) OnTimeoutPacket( ctx sdk.Context, packet channeltypes.Packet, ) (*sdk.Result, error) { @@ -433,9 +327,124 @@ OnTimeoutPacket( } ``` -### Routing +## Bind ports + +Currently, ports must be bound on app initialization. A module may bind to ports in `InitGenesis` +like so: + +```go +func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, state types.GenesisState) { + // ... other initialization logic + + // Only try to bind to port if it is not already bound, since we may already own + // port capability from capability InitGenesis + if !isBound(ctx, state.PortID) { + // module binds to desired ports on InitChain + // and claims returned capabilities + cap1 := keeper.IBCPortKeeper.BindPort(ctx, port1) + cap2 := keeper.IBCPortKeeper.BindPort(ctx, port2) + cap3 := keeper.IBCPortKeeper.BindPort(ctx, port3) + + // NOTE: The module's scoped capability keeper must be private + keeper.scopedKeeper.ClaimCapability(cap1) + keeper.scopedKeeper.ClaimCapability(cap2) + keeper.scopedKeeper.ClaimCapability(cap3) + } + + // ... more initialization logic +} +``` + +## Define packets and acks + +### Custom packets + +Modules connected by a channel must agree on what application data they are sending over the +channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up +to each application module to determine how to implement this agreement. However, for most +applications this will happen as a version negotiation during the channel handshake. While more +complex version negotiation is possible to implement inside the channel opening handshake, a very +simple version negotation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). + +Thus, a module must define its a custom packet data structure, along with a well-defined way to +encode and decode it to and from `[]byte`. + +```go +// Custom packet data defined in application module +type CustomPacketData struct { + // Custom fields ... +} + +EncodePacketData(packetData CustomPacketData) []byte { + // encode packetData to bytes +} + +DecodePacketData(encoded []byte) (CustomPacketData) { + // decode from bytes to packet data +} +``` + +Then a module must encode its packet data before sending it through IBC. + +```go +// Sending custom application packet data +data := EncodePacketData(customPacketData) +packet.Data = data +IBCChannelKeeper.SendPacket(ctx, packet) +``` + +A module receiving a packet must decode the `PacketData` into a structure it expects so that it can +act on it. + +```go +// Receiving custom application packet data (in OnRecvPacket) +packetData := DecodePacketData(packet.Data) +// handle received custom packet data +``` + +### Acknowledgements + +Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. +In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement +will be written once the packet has been processed by the application which may be well after the packet receipt. + +NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement +for a packet as soon as it has been received from the IBC module. + +This acknowledgement can then be relayed back to the original sender chain, which can take action +depending on the contents of the acknowledgement. + +Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and +receive acknowledegments with the IBC modules as byte strings. + +Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an +acknowledgement struct along with encoding and decoding it, is very similar to the packet data +example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) +specifies a recommended format for acknowledgements. This acknowledgement type can be imported from +[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). -As mentioned above, modules must implement the IBC module interface (which contains both channel +While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): + +```protobuf +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} +``` + +## Routing + +As mentioned above, modules must implement the `IBCModule` interface (which contains both channel handshake callbacks and packet handling callbacks). The concrete implementation of this interface must be registered with the module name as a route on the IBC `Router`. @@ -454,9 +463,12 @@ ibcRouter.AddRoute(moduleName, moduleCallbacks) // Setting Router will finalize all routes by sealing router // No more routes can be added app.IBCKeeper.SetRouter(ibcRouter) + +// ... +} ``` -## Working Example +## Working example For a real working example of an IBC application, you can look through the `ibc-transfer` module which implements everything discussed above. From 8e30bb90f66a6016d1e3e5c31c017e98b33c2ba4 Mon Sep 17 00:00:00 2001 From: Thomas Dekeyser Date: Thu, 7 Jul 2022 16:31:37 +0200 Subject: [PATCH 2/9] rewrite bind port section --- docs/ibc/apps.md | 111 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 89 insertions(+), 22 deletions(-) diff --git a/docs/ibc/apps.md b/docs/ibc/apps.md index c8327466689..93b54cb832d 100644 --- a/docs/ibc/apps.md +++ b/docs/ibc/apps.md @@ -332,28 +332,95 @@ func (k Keeper) OnTimeoutPacket( Currently, ports must be bound on app initialization. A module may bind to ports in `InitGenesis` like so: -```go -func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, state types.GenesisState) { - // ... other initialization logic - - // Only try to bind to port if it is not already bound, since we may already own - // port capability from capability InitGenesis - if !isBound(ctx, state.PortID) { - // module binds to desired ports on InitChain - // and claims returned capabilities - cap1 := keeper.IBCPortKeeper.BindPort(ctx, port1) - cap2 := keeper.IBCPortKeeper.BindPort(ctx, port2) - cap3 := keeper.IBCPortKeeper.BindPort(ctx, port3) - - // NOTE: The module's scoped capability keeper must be private - keeper.scopedKeeper.ClaimCapability(cap1) - keeper.scopedKeeper.ClaimCapability(cap2) - keeper.scopedKeeper.ClaimCapability(cap3) - } - - // ... more initialization logic -} -``` +In order to bind modules to their respective ports on initialization, the following needs to be implemented: + +1. In the `module.go` file, add: + + ```go + var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // Add this line + _ porttypes.IBCModule = IBCModule{} + ) + ``` + +1. Add port ID to the `GenesisState` proto definition: + + ```protobuf + message GenesisState { + string port_id = 1; + // other fields + } + ``` + +1. Add port ID to `x/moduleName/types/genesis.go`: + + ```go + // in x/moduleName/types/genesis.go + + // DefaultGenesisState returns a GenesisState with "transfer" as the default PortID. + func DefaultGenesisState() *GenesisState { + return &GenesisState{ + PortId: PortID, + // additional k-v fields + } + } + + // Validate performs basic genesis state validation returning an error upon any + // failure. + func (gs GenesisState) Validate() error { + if err := host.PortIdentifierValidator(gs.PortId); err != nil { + return err + } + //addtional validations + + return gs.Params.Validate() + } + ``` + +1. Bind to port(s) in `InitGenesis`: + + ```go + // InitGenesis initializes the ibc-module state and binds to PortID. + func (k Keeper) InitGenesis(ctx sdk.Context, state types.GenesisState) { + k.SetPort(ctx, state.PortId) + + // ... + + // Only try to bind to port if it is not already bound, since we may already own + // port capability from capability InitGenesis + if !k.IsBound(ctx, state.PortId) { + // transfer module binds to the transfer port on InitChain + // and claims the returned capability + err := k.BindPort(ctx, state.PortId) + if err != nil { + panic(fmt.Sprintf("could not claim port capability: %v", err)) + } + } + + // ... + } + ``` + + With: + + ```go + // IsBound checks if the module is already bound to the desired port + func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) + return ok + } + + // BindPort defines a wrapper function for the port Keeper's function in + // order to expose it to module's InitGenesis function + func (k Keeper) BindPort(ctx sdk.Context, portID string) error { + cap := k.portKeeper.BindPort(ctx, portID) + return k.ClaimCapability(ctx, cap, host.PortPath(portID)) + } + ``` + + The module binds to the desired port(s) and returns the capabilities. ## Define packets and acks From 1c3b8d81a6816bf6b3451aed6256bafdb8195f11 Mon Sep 17 00:00:00 2001 From: Thomas Dekeyser Date: Thu, 7 Jul 2022 17:16:02 +0200 Subject: [PATCH 3/9] restructure applications doc into folder structure --- docs/.vuepress/config.js | 283 +++++++++++++----------- docs/ibc/apps/apps.md | 50 +++++ docs/ibc/apps/bindports.md | 97 ++++++++ docs/ibc/{apps.md => apps/ibcmodule.md} | 273 +---------------------- docs/ibc/apps/packets_acks.md | 92 ++++++++ docs/ibc/apps/routing.md | 31 +++ 6 files changed, 436 insertions(+), 390 deletions(-) create mode 100644 docs/ibc/apps/apps.md create mode 100644 docs/ibc/apps/bindports.md rename docs/ibc/{apps.md => apps/ibcmodule.md} (56%) create mode 100644 docs/ibc/apps/packets_acks.md create mode 100644 docs/ibc/apps/routing.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index a15407fd54a..a5841915b3b 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -3,19 +3,48 @@ module.exports = { title: "IBC-Go", locales: { "/": { - lang: "en-US" + lang: "en-US", }, }, base: process.env.VUEPRESS_BASE || "/", head: [ - ['link', { rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" }], - ['link', { rel: "icon", type: "image/png", sizes: "32x32", href: "/favicon-32x32.png" }], - ['link', { rel: "icon", type: "image/png", sizes: "16x16", href: "/favicon-16x16.png" }], - ['link', { rel: "manifest", href: "/site.webmanifest" }], - ['meta', { name: "msapplication-TileColor", content: "#2e3148" }], - ['meta', { name: "theme-color", content: "#ffffff" }], - ['link', { rel: "icon", type: "image/svg+xml", href: "/favicon-svg.svg" }], - ['link', { rel: "apple-touch-icon-precomposed", href: "/apple-touch-icon-precomposed.png" }], + [ + "link", + { + rel: "apple-touch-icon", + sizes: "180x180", + href: "/apple-touch-icon.png", + }, + ], + [ + "link", + { + rel: "icon", + type: "image/png", + sizes: "32x32", + href: "/favicon-32x32.png", + }, + ], + [ + "link", + { + rel: "icon", + type: "image/png", + sizes: "16x16", + href: "/favicon-16x16.png", + }, + ], + ["link", { rel: "manifest", href: "/site.webmanifest" }], + ["meta", { name: "msapplication-TileColor", content: "#2e3148" }], + ["meta", { name: "theme-color", content: "#ffffff" }], + ["link", { rel: "icon", type: "image/svg+xml", href: "/favicon-svg.svg" }], + [ + "link", + { + rel: "apple-touch-icon-precomposed", + href: "/apple-touch-icon-precomposed.png", + }, + ], ], themeConfig: { repo: "cosmos/ibc-go", @@ -32,109 +61,109 @@ module.exports = { //}, versions: [ { - "label": "main", - "key": "main" + label: "main", + key: "main", }, { - "label": "v1.1.0", - "key": "v1.1.0" + label: "v1.1.0", + key: "v1.1.0", }, { - "label": "v1.2.0", - "key": "v1.2.0" + label: "v1.2.0", + key: "v1.2.0", }, { - "label": "v1.3.0", - "key": "v1.3.0" + label: "v1.3.0", + key: "v1.3.0", }, { - "label": "v1.5.0", - "key": "v1.5.0" + label: "v1.5.0", + key: "v1.5.0", }, { - "label": "v1.4.0", - "key": "v1.4.0" + label: "v1.4.0", + key: "v1.4.0", }, { - "label": "v2.0.0", - "key": "v2.0.0" - } , + label: "v2.0.0", + key: "v2.0.0", + }, { - "label": "v2.1.0", - "key": "v2.1.0" - }, - { - "label": "v2.2.0", - "key": "v2.2.0" + label: "v2.1.0", + key: "v2.1.0", }, - { - "label": "v2.3.0", - "key": "v2.3.0" + { + label: "v2.2.0", + key: "v2.2.0", }, { - "label": "v3.0.0", - "key": "v3.0.0" + label: "v2.3.0", + key: "v2.3.0", }, { - "label": "v3.1.0", - "key": "v3.1.0" - } + label: "v3.0.0", + key: "v3.0.0", + }, + { + label: "v3.1.0", + key: "v3.1.0", + }, ], topbar: { - banner: true + banner: true, }, - sidebar: { + sidebar: { auto: false, nav: [ - { + { title: "Using IBC-Go", children: [ { title: "Overview", directory: false, - path: "/ibc/overview.html" - }, + path: "/ibc/overview.html", + }, { title: "Integration", directory: false, - path: "/ibc/integration.html" + path: "/ibc/integration.html", }, { title: "Applications", - directory: false, - path: "/ibc/apps.html" + directory: true, + path: "/ibc/apps", }, { title: "Middleware", directory: true, - path: "/ibc/middleware" + path: "/ibc/middleware", }, { title: "Upgrades", directory: true, - path: "/ibc/upgrades" + path: "/ibc/upgrades", }, { title: "Governance Proposals", directory: false, - path: "/ibc/proposals.html" + path: "/ibc/proposals.html", }, { title: "Relayer", directory: false, - path: "/ibc/relayer.html" + path: "/ibc/relayer.html", }, { title: "Protobuf Documentation", directory: false, - path: "/ibc/proto-docs.html" + path: "/ibc/proto-docs.html", }, { title: "Roadmap", directory: false, - path: "/roadmap/roadmap.html" + path: "/roadmap/roadmap.html", }, - ] + ], }, { title: "IBC Application Modules", @@ -147,34 +176,34 @@ module.exports = { { title: "Overview", directory: false, - path: "/apps/interchain-accounts/overview.html" - }, + path: "/apps/interchain-accounts/overview.html", + }, { title: "Authentication Modules", directory: false, - path: "/apps/interchain-accounts/auth-modules.html" + path: "/apps/interchain-accounts/auth-modules.html", }, { title: "Active Channels", directory: false, - path: "/apps/interchain-accounts/active-channels.html" + path: "/apps/interchain-accounts/active-channels.html", }, { title: "Integration", directory: false, - path: "/apps/interchain-accounts/integration.html" + path: "/apps/interchain-accounts/integration.html", }, { title: "Parameters", directory: false, - path: "/apps/interchain-accounts/parameters.html" + path: "/apps/interchain-accounts/parameters.html", }, { title: "Transactions", directory: false, - path: "/apps/interchain-accounts/transactions.html" + path: "/apps/interchain-accounts/transactions.html", }, - ] + ], }, { title: "Transfer", @@ -184,41 +213,41 @@ module.exports = { { title: "Overview", directory: false, - path: "/apps/transfer/overview.html" - }, + path: "/apps/transfer/overview.html", + }, { title: "State", directory: false, - path: "/apps/transfer/state.html" + path: "/apps/transfer/state.html", }, { title: "State Transitions", directory: false, - path: "/apps/transfer/state-transitions.html" + path: "/apps/transfer/state-transitions.html", }, { title: "Messages", directory: false, - path: "/apps/transfer/messages.html" + path: "/apps/transfer/messages.html", }, { title: "Events", directory: false, - path: "/apps/transfer/events.html" + path: "/apps/transfer/events.html", }, { title: "Metrics", directory: false, - path: "/apps/transfer/metrics.html" + path: "/apps/transfer/metrics.html", }, { title: "Params", directory: false, - path: "/apps/transfer/params.html" + path: "/apps/transfer/params.html", }, - ] + ], }, - ] + ], }, { title: "IBC Middleware Modules", @@ -231,77 +260,78 @@ module.exports = { { title: "Overview", directory: false, - path: "/middleware/ics29-fee/overview.html" - }, + path: "/middleware/ics29-fee/overview.html", + }, { title: "Integration", directory: false, - path: "/middleware/ics29-fee/integration.html" + path: "/middleware/ics29-fee/integration.html", }, { title: "End Users", directory: false, - path: "/middleware/ics29-fee/end-users.html" + path: "/middleware/ics29-fee/end-users.html", }, { title: "Fee Messages", directory: false, - path: "/middleware/ics29-fee/msgs.html" + path: "/middleware/ics29-fee/msgs.html", }, { title: "Fee Distribution", directory: false, - path: "/middleware/ics29-fee/fee-distribution.html" + path: "/middleware/ics29-fee/fee-distribution.html", }, { title: "Events", directory: false, - path: "/middleware/ics29-fee/events.html" + path: "/middleware/ics29-fee/events.html", }, - ] + ], }, - ] + ], }, { title: "Migrations", children: [ { - title: "Support transfer of coins whose base denom contains slashes", + title: + "Support transfer of coins whose base denom contains slashes", directory: false, - path: "/migrations/support-denoms-with-slashes.html" + path: "/migrations/support-denoms-with-slashes.html", }, { title: "SDK v0.43 to IBC-Go v1", directory: false, - path: "/migrations/sdk-to-v1.html" + path: "/migrations/sdk-to-v1.html", }, { title: "IBC-Go v1 to v2", directory: false, - path: "/migrations/v1-to-v2.html" + path: "/migrations/v1-to-v2.html", }, { title: "IBC-Go v2 to v3", directory: false, - path: "/migrations/v2-to-v3.html" + path: "/migrations/v2-to-v3.html", }, { title: "IBC-Go v3 to v4", directory: false, - path: "/migrations/v3-to-v4.html" + path: "/migrations/v3-to-v4.html", }, - ] + ], }, { title: "Resources", children: [ { title: "IBC Specification", - path: "https://github.com/cosmos/ibc" + path: "https://github.com/cosmos/ibc", }, - ] - } - ] + ], + }, + ], }, gutter: { title: "Help & Support", @@ -310,46 +340,46 @@ module.exports = { title: "Discord", text: "Chat with IBC developers on Discord.", url: "https://discordapp.com/channels/669268347736686612", - bg: "linear-gradient(225.11deg, #2E3148 0%, #161931 95.68%)" + bg: "linear-gradient(225.11deg, #2E3148 0%, #161931 95.68%)", }, github: { title: "Found an Issue?", - text: "Help us improve this page by suggesting edits on GitHub." - } + text: "Help us improve this page by suggesting edits on GitHub.", + }, }, footer: { question: { - text: "Chat with IBC developers in Discord." + text: "Chat with IBC developers in Discord.", }, textLink: { text: "ibcprotocol.org", - url: "https://ibcprotocol.org" + url: "https://ibcprotocol.org", }, services: [ { service: "medium", - url: "https://blog.cosmos.network/" + url: "https://blog.cosmos.network/", }, { service: "twitter", - url: "https://twitter.com/cosmos" + url: "https://twitter.com/cosmos", }, { service: "linkedin", - url: "https://www.linkedin.com/company/interchain-gmbh" + url: "https://www.linkedin.com/company/interchain-gmbh", }, { service: "reddit", - url: "https://reddit.com/r/cosmosnetwork" + url: "https://reddit.com/r/cosmosnetwork", }, { service: "telegram", - url: "https://t.me/cosmosproject" + url: "https://t.me/cosmosproject", }, { service: "youtube", - url: "https://www.youtube.com/c/CosmosProject" - } + url: "https://www.youtube.com/c/CosmosProject", + }, ], smallprint: "The development of IBC-Go is led primarily by [Interchain GmbH](https://interchain.berlin/). Funding for this development comes primarily from the Interchain Foundation, a Swiss non-profit.", @@ -359,64 +389,63 @@ module.exports = { children: [ { title: "Cosmos SDK", - url: "https://docs.cosmos.network" + url: "https://docs.cosmos.network", }, { title: "Cosmos Hub", - url: "https://hub.cosmos.network" + url: "https://hub.cosmos.network", }, { title: "Tendermint Core", - url: "https://docs.tendermint.com" - } - ] + url: "https://docs.tendermint.com", + }, + ], }, { title: "Community", children: [ { title: "Cosmos blog", - url: "https://blog.cosmos.network" + url: "https://blog.cosmos.network", }, { title: "Forum", - url: "https://forum.cosmos.network" + url: "https://forum.cosmos.network", }, { title: "Chat", - url: "https://discord.gg/W8trcGV" - } - ] + url: "https://discord.gg/W8trcGV", + }, + ], }, { title: "Contributing", children: [ { title: "Contributing to the docs", - url: - "https://github.com/cosmos/ibc-go/blob/main/docs/DOCS_README.md" + url: "https://github.com/cosmos/ibc-go/blob/main/docs/DOCS_README.md", }, { title: "Source code on GitHub", - url: "https://github.com/cosmos/ibc-go/" - } - ] - } - ] - } + url: "https://github.com/cosmos/ibc-go/", + }, + ], + }, + ], + }, }, plugins: [ [ "@vuepress/google-analytics", { - ga: "UA-51029217-2" - } + ga: "UA-51029217-2", + }, ], [ "sitemap", { - hostname: "https://ibc.cosmos.network" - } - ] - ] + hostname: "https://ibc.cosmos.network", + }, + ], + ], }; diff --git a/docs/ibc/apps/apps.md b/docs/ibc/apps/apps.md new file mode 100644 index 00000000000..5d71e7c3f3d --- /dev/null +++ b/docs/ibc/apps/apps.md @@ -0,0 +1,50 @@ + + +# IBC Applications + +Learn how to build custom IBC application modules that enable packets to be sent to and received from other IBC-enabled chains. {synopsis} + +This document serves as a guide for developers who want to write their own Inter-blockchain Communication Protocol (IBC) applications for custom use cases. + +Due to the modular design of the IBC protocol, IBC application developers do not need to concern themselves with the low-level details of clients, connections, and proof verification. Nevertheless, an overview of these low-level concepts can be found in [a previous section](./overview.md). +The document goes into detail on the abstraction layer most relevant for application developers (channels and ports), and describes how to define your own custom packets, `IBCModule` callbacks and more to make an application module IBC ready. + +**To have your module interact over IBC you must:** + +- implement the `IBCModule` interface, i.e.: + - channel (opening) handshake callbacks + - channel closing handshake callbacks + - packet callbacks +- bind to a port(s) +- define your own packet data and acknowledgement structs as well as how to encode/decode them +- add a route to the IBC router + +The following sections provide a more detailed explanation of how to write an IBC application +module correctly corresponding to the listed steps. + +## Pre-requisites Readings + +- [IBC Overview](./overview.md)) {prereq} +- [IBC default integration](./integration.md) {prereq} + +## Working example + +For a real working example of an IBC application, you can look through the `ibc-transfer` module +which implements everything discussed in this section. + +Here are the useful parts of the module to look at: + +[Binding to transfer +port](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/genesis.go) + +[Sending transfer +packets](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/relay.go) + +[Implementing IBC +callbacks](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/ibc_module.go) + +## Next {hide} + +Learn about [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/intro.md) {hide} diff --git a/docs/ibc/apps/bindports.md b/docs/ibc/apps/bindports.md new file mode 100644 index 00000000000..11f9df77857 --- /dev/null +++ b/docs/ibc/apps/bindports.md @@ -0,0 +1,97 @@ + + +# Bind ports + +Learn what changes to make to bind modules to their ports on initialization. {synopsis} + +Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: + +1. In the `module.go` file, add: + + ```go + var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // Add this line + _ porttypes.IBCModule = IBCModule{} + ) + ``` + +1. Add port ID to the `GenesisState` proto definition: + + ```protobuf + message GenesisState { + string port_id = 1; + // other fields + } + ``` + +1. Add port ID to `x//types/genesis.go`: + + ```go + // in x//types/genesis.go + + // DefaultGenesisState returns a GenesisState with "transfer" as the default PortID. + func DefaultGenesisState() *GenesisState { + return &GenesisState{ + PortId: PortID, + // additional k-v fields + } + } + + // Validate performs basic genesis state validation returning an error upon any + // failure. + func (gs GenesisState) Validate() error { + if err := host.PortIdentifierValidator(gs.PortId); err != nil { + return err + } + //addtional validations + + return gs.Params.Validate() + } + ``` + +1. Bind to port(s) in `InitGenesis`: + + ```go + // InitGenesis initializes the ibc-module state and binds to PortID. + func (k Keeper) InitGenesis(ctx sdk.Context, state types.GenesisState) { + k.SetPort(ctx, state.PortId) + + // ... + + // Only try to bind to port if it is not already bound, since we may already own + // port capability from capability InitGenesis + if !k.IsBound(ctx, state.PortId) { + // transfer module binds to the transfer port on InitChain + // and claims the returned capability + err := k.BindPort(ctx, state.PortId) + if err != nil { + panic(fmt.Sprintf("could not claim port capability: %v", err)) + } + } + + // ... + } + ``` + + With: + + ```go + // IsBound checks if the module is already bound to the desired port + func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) + return ok + } + + // BindPort defines a wrapper function for the port Keeper's function in + // order to expose it to module's InitGenesis function + func (k Keeper) BindPort(ctx sdk.Context, portID string) error { + cap := k.portKeeper.BindPort(ctx, portID) + return k.ClaimCapability(ctx, cap, host.PortPath(portID)) + } + ``` + + The module binds to the desired port(s) and returns the capabilities. diff --git a/docs/ibc/apps.md b/docs/ibc/apps/ibcmodule.md similarity index 56% rename from docs/ibc/apps.md rename to docs/ibc/apps/ibcmodule.md index 93b54cb832d..1dd05c0071d 100644 --- a/docs/ibc/apps.md +++ b/docs/ibc/apps/ibcmodule.md @@ -1,40 +1,15 @@ -# IBC Applications +# Implement `IBCModule` interface and callbacks -Learn how to build custom IBC application modules that enable packets to be sent to and received from other IBC-enabled chains. {synopsis} - -This document serves as a guide for developers who want to write their own Inter-blockchain Communication Protocol (IBC) applications for custom use cases. - -Due to the modular design of the IBC protocol, IBC application developers do not need to concern themselves with the low-level details of clients, connections, and proof verification. Nevertheless, an overview of these low-level concepts can be found in [a previous section](./overview.md). -The document goes into detail on the abstraction layer most relevant for application developers (channels and ports), and describes how to define your own custom packets, `IBCModule` callbacks and more to make an application module IBC ready. - -**To have your module interact over IBC you must:** - -- implement the `IBCModule` interface, i.e.: - - channel (opening) handshake callbacks - - channel closing handshake callbacks - - packet callbacks -- bind to a port(s) -- define your own packet data and acknowledgement structs as well as how to encode/decode them -- add a route to the IBC router - -Below is a more detailed explanation of how to write an IBC application -module correctly. - -## Pre-requisites Readings - -- [IBC Overview](./overview.md)) {prereq} -- [IBC default integration](./integration.md) {prereq} - -## Implement `IBCModule` interface and callbacks +Learn how to implement the `IBCModule` interface and all of the callbacks it requires. {synopsis} 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. The include callbacks related to channel handshake, opening and closing and packet callbacks (`OnRecvPacket`, `OnAcknowledgementPacket` and `OnTimeoutPacket`). -### Channel handshake callbacks +## Channel handshake callbacks This section will describe the callbacks that are called during channel handshake execution. @@ -164,7 +139,7 @@ func (k Keeper) OnChanCloseConfirm( } ``` -#### Channel handshake version negotiation +### Channel handshake version negotiation Application modules are expected to verify versioning used during the channel handshake procedure. @@ -189,7 +164,7 @@ encoded version into each handhshake call as necessary. ICS20 currently implements basic string matching with a single supported version. -### Packet callbacks +## Packet callbacks Just as IBC expected modules to implement callbacks for channel handshakes, IBC also expects modules to implement callbacks for handling the packet flow through a channel, as defined in the `IBCModule` interface. @@ -206,7 +181,7 @@ Briefly, a successful packet flow works as follows: 4. if the packet is not successfully received before the timeout, then module A processes the packet's timeout. -#### Sending packets +### Sending packets Modules **do not send packets through callbacks**, since the modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where msgs 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`. @@ -228,7 +203,7 @@ In order to prevent modules from sending packets on channels they do not own, IB modules to pass in the correct channel capability for the packet's source channel. ::: -#### Receiving packets +### 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 @@ -280,7 +255,7 @@ type Acknowledgement interface { } ``` -#### Acknowledging packets +### Acknowledging packets After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the @@ -309,7 +284,7 @@ func (k Keeper) OnAcknowledgementPacket( } ``` -#### Timeout packets +### Timeout packets If the timeout for a packet is reached before the packet is successfully received or the counterparty channel end is closed before the packet is successfully received, then the receiving @@ -326,231 +301,3 @@ func (k Keeper) OnTimeoutPacket( // do custom timeout logic } ``` - -## Bind ports - -Currently, ports must be bound on app initialization. A module may bind to ports in `InitGenesis` -like so: - -In order to bind modules to their respective ports on initialization, the following needs to be implemented: - -1. In the `module.go` file, add: - - ```go - var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} - // Add this line - _ porttypes.IBCModule = IBCModule{} - ) - ``` - -1. Add port ID to the `GenesisState` proto definition: - - ```protobuf - message GenesisState { - string port_id = 1; - // other fields - } - ``` - -1. Add port ID to `x/moduleName/types/genesis.go`: - - ```go - // in x/moduleName/types/genesis.go - - // DefaultGenesisState returns a GenesisState with "transfer" as the default PortID. - func DefaultGenesisState() *GenesisState { - return &GenesisState{ - PortId: PortID, - // additional k-v fields - } - } - - // Validate performs basic genesis state validation returning an error upon any - // failure. - func (gs GenesisState) Validate() error { - if err := host.PortIdentifierValidator(gs.PortId); err != nil { - return err - } - //addtional validations - - return gs.Params.Validate() - } - ``` - -1. Bind to port(s) in `InitGenesis`: - - ```go - // InitGenesis initializes the ibc-module state and binds to PortID. - func (k Keeper) InitGenesis(ctx sdk.Context, state types.GenesisState) { - k.SetPort(ctx, state.PortId) - - // ... - - // Only try to bind to port if it is not already bound, since we may already own - // port capability from capability InitGenesis - if !k.IsBound(ctx, state.PortId) { - // transfer module binds to the transfer port on InitChain - // and claims the returned capability - err := k.BindPort(ctx, state.PortId) - if err != nil { - panic(fmt.Sprintf("could not claim port capability: %v", err)) - } - } - - // ... - } - ``` - - With: - - ```go - // IsBound checks if the module is already bound to the desired port - func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { - _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) - return ok - } - - // BindPort defines a wrapper function for the port Keeper's function in - // order to expose it to module's InitGenesis function - func (k Keeper) BindPort(ctx sdk.Context, portID string) error { - cap := k.portKeeper.BindPort(ctx, portID) - return k.ClaimCapability(ctx, cap, host.PortPath(portID)) - } - ``` - - The module binds to the desired port(s) and returns the capabilities. - -## Define packets and acks - -### Custom packets - -Modules connected by a channel must agree on what application data they are sending over the -channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up -to each application module to determine how to implement this agreement. However, for most -applications this will happen as a version negotiation during the channel handshake. While more -complex version negotiation is possible to implement inside the channel opening handshake, a very -simple version negotation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). - -Thus, a module must define its a custom packet data structure, along with a well-defined way to -encode and decode it to and from `[]byte`. - -```go -// Custom packet data defined in application module -type CustomPacketData struct { - // Custom fields ... -} - -EncodePacketData(packetData CustomPacketData) []byte { - // encode packetData to bytes -} - -DecodePacketData(encoded []byte) (CustomPacketData) { - // decode from bytes to packet data -} -``` - -Then a module must encode its packet data before sending it through IBC. - -```go -// Sending custom application packet data -data := EncodePacketData(customPacketData) -packet.Data = data -IBCChannelKeeper.SendPacket(ctx, packet) -``` - -A module receiving a packet must decode the `PacketData` into a structure it expects so that it can -act on it. - -```go -// Receiving custom application packet data (in OnRecvPacket) -packetData := DecodePacketData(packet.Data) -// handle received custom packet data -``` - -### Acknowledgements - -Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. -In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement -will be written once the packet has been processed by the application which may be well after the packet receipt. - -NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement -for a packet as soon as it has been received from the IBC module. - -This acknowledgement can then be relayed back to the original sender chain, which can take action -depending on the contents of the acknowledgement. - -Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and -receive acknowledegments with the IBC modules as byte strings. - -Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an -acknowledgement struct along with encoding and decoding it, is very similar to the packet data -example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) -specifies a recommended format for acknowledgements. This acknowledgement type can be imported from -[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). - -While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): - -```protobuf -// Acknowledgement is the recommended acknowledgement format to be used by -// app-specific protocols. -// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental -// conflicts with other protobuf message formats used for acknowledgements. -// The first byte of any message with this format will be the non-ASCII values -// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: -// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope -message Acknowledgement { - // response contains either a result or an error and must be non-empty - oneof response { - bytes result = 21; - string error = 22; - } -} -``` - -## Routing - -As mentioned above, modules must implement the `IBCModule` interface (which contains both channel -handshake callbacks and packet handling callbacks). The concrete implementation of this interface -must be registered with the module name as a route on the IBC `Router`. - -```go -// app.go -func NewApp(...args) *App { -// ... - -// Create static IBC router, add module routes, then set and seal it -ibcRouter := port.NewRouter() - -ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) -// Note: moduleCallbacks must implement IBCModule interface -ibcRouter.AddRoute(moduleName, moduleCallbacks) - -// Setting Router will finalize all routes by sealing router -// No more routes can be added -app.IBCKeeper.SetRouter(ibcRouter) - -// ... -} -``` - -## Working example - -For a real working example of an IBC application, you can look through the `ibc-transfer` module -which implements everything discussed above. - -Here are the useful parts of the module to look at: - -[Binding to transfer -port](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/genesis.go) - -[Sending transfer -packets](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/relay.go) - -[Implementing IBC -callbacks](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/ibc_module.go) - -## Next {hide} - -Learn about [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/intro.md) {hide} diff --git a/docs/ibc/apps/packets_acks.md b/docs/ibc/apps/packets_acks.md new file mode 100644 index 00000000000..1dc9fd94fb1 --- /dev/null +++ b/docs/ibc/apps/packets_acks.md @@ -0,0 +1,92 @@ + + +# Define packets and acks + +Learn how to define custom packet and acknowledgement structs and how to encode and decode them. {synopsis} + +## Custom packets + +Modules connected by a channel must agree on what application data they are sending over the +channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up +to each application module to determine how to implement this agreement. However, for most +applications this will happen as a version negotiation during the channel handshake. While more +complex version negotiation is possible to implement inside the channel opening handshake, a very +simple version negotation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). + +Thus, a module must define its a custom packet data structure, along with a well-defined way to +encode and decode it to and from `[]byte`. + +```go +// Custom packet data defined in application module +type CustomPacketData struct { + // Custom fields ... +} + +EncodePacketData(packetData CustomPacketData) []byte { + // encode packetData to bytes +} + +DecodePacketData(encoded []byte) (CustomPacketData) { + // decode from bytes to packet data +} +``` + +Then a module must encode its packet data before sending it through IBC. + +```go +// Sending custom application packet data +data := EncodePacketData(customPacketData) +packet.Data = data +IBCChannelKeeper.SendPacket(ctx, packet) +``` + +A module receiving a packet must decode the `PacketData` into a structure it expects so that it can +act on it. + +```go +// Receiving custom application packet data (in OnRecvPacket) +packetData := DecodePacketData(packet.Data) +// handle received custom packet data +``` + +## Acknowledgements + +Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. +In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement +will be written once the packet has been processed by the application which may be well after the packet receipt. + +NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement +for a packet as soon as it has been received from the IBC module. + +This acknowledgement can then be relayed back to the original sender chain, which can take action +depending on the contents of the acknowledgement. + +Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and +receive acknowledegments with the IBC modules as byte strings. + +Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an +acknowledgement struct along with encoding and decoding it, is very similar to the packet data +example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) +specifies a recommended format for acknowledgements. This acknowledgement type can be imported from +[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). + +While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): + +```protobuf +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} +``` diff --git a/docs/ibc/apps/routing.md b/docs/ibc/apps/routing.md new file mode 100644 index 00000000000..7f05aa52d06 --- /dev/null +++ b/docs/ibc/apps/routing.md @@ -0,0 +1,31 @@ + + +# Routing + +Learn how to hook a route to the IBC router for the custom IBC module. {synopsis} + +As mentioned above, modules must implement the `IBCModule` interface (which contains both channel +handshake callbacks and packet handling callbacks). The concrete implementation of this interface +must be registered with the module name as a route on the IBC `Router`. + +```go +// app.go +func NewApp(...args) *App { +// ... + +// Create static IBC router, add module routes, then set and seal it +ibcRouter := port.NewRouter() + +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) +// Note: moduleCallbacks must implement IBCModule interface +ibcRouter.AddRoute(moduleName, moduleCallbacks) + +// Setting Router will finalize all routes by sealing router +// No more routes can be added +app.IBCKeeper.SetRouter(ibcRouter) + +// ... +} +``` From 043511ffb3e49e99ff5fd7e965831c31b08a23f9 Mon Sep 17 00:00:00 2001 From: Thomas Dekeyser Date: Fri, 8 Jul 2022 08:34:15 +0200 Subject: [PATCH 4/9] add keeper section, make some minor corrections in bind ports, custom packet and implmenent IBC module sections --- docs/ibc/apps/apps.md | 1 + docs/ibc/apps/bindports.md | 34 +++++++++----- docs/ibc/apps/ibcmodule.md | 42 +++++++++++++----- docs/ibc/apps/keeper.md | 83 +++++++++++++++++++++++++++++++++++ docs/ibc/apps/packets_acks.md | 4 +- docs/ibc/apps/routing.md | 2 +- 6 files changed, 140 insertions(+), 26 deletions(-) create mode 100644 docs/ibc/apps/keeper.md diff --git a/docs/ibc/apps/apps.md b/docs/ibc/apps/apps.md index 5d71e7c3f3d..0510451e8fe 100644 --- a/docs/ibc/apps/apps.md +++ b/docs/ibc/apps/apps.md @@ -18,6 +18,7 @@ The document goes into detail on the abstraction layer most relevant for applica - channel closing handshake callbacks - packet callbacks - bind to a port(s) +- add keeper methods - define your own packet data and acknowledgement structs as well as how to encode/decode them - add a route to the IBC router diff --git a/docs/ibc/apps/bindports.md b/docs/ibc/apps/bindports.md index 11f9df77857..5165d714e40 100644 --- a/docs/ibc/apps/bindports.md +++ b/docs/ibc/apps/bindports.md @@ -8,17 +8,6 @@ Learn what changes to make to bind modules to their ports on initialization. {sy Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: -1. In the `module.go` file, add: - - ```go - var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} - // Add this line - _ porttypes.IBCModule = IBCModule{} - ) - ``` - 1. Add port ID to the `GenesisState` proto definition: ```protobuf @@ -28,6 +17,25 @@ Currently, ports must be bound on app initialization. In order to bind modules t } ``` +1. Add port ID as a key to the module store: + + ```go + // x//types/keys.go + const ( + // ModuleName defines the IBC Module name + ModuleName = "moduleName" + + // Version defines the current version the IBC + // module supports + Version = "moduleVersion-1" + + // PortID is the default port id that module binds to + PortID = "portID" + + // ... + ) + ``` + 1. Add port ID to `x//types/genesis.go`: ```go @@ -53,7 +61,7 @@ Currently, ports must be bound on app initialization. In order to bind modules t } ``` -1. Bind to port(s) in `InitGenesis`: +1. Bind to port(s) in the module keeper's `InitGenesis`: ```go // InitGenesis initializes the ibc-module state and binds to PortID. @@ -95,3 +103,5 @@ Currently, ports must be bound on app initialization. In order to bind modules t ``` The module binds to the desired port(s) and returns the capabilities. + + > In the above we find reference to keeper methods that wrap other keeper functionality, in the next section the keeper methods that need to be implemented will be defined. diff --git a/docs/ibc/apps/ibcmodule.md b/docs/ibc/apps/ibcmodule.md index 1dd05c0071d..47dea8d1e70 100644 --- a/docs/ibc/apps/ibcmodule.md +++ b/docs/ibc/apps/ibcmodule.md @@ -9,6 +9,24 @@ Learn how to implement the `IBCModule` interface and all of the callbacks it req 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. The include callbacks related to channel handshake, opening and closing and packet callbacks (`OnRecvPacket`, `OnAcknowledgementPacket` and `OnTimeoutPacket`). +```go +// IBCModule implements the ICS26 interface for transfer given the transfer keeper. +type IBCModule struct { + keeper keeper.Keeper +} +``` + +Additionally, in the `module.go` file, add the following line: + +```go +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // Add this line + _ porttypes.IBCModule = IBCModule{} +) +``` + ## Channel handshake callbacks This section will describe the callbacks that are called during channel handshake execution. @@ -19,7 +37,7 @@ Here are the channel handshake callbacks that modules are expected to implement: ```go // Called by IBC Handler on MsgOpenInit -func (k Keeper) OnChanOpenInit(ctx sdk.Context, +func (im IBCModule) OnChanOpenInit(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, @@ -29,7 +47,7 @@ func (k Keeper) OnChanOpenInit(ctx sdk.Context, version string, ) error { // OpenInit must claim the channelCapability that IBC passes into the callback - if err := k.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { return err } @@ -43,7 +61,7 @@ func (k Keeper) OnChanOpenInit(ctx sdk.Context, } // Called by IBC Handler on MsgOpenTry -func (k Keeper) OnChanOpenTry( +func (im IBCModule) OnChanOpenTry( ctx sdk.Context, order channeltypes.Order, connectionHops []string, @@ -57,9 +75,9 @@ func (k Keeper) OnChanOpenTry( // (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, module does not have channel capability and we must claim it from IBC - if !k.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) { + if !im.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 := k.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + if err := im.keeper.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { return err } } @@ -82,7 +100,7 @@ func (k Keeper) OnChanOpenTry( } // Called by IBC Handler on MsgOpenAck -func (k Keeper) OnChanOpenAck( +func (im IBCModule) OnChanOpenAck( ctx sdk.Context, portID, channelID string, @@ -96,7 +114,7 @@ func (k Keeper) OnChanOpenAck( } // Called by IBC Handler on MsgOpenConfirm -func (k Keeper) OnChanOpenConfirm( +func (im IBCModule) OnChanOpenConfirm( ctx sdk.Context, portID, channelID string, @@ -113,7 +131,7 @@ The channel closing handshake will also invoke module callbacks that can return ```go // Called by IBC Handler on MsgCloseInit -func (k Keeper) OnChanCloseInit( +func (im IBCModule) OnChanCloseInit( ctx sdk.Context, portID, channelID string, @@ -126,7 +144,7 @@ func (k Keeper) OnChanCloseInit( } // Called by IBC Handler on MsgCloseConfirm -func (k Keeper) OnChanCloseConfirm( +func (im IBCModule) OnChanCloseConfirm( ctx sdk.Context, portID, channelID string, @@ -226,7 +244,7 @@ for asynchronous acknowledgements. > Note that some of the code below is _pseudo code_, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodePacketData(packet.Data)` function. ```go -func (k Keeper) OnRecvPacket( +func (im IBCModule) OnRecvPacket( ctx sdk.Context, packet channeltypes.Packet, ) ibcexported.Acknowledgement { @@ -270,7 +288,7 @@ is responsible for decoding the acknowledgement and processing it. > Note that some of the code below is _pseudo code_, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodeAcknowledgement(acknowledgments)` and `processAck(ack)` functions. ```go -func (k Keeper) OnAcknowledgementPacket( +func (im IBCModule) OnAcknowledgementPacket( ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, @@ -294,7 +312,7 @@ indeed valid, so our module only needs to implement the state machine logic for timeout is reached and the packet can no longer be received. ```go -func (k Keeper) OnTimeoutPacket( +func (im IBCModule) OnTimeoutPacket( ctx sdk.Context, packet channeltypes.Packet, ) (*sdk.Result, error) { diff --git a/docs/ibc/apps/keeper.md b/docs/ibc/apps/keeper.md new file mode 100644 index 00000000000..96f0f0ffe9b --- /dev/null +++ b/docs/ibc/apps/keeper.md @@ -0,0 +1,83 @@ + + +# Keeper + +Learn how to implement the IBC Module keeper. {synopsis} + +In the previous sections, on channel handshake callbacks and port binding in `InitGenesis`, a reference was made to keeper methods that need to be implemented when creating a custom IBC module. Below is an overview of how to define an IBC module's keeper. + +> Note that some code has been left out for clarity, to get a full code overview, please refer to [the transfer module's keeper in the ibc-go repo](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/keeper.go). + +```go +// Keeper defines the IBC fungible transfer keeper +type Keeper struct { + storeKey sdk.StoreKey + cdc codec.BinaryCodec + paramSpace paramtypes.Subspace + + channelKeeper types.ChannelKeeper + portKeeper types.PortKeeper + scopedKeeper capabilitykeeper.ScopedKeeper + + // ... additional according to custom logic +} + +// NewKeeper creates a new IBC module Keeper instance +func NewKeeper( + // args +) Keeper { + // ... + + return Keeper{ + cdc: cdc, + storeKey: key, + paramSpace: paramSpace, + + channelKeeper: channelKeeper, + portKeeper: portKeeper, + scopedKeeper: scopedKeeper, + + // ... additional according to custom logic + } +} + +// IsBound checks if the transfer module is already bound to the desired port +func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) + return ok +} + +// BindPort defines a wrapper function for the port Keeper's function in +// order to expose it to module's InitGenesis function +func (k Keeper) BindPort(ctx sdk.Context, portID string) error { + cap := k.portKeeper.BindPort(ctx, portID) + return k.ClaimCapability(ctx, cap, host.PortPath(portID)) +} + +// GetPort returns the portID for the transfer module. Used in ExportGenesis +func (k Keeper) GetPort(ctx sdk.Context) string { + store := ctx.KVStore(k.storeKey) + return string(store.Get(types.PortKey)) +} + +// SetPort sets the portID for the transfer module. Used in InitGenesis +func (k Keeper) SetPort(ctx sdk.Context, portID string) { + store := ctx.KVStore(k.storeKey) + store.Set(types.PortKey, []byte(portID)) +} + +// AuthenticateCapability wraps the scopedKeeper's AuthenticateCapability function +func (k Keeper) AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) bool { + return k.scopedKeeper.AuthenticateCapability(ctx, cap, name) +} + +// ClaimCapability allows the transfer module that can claim a capability that IBC module +// passes to it +func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error { + return k.scopedKeeper.ClaimCapability(ctx, cap, name) +} + +// ... additional according to custom logic +``` diff --git a/docs/ibc/apps/packets_acks.md b/docs/ibc/apps/packets_acks.md index 1dc9fd94fb1..6f716adae92 100644 --- a/docs/ibc/apps/packets_acks.md +++ b/docs/ibc/apps/packets_acks.md @@ -1,5 +1,5 @@ # Define packets and acks @@ -33,6 +33,8 @@ DecodePacketData(encoded []byte) (CustomPacketData) { } ``` +> Note that the `CustomPacketData` struct is defined in the proto definition and then compiled by the protobuf compiler. + Then a module must encode its packet data before sending it through IBC. ```go diff --git a/docs/ibc/apps/routing.md b/docs/ibc/apps/routing.md index 7f05aa52d06..220416f9def 100644 --- a/docs/ibc/apps/routing.md +++ b/docs/ibc/apps/routing.md @@ -1,5 +1,5 @@ # Routing From a83f8038ebab4bf1d10209af7e407d808798e87f Mon Sep 17 00:00:00 2001 From: Thomas Dekeyser Date: Fri, 8 Jul 2022 10:45:16 +0200 Subject: [PATCH 5/9] update ibcmodule interface to encorporate the simpliefied handshake callbacks and version negotiation --- docs/ibc/apps/ibcmodule.md | 100 +++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 38 deletions(-) diff --git a/docs/ibc/apps/ibcmodule.md b/docs/ibc/apps/ibcmodule.md index 47dea8d1e70..a116d238ea6 100644 --- a/docs/ibc/apps/ibcmodule.md +++ b/docs/ibc/apps/ibcmodule.md @@ -45,19 +45,30 @@ func (im IBCModule) OnChanOpenInit(ctx sdk.Context, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version string, -) error { - // OpenInit must claim the channelCapability that IBC passes into the callback - if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { - return err - } - +) (string,error) { // ... do custom initialization logic // Use above arguments to determine if we want to abort handshake // Examples: Abort if order == UNORDERED, // Abort if version is unsupported - err := checkArguments(args) - return err + if err := checkArguments(args); err != nil { + return "", err + } + + if strings.TrimSpace(version) == "" { + version = types.Version + } + + if version != types.Version { + return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version) + } + + // OpenInit must claim the channelCapability that IBC passes into the callback + if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return "", err + } + + return version, nil } // Called by IBC Handler on MsgOpenTry @@ -71,6 +82,17 @@ func (im IBCModule) OnChanOpenTry( counterparty channeltypes.Counterparty, counterpartyVersion string, ) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + if err := checkArguments(args); err != nil { + return "", err + } + + 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 @@ -78,25 +100,11 @@ func (im IBCModule) OnChanOpenTry( if !im.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 := im.keeper.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { - return err + return "", err } } - // ... do custom initialization logic - - // Use above arguments to determine if we want to abort handshake - if err := checkArguments(args); err != nil { - return err - } - - // Construct application version - // IBC applications must return the appropriate application version - // This can be a simple string or it can be a complex version constructed - // from the counterpartyVersion and other arguments. - // The version returned will be the channel version used for both channel ends. - appVersion := negotiateAppVersion(counterpartyVersion, args) - - return appVersion, nil + return types.Version, nil } // Called by IBC Handler on MsgOpenAck @@ -106,11 +114,13 @@ func (im IBCModule) OnChanOpenAck( channelID string, counterpartyVersion string, ) error { - // ... do custom initialization logic + if counterpartyVersion != types.Version { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: %s, expected %s", counterpartyVersion, types.Version) + } - // Use above arguments to determine if we want to abort handshake - err := checkArguments(args) - return err + // do custom logic + + return nil } // Called by IBC Handler on MsgOpenConfirm @@ -119,11 +129,9 @@ func (im IBCModule) OnChanOpenConfirm( portID, channelID string, ) error { - // ... do custom initialization logic + // do custom logic - // Use above arguments to determine if we want to abort handshake - err := checkArguments(args) - return err + return nil } ``` @@ -161,12 +169,28 @@ func (im IBCModule) OnChanCloseConfirm( Application modules are expected to verify versioning used during the channel handshake procedure. -- `ChanOpenInit` callback should verify that the `MsgChanOpenInit.Version` is valid -- `ChanOpenTry` callback should construct the application version used for both channel ends. If no application version can be constructed, it must return an error. -- `ChanOpenAck` callback should verify that the `MsgChanOpenAck.CounterpartyVersion` is valid and supported. - -IBC expects application modules to perform application version negotiation in `OnChanOpenTry`. The negotiated version -must be returned to core IBC. If the version cannot be negotiated, an error should be returned. +- `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 provided version is 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 modules + 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 to abort the handshake. It may also perform custom ACK logic. Versions must be strings but can implement any versioning structure. If your application plans to have linear releases then semantic versioning is recommended. If your application plans to release From 1651fdb03fc85ef5daf270f4a632928020e8c0fd Mon Sep 17 00:00:00 2001 From: Thomas Dekeyser Date: Fri, 8 Jul 2022 11:14:44 +0200 Subject: [PATCH 6/9] fix broken links --- docs/ibc/apps/apps.md | 6 +++--- docs/ibc/apps/bindports.md | 5 +++++ docs/ibc/apps/ibcmodule.md | 5 +++++ docs/ibc/apps/keeper.md | 5 +++++ docs/ibc/apps/packets_acks.md | 5 +++++ docs/ibc/apps/routing.md | 5 +++++ docs/ibc/middleware/develop.md | 12 ++++++------ 7 files changed, 34 insertions(+), 9 deletions(-) diff --git a/docs/ibc/apps/apps.md b/docs/ibc/apps/apps.md index 0510451e8fe..13b45612f97 100644 --- a/docs/ibc/apps/apps.md +++ b/docs/ibc/apps/apps.md @@ -8,7 +8,7 @@ Learn how to build custom IBC application modules that enable packets to be sent This document serves as a guide for developers who want to write their own Inter-blockchain Communication Protocol (IBC) applications for custom use cases. -Due to the modular design of the IBC protocol, IBC application developers do not need to concern themselves with the low-level details of clients, connections, and proof verification. Nevertheless, an overview of these low-level concepts can be found in [a previous section](./overview.md). +Due to the modular design of the IBC protocol, IBC application developers do not need to concern themselves with the low-level details of clients, connections, and proof verification. Nevertheless, an overview of these low-level concepts can be found in [a previous section](../overview.md). The document goes into detail on the abstraction layer most relevant for application developers (channels and ports), and describes how to define your own custom packets, `IBCModule` callbacks and more to make an application module IBC ready. **To have your module interact over IBC you must:** @@ -27,8 +27,8 @@ module correctly corresponding to the listed steps. ## Pre-requisites Readings -- [IBC Overview](./overview.md)) {prereq} -- [IBC default integration](./integration.md) {prereq} +- [IBC Overview](../overview.md)) {prereq} +- [IBC default integration](../integration.md) {prereq} ## Working example diff --git a/docs/ibc/apps/bindports.md b/docs/ibc/apps/bindports.md index 5165d714e40..73bb42d464c 100644 --- a/docs/ibc/apps/bindports.md +++ b/docs/ibc/apps/bindports.md @@ -6,6 +6,11 @@ order: 3 Learn what changes to make to bind modules to their ports on initialization. {synopsis} +## Pre-requisites Readings + +- [IBC Overview](../overview.md)) {prereq} +- [IBC default integration](../integration.md) {prereq} + Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: 1. Add port ID to the `GenesisState` proto definition: diff --git a/docs/ibc/apps/ibcmodule.md b/docs/ibc/apps/ibcmodule.md index a116d238ea6..15b74b4b641 100644 --- a/docs/ibc/apps/ibcmodule.md +++ b/docs/ibc/apps/ibcmodule.md @@ -27,6 +27,11 @@ var ( ) ``` +## Pre-requisites Readings + +- [IBC Overview](../overview.md)) {prereq} +- [IBC default integration](../integration.md) {prereq} + ## Channel handshake callbacks This section will describe the callbacks that are called during channel handshake execution. diff --git a/docs/ibc/apps/keeper.md b/docs/ibc/apps/keeper.md index 96f0f0ffe9b..deb2358e559 100644 --- a/docs/ibc/apps/keeper.md +++ b/docs/ibc/apps/keeper.md @@ -6,6 +6,11 @@ order: 4 Learn how to implement the IBC Module keeper. {synopsis} +## Pre-requisites Readings + +- [IBC Overview](../overview.md)) {prereq} +- [IBC default integration](../integration.md) {prereq} + In the previous sections, on channel handshake callbacks and port binding in `InitGenesis`, a reference was made to keeper methods that need to be implemented when creating a custom IBC module. Below is an overview of how to define an IBC module's keeper. > Note that some code has been left out for clarity, to get a full code overview, please refer to [the transfer module's keeper in the ibc-go repo](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/keeper.go). diff --git a/docs/ibc/apps/packets_acks.md b/docs/ibc/apps/packets_acks.md index 6f716adae92..ff67dba4c03 100644 --- a/docs/ibc/apps/packets_acks.md +++ b/docs/ibc/apps/packets_acks.md @@ -6,6 +6,11 @@ order: 5 Learn how to define custom packet and acknowledgement structs and how to encode and decode them. {synopsis} +## Pre-requisites Readings + +- [IBC Overview](../overview.md)) {prereq} +- [IBC default integration](../integration.md) {prereq} + ## Custom packets Modules connected by a channel must agree on what application data they are sending over the diff --git a/docs/ibc/apps/routing.md b/docs/ibc/apps/routing.md index 220416f9def..1095462dcba 100644 --- a/docs/ibc/apps/routing.md +++ b/docs/ibc/apps/routing.md @@ -4,6 +4,11 @@ order: 6 # Routing +## Pre-requisites Readings + +- [IBC Overview](../overview.md)) {prereq} +- [IBC default integration](../integration.md) {prereq} + Learn how to hook a route to the IBC router for the custom IBC module. {synopsis} As mentioned above, modules must implement the `IBCModule` interface (which contains both channel diff --git a/docs/ibc/middleware/develop.md b/docs/ibc/middleware/develop.md index 705040b1db7..70b01e7dff6 100644 --- a/docs/ibc/middleware/develop.md +++ b/docs/ibc/middleware/develop.md @@ -16,7 +16,7 @@ Middleware allows developers to define the extensions as separate modules that c - [IBC Overview](../overview.md) {prereq} - [IBC Integration](../integration.md) {prereq} -- [IBC Application Developer Guide](../apps.md) {prereq} +- [IBC Application Developer Guide](../apps/apps.md) {prereq} ## Definitions @@ -26,7 +26,7 @@ Middleware allows developers to define the extensions as separate modules that c `Base Application`: A base application is an IBC application that does not contain any middleware. It may be nested by 0 or multiple middleware to form an application stack. -`Application Stack (or stack)`: A stack is the complete set of application logic (middleware(s) + base application) that gets connected to core IBC. A stack may be just a base application, or it may be a series of middlewares that nest a base application. +`Application Stack (or stack)`: A stack is the complete set of application logic (middleware(s) + base application) that gets connected to core IBC. A stack may be just a base application, or it may be a series of middlewares that nest a base application. ## Create a custom IBC Middleware @@ -49,7 +49,7 @@ type Middleware interface { type ICS4Wrapper interface { SendPacket(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.Packet) error WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.Packet, ack []byte) error - GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) + GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) } ``` @@ -125,7 +125,7 @@ func OnChanOpenTry( if err != nil { return err } - + middlewareVersion := negotiateMiddlewareVersion(cpMiddlewareVersion) version := constructVersion(middlewareVersion, appVersion) @@ -144,7 +144,7 @@ func OnChanOpenAck( return error } doCustomLogic() - + // call the underlying applications OnChanOpenTry callback app.OnChanOpenAck(ctx, portID, channelID, appVersion) } @@ -241,7 +241,7 @@ func SendPacket(appPacket channeltypes.Packet) { return ics4Keeper.SendPacket(packet) } -// middleware must return the underlying application version +// middleware must return the underlying application version func GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { version, found := ics4Keeper.GetAppVersion(ctx, portID, channelID) if !found { From e610af4ea9fb5751361d9402aa913e2b0563a3aa Mon Sep 17 00:00:00 2001 From: Thomas Dekeyser Date: Fri, 8 Jul 2022 11:26:10 +0200 Subject: [PATCH 7/9] fix remaining broken link --- docs/ibc/integration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ibc/integration.md b/docs/ibc/integration.md index 09c1d2d2de9..d2d9f057ea3 100644 --- a/docs/ibc/integration.md +++ b/docs/ibc/integration.md @@ -24,7 +24,7 @@ Integrating the IBC module to your SDK-based application is straighforward. The ### Module `BasicManager` and `ModuleAccount` permissions -The first step is to add the following modules to the `BasicManager`: `x/capability`, `x/ibc`, +The first step is to add the following modules to the `BasicManager`: `x/capability`, `x/ibc`, and `x/ibc-transfer`. After that, we need to grant `Minter` and `Burner` permissions to the `ibc-transfer` `ModuleAccount` to mint and burn relayed tokens. @@ -72,7 +72,7 @@ type App struct { ### Configure the `Keepers` -During initialization, besides initializing the IBC `Keepers` (for the `x/ibc`, and +During initialization, besides initializing the IBC `Keepers` (for the `x/ibc`, and `x/ibc-transfer` modules), we need to grant specific capabilities through the capability module `ScopedKeepers` so that we can authenticate the object-capability permissions for each of the IBC channels. @@ -221,4 +221,4 @@ different chains. If you want to have a broader view of the changes take a look ## Next {hide} -Learn about how to create [custom IBC modules](./apps.md) for your application {hide} +Learn about how to create [custom IBC modules](./apps/apps.md) for your application {hide} From 4bff6af25b1936baec3f1a1dad33ba762fd37d0f Mon Sep 17 00:00:00 2001 From: Thomas Dekeyser Date: Tue, 19 Jul 2022 13:02:33 +0200 Subject: [PATCH 8/9] fix some nits, correct for removal of crossing hellos and add some more explanation on portIDs --- docs/ibc/apps/apps.md | 2 +- docs/ibc/apps/bindports.md | 4 +- docs/ibc/apps/ibcmodule.md | 82 ++++++++++++++++------------------- docs/ibc/apps/keeper.md | 12 ++--- docs/ibc/apps/packets_acks.md | 2 +- 5 files changed, 48 insertions(+), 54 deletions(-) diff --git a/docs/ibc/apps/apps.md b/docs/ibc/apps/apps.md index 13b45612f97..a24032168aa 100644 --- a/docs/ibc/apps/apps.md +++ b/docs/ibc/apps/apps.md @@ -8,7 +8,7 @@ Learn how to build custom IBC application modules that enable packets to be sent This document serves as a guide for developers who want to write their own Inter-blockchain Communication Protocol (IBC) applications for custom use cases. -Due to the modular design of the IBC protocol, IBC application developers do not need to concern themselves with the low-level details of clients, connections, and proof verification. Nevertheless, an overview of these low-level concepts can be found in [a previous section](../overview.md). +Due to the modular design of the IBC protocol, IBC application developers do not need to concern themselves with the low-level details of clients, connections, and proof verification. Nevertheless, an overview of these low-level concepts can be found in [the Overview section](../overview.md). The document goes into detail on the abstraction layer most relevant for application developers (channels and ports), and describes how to define your own custom packets, `IBCModule` callbacks and more to make an application module IBC ready. **To have your module interact over IBC you must:** diff --git a/docs/ibc/apps/bindports.md b/docs/ibc/apps/bindports.md index 73bb42d464c..c0cfa703191 100644 --- a/docs/ibc/apps/bindports.md +++ b/docs/ibc/apps/bindports.md @@ -13,6 +13,8 @@ Learn what changes to make to bind modules to their ports on initialization. {sy Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: +> Note that `portID` does not refer to a certain numerical ID, like `localhost:8080` with a `portID` 8080. Rather it refers to the application module the port binds. For IBC Modules built with the Cosmos SDK, it defaults to the module's name and for Cosmwasm contracts it defaults to the contract address. + 1. Add port ID to the `GenesisState` proto definition: ```protobuf @@ -109,4 +111,4 @@ Currently, ports must be bound on app initialization. In order to bind modules t The module binds to the desired port(s) and returns the capabilities. - > In the above we find reference to keeper methods that wrap other keeper functionality, in the next section the keeper methods that need to be implemented will be defined. + In the above we find reference to keeper methods that wrap other keeper functionality, in the next section the keeper methods that need to be implemented will be defined. diff --git a/docs/ibc/apps/ibcmodule.md b/docs/ibc/apps/ibcmodule.md index 15b74b4b641..d5864435700 100644 --- a/docs/ibc/apps/ibcmodule.md +++ b/docs/ibc/apps/ibcmodule.md @@ -7,10 +7,12 @@ order: 2 Learn how to implement the `IBCModule` interface and all of the callbacks it requires. {synopsis} 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. The include callbacks related to channel handshake, opening and closing and packet callbacks (`OnRecvPacket`, `OnAcknowledgementPacket` and `OnTimeoutPacket`). +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. They include callbacks related to channel handshake, closing and packet callbacks (`OnRecvPacket`, `OnAcknowledgementPacket` and `OnTimeoutPacket`). ```go -// IBCModule implements the ICS26 interface for transfer given the transfer keeper. +// IBCModule implements the ICS26 interface for given the keeper. +// The implementation of the IBCModule interface could for example be in a file called ibc_module.go, +// but ultimately file structure is up to the developer type IBCModule struct { keeper keeper.Keeper } @@ -34,11 +36,11 @@ var ( ## Channel handshake callbacks -This section will describe the callbacks that are called during channel handshake execution. +This section will describe the callbacks that are called during channel handshake execution. Among other things, it will claim channel capabilities passed on from core IBC. For a refresher on capabilities, check [the Overview section](../overview.md#capabilities). Here are the channel handshake callbacks that modules are expected to implement: -> Note that some of the code below is _pseudo code_, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `checkArguments(args)` function. +> Note that some of the code below is _pseudo code_, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `checkArguments` and `negotiateAppVersion` functions. ```go // Called by IBC Handler on MsgOpenInit @@ -50,24 +52,17 @@ func (im IBCModule) OnChanOpenInit(ctx sdk.Context, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version string, -) (string,error) { +) (string, error) { // ... do custom initialization logic // Use above arguments to determine if we want to abort handshake - // Examples: Abort if order == UNORDERED, - // Abort if version is unsupported + // Examples: + // - Abort if order == UNORDERED, + // - Abort if version is unsupported if err := checkArguments(args); err != nil { return "", err } - if strings.TrimSpace(version) == "" { - version = types.Version - } - - if version != types.Version { - return "", sdkerrors.Wrapf(types.ErrInvalidVersion, "got %s, expected %s", version, types.Version) - } - // OpenInit must claim the channelCapability that IBC passes into the callback if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { return "", err @@ -94,22 +89,19 @@ func (im IBCModule) OnChanOpenTry( return "", err } - 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, module does not have channel capability and we must claim it from IBC - if !im.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 := im.keeper.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { - return "", err - } + // OpenTry must claim the channelCapability that IBC passes into the callback + if err := im.keeper.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return err } - return types.Version, nil + // Construct application version + // IBC applications must return the appropriate application version + // This can be a simple string or it can be a complex version constructed + // from the counterpartyVersion and other arguments. + // The version returned will be the channel version used for both channel ends. + appVersion := negotiateAppVersion(counterpartyVersion, args) + + return appVersion, nil } // Called by IBC Handler on MsgOpenAck @@ -174,36 +166,36 @@ func (im IBCModule) OnChanCloseConfirm( Application modules are expected to verify versioning used during the channel handshake procedure. -- `onChanOpenInit` will verify that the relayer-chosen parameters +- `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 + 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 + **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 provided version is empty string. -- `onChanOpenTry` will verify the relayer-chosen parameters along with the + 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 modules + 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 to abort the handshake. It may also perform custom ACK logic. + `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. If your application plans to have linear releases then semantic versioning is recommended. If your application plans to release various features in between major releases then it is advised to use the same versioning scheme as IBC. This versioning scheme specifies a version identifier and compatible feature set with that identifier. Valid version selection includes selecting a compatible version identifier with -a subset of features supported by your application for that version. The struct is used for this -scheme can be found in `03-connection/types`. +a subset of features supported by your application for that version. The struct used for this +scheme can be found in [03-connection/types](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection/types/version.go#L16). Since the version type is a string, applications have the ability to do simple version verification via string matching or they can use the already impelemented versioning system and pass the proto @@ -213,7 +205,7 @@ ICS20 currently implements basic string matching with a single supported version ## Packet callbacks -Just as IBC expected modules to implement callbacks for channel handshakes, IBC also expects modules to implement callbacks for handling the packet flow through a channel, as defined in the `IBCModule` interface. +Just as IBC expects modules to implement callbacks for channel handshakes, it also expects modules to implement callbacks for handling the packet flow through a channel, as defined in the `IBCModule` interface. Once a module A and module B are connected to each other, relayers can start relaying packets and acknowledgements back and forth on the channel. @@ -230,7 +222,7 @@ Briefly, a successful packet flow works as follows: ### Sending packets -Modules **do not send packets through callbacks**, since the modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where msgs sent to the IBC +Modules **do not send packets through callbacks**, since the 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`. > Note that some of the code below is _pseudo code_, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `EncodePacketData(customPacketData)` function. @@ -257,7 +249,7 @@ invoked by the IBC module after the packet has been proved valid and correctly p 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 acknowledgement which implements the Acknowledgement interface. +Modules may return to the IBC handler an acknowledgement which implements the `Acknowledgement` interface. The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the acknowledgement back to the sender module. @@ -291,7 +283,7 @@ func (im IBCModule) OnRecvPacket( } ``` -Reminder, the Acknowledgement interface: +Reminder, the `Acknowledgement` interface: ```go // Acknowledgement defines the interface used to return @@ -306,7 +298,7 @@ type Acknowledgement interface { After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the -acknowledgement is entirely upto the modules on the channel (just like the packet data); however, it +acknowledgement is entirely up to the modules on the channel (just like the packet data); however, it may often contain information on whether the packet was successfully processed along with some additional data that could be useful for remediation if the packet processing failed. diff --git a/docs/ibc/apps/keeper.md b/docs/ibc/apps/keeper.md index deb2358e559..6cbba0fbb8f 100644 --- a/docs/ibc/apps/keeper.md +++ b/docs/ibc/apps/keeper.md @@ -16,7 +16,7 @@ In the previous sections, on channel handshake callbacks and port binding in `In > Note that some code has been left out for clarity, to get a full code overview, please refer to [the transfer module's keeper in the ibc-go repo](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/keeper.go). ```go -// Keeper defines the IBC fungible transfer keeper +// Keeper defines the IBC app module keeper type Keeper struct { storeKey sdk.StoreKey cdc codec.BinaryCodec @@ -29,7 +29,7 @@ type Keeper struct { // ... additional according to custom logic } -// NewKeeper creates a new IBC module Keeper instance +// NewKeeper creates a new IBC app module Keeper instance func NewKeeper( // args ) Keeper { @@ -48,7 +48,7 @@ func NewKeeper( } } -// IsBound checks if the transfer module is already bound to the desired port +// IsBound checks if the IBC app module is already bound to the desired port func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) return ok @@ -61,13 +61,13 @@ func (k Keeper) BindPort(ctx sdk.Context, portID string) error { return k.ClaimCapability(ctx, cap, host.PortPath(portID)) } -// GetPort returns the portID for the transfer module. Used in ExportGenesis +// GetPort returns the portID for the IBC app module. Used in ExportGenesis func (k Keeper) GetPort(ctx sdk.Context) string { store := ctx.KVStore(k.storeKey) return string(store.Get(types.PortKey)) } -// SetPort sets the portID for the transfer module. Used in InitGenesis +// SetPort sets the portID for the IBC app module. Used in InitGenesis func (k Keeper) SetPort(ctx sdk.Context, portID string) { store := ctx.KVStore(k.storeKey) store.Set(types.PortKey, []byte(portID)) @@ -78,7 +78,7 @@ func (k Keeper) AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Cap return k.scopedKeeper.AuthenticateCapability(ctx, cap, name) } -// ClaimCapability allows the transfer module that can claim a capability that IBC module +// ClaimCapability allows the IBC app module to claim a capability that core IBC // passes to it func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error { return k.scopedKeeper.ClaimCapability(ctx, cap, name) diff --git a/docs/ibc/apps/packets_acks.md b/docs/ibc/apps/packets_acks.md index ff67dba4c03..1871eca8915 100644 --- a/docs/ibc/apps/packets_acks.md +++ b/docs/ibc/apps/packets_acks.md @@ -20,7 +20,7 @@ applications this will happen as a version negotiation during the channel handsh complex version negotiation is possible to implement inside the channel opening handshake, a very simple version negotation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). -Thus, a module must define its a custom packet data structure, along with a well-defined way to +Thus, a module must define its custom packet data structure, along with a well-defined way to encode and decode it to and from `[]byte`. ```go From 8fbdd0d8bc719ec8b0b1ce4e69b3323aefde1af3 Mon Sep 17 00:00:00 2001 From: Thomas Dekeyser Date: Tue, 19 Jul 2022 13:33:47 +0200 Subject: [PATCH 9/9] update middleware docs to resolve merge confilicts --- docs/ibc/middleware/develop.md | 35 +++++++++------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/docs/ibc/middleware/develop.md b/docs/ibc/middleware/develop.md index 2fa8076e9f5..157115b55e5 100644 --- a/docs/ibc/middleware/develop.md +++ b/docs/ibc/middleware/develop.md @@ -48,11 +48,7 @@ type Middleware interface { // which will call the next middleware until it reaches the core IBC handler. type ICS4Wrapper interface { SendPacket(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.Packet) error -<<<<<<< HEAD - WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.Packet, ack []byte) error -======= WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.Packet, ack exported.Acknowledgement) error ->>>>>>> new-changes GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) } ``` @@ -68,7 +64,10 @@ In the case where the IBC middleware expects to speak to a compatible IBC middle Middleware accomplishes this by formatting the version in a JSON-encoded string containing the middleware version and the application version. The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application. The format of the version is specified in ICS-30 as the following: ```json -{"":"","app_version":""} +{ + "": "", + "app_version": "" +} ``` The `` key in the JSON struct should be replaced by the actual name of the key for the corresponding middleware (e.g. `fee_version`). @@ -94,12 +93,12 @@ func (im IBCModule) OnChanOpenInit( counterparty channeltypes.Counterparty, version string, ) (string, error) { - // try to unmarshal JSON-encoded version string and pass + // try to unmarshal JSON-encoded version string and pass // the app-specific version to app callback. // otherwise, pass version directly to app callback. metadata, err := Unmarshal(version) if err != nil { - // Since it is valid for fee version to not be specified, + // Since it is valid for fee version to not be specified, // the above middleware version may be for another middleware. // Pass the entire version string onto the underlying application. return im.app.OnChanOpenInit( @@ -155,7 +154,7 @@ func OnChanOpenTry( ) (string, error) { doCustomLogic() - // try to unmarshal JSON-encoded version string and pass + // try to unmarshal JSON-encoded version string and pass // the app-specific version to app callback. // otherwise, pass version directly to app callback. cpMetadata, err := Unmarshal(counterpartyVersion) @@ -187,14 +186,9 @@ func OnChanOpenTry( if err != nil { return "", err } -<<<<<<< HEAD - middlewareVersion := negotiateMiddlewareVersion(cpMiddlewareVersion) -======= - // negotiate final middleware version middlewareVersion := negotiateMiddlewareVersion(cpMetadata.MiddlewareVersion) ->>>>>>> new-changes version := constructVersion(middlewareVersion, appVersion) return version, nil @@ -213,7 +207,7 @@ func OnChanOpenAck( counterpartyChannelID string, counterpartyVersion string, ) error { - // try to unmarshal JSON-encoded version string and pass + // try to unmarshal JSON-encoded version string and pass // the app-specific version to app callback. // otherwise, pass version directly to app callback. cpMetadata, err = UnmarshalJSON(counterpartyVersion) @@ -225,15 +219,9 @@ func OnChanOpenAck( return error } doCustomLogic() -<<<<<<< HEAD - // call the underlying applications OnChanOpenTry callback - app.OnChanOpenAck(ctx, portID, channelID, appVersion) -======= - // call the underlying application's OnChanOpenTry callback return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, cpMetadata.AppVersion) ->>>>>>> new-changes } ``` @@ -387,18 +375,13 @@ See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f5 #### `GetAppVersion` -<<<<<<< HEAD -// middleware must return the underlying application version -func GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { -======= ```go -// middleware must return the underlying application version +// middleware must return the underlying application version func GetAppVersion( ctx sdk.Context, portID, channelID string, ) (string, bool) { ->>>>>>> new-changes version, found := ics4Keeper.GetAppVersion(ctx, portID, channelID) if !found { return "", false