From b2f3e2645f7b3c6a230152b12d4380e13aec596a Mon Sep 17 00:00:00 2001 From: Susannah Evans <65018876+womensrights@users.noreply.github.com> Date: Tue, 18 Mar 2025 23:15:16 +0100 Subject: [PATCH 1/4] update ibcv2 middleware docs --- docs/docs/01-ibc/04-middleware/02-develop.md | 2 +- .../01-ibc/04-middleware/02-developIBCv2.md | 185 ++++++++++++++++++ .../01-ibc/04-middleware/03-integration.md | 18 +- 3 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 docs/docs/01-ibc/04-middleware/02-developIBCv2.md diff --git a/docs/docs/01-ibc/04-middleware/02-develop.md b/docs/docs/01-ibc/04-middleware/02-develop.md index 2f5742ede31..541c27a77cb 100644 --- a/docs/docs/01-ibc/04-middleware/02-develop.md +++ b/docs/docs/01-ibc/04-middleware/02-develop.md @@ -1,7 +1,7 @@ --- title: Create a custom IBC middleware sidebar_label: Create a custom IBC middleware -sidebar_position: 2 +sidebar_position: 3 slug: /ibc/middleware/develop --- diff --git a/docs/docs/01-ibc/04-middleware/02-developIBCv2.md b/docs/docs/01-ibc/04-middleware/02-developIBCv2.md new file mode 100644 index 00000000000..ad51d54d881 --- /dev/null +++ b/docs/docs/01-ibc/04-middleware/02-developIBCv2.md @@ -0,0 +1,185 @@ +--- +title: Create and integrate IBC v2 middleware +sidebar_label: Create and integrate IBC v2 middleware +sidebar_position: 2 +slug: /ibc/middleware/developIBCv2 +--- + + +# Create a custom IBC v2 middleware + +IBC middleware will wrap over an underlying IBC application (a base application or downstream middleware) and sits between core IBC and the base application. + +The interfaces a middleware must implement are found in [core/api](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/module.go#L11). Note that this interface has chanhged from IBC classic. + +An `IBCMiddleware` struct implementing the `Middleware` interface, can be defined with its constructor as follows: + +```go +// @ x/module_name/ibc_middleware.go + +// IBCMiddleware implements the IBCv2 middleware interface +type IBCMiddleware struct { + app api.IBCModule // underlying app or middleware + writeAckWrapper api. WriteAcknowledgementWrapper // writes acknowledgement for an async acknowledgement + PacketDataUnmarshaler api.PacketDataUnmarshaler // optional interface + keeper types.Keeper // required for stateful middleware + // Keeper may include middleware specific keeper and the ChannelKeeperV2 + + // additional middleware specific fields +} + +// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application +func NewIBCMiddleware(app api.IBCModule, +writeAckWrapper api.WriteAcknowledgementWrapper, +k types.Keeper +) IBCMiddleware { + return IBCMiddleware{ + app: app, + writeAckWrapper: writeAckWrapper, + keeper: k, + } +} +``` + +:::note +The ICS4Wrapper has been removed in IBC v2 and there are no channel handshake callbacks, a writeAckWrapper has been added to the interface +::: + +## Implement `IBCModule` interface + +`IBCMiddleware` is a struct that implements the [`IBCModule` interface (`api.IBCModule`)](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/module.go#L11-L53). It is recommended to separate these callbacks into a separate file `ibc_middleware.go`. + +> Note how this is analogous to implementing the same interfaces for IBC applications that act as base applications. + +The middleware must have access to the underlying application, and be called before it during all ICS-26 callbacks. It may execute custom logic during these callbacks, and then call the underlying application's callback. + +> Middleware **may** choose not to call the underlying application's callback at all. Though these should generally be limited to error cases. + +The `IBCModule` interface consists of the packet callbacks where cutom logic is performed. + +### Packet callbacks + +The packet callbacks are where the middleware performs most of its custom logic. The middleware may read the packet flow data and perform some additional packet handling, or it may modify the incoming data before it reaches the underlying application. This enables a wide degree of usecases, as a simple base application like token-transfer can be transformed for a variety of usecases by combining it with custom middleware, for example acting as a filter for which tokens can be sent and recieved. + +#### `OnRecvPacket` + +```go +func (im IBCMiddleware) OnRecvPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, +) channeltypesv2.RecvPacketResult { + recvResult := im.app.OnRecvPacket(ctx, sourceClient, destinationClient, sequence, payload, relayer) + + doCustomLogic(recvResult) // middleware may modify success acknowledgment + + return recvResult +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L161-L230) an example implementation of this callback for the Callbacks Middleware module. + +#### `OnAcknowledgementPacket` + +```go +func (im IBCMiddleware) OnAcknowledgementPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + acknowledgement []byte, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, +) error { + + doCustomLogic(payload, acknowledgement) + + return nil +} +``` + +See [here](hhttps://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L236-L302) an example implementation of this callback for the Callbacks Middleware module. + +#### `OnTimeoutPacket` + +```go +func (im IBCMiddleware) OnTimeoutPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, +) error { + doCustomLogic(payload) + + return nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L309-L367) an example implementation of this callback for the Callbacks Middleware module. + +### WriteAckWrapper + +Middleware must also wrap the `WriteAcknowledgement` interface so that any acknowledgement written by the application passes through the middleware first. This allows middleware to modify or delay writing an acknowledgment before committed to the IBC store. + +```go +// WithWriteAckWrapper sets the WriteAcknowledgementWrapper for the middleware. +func (im *IBCMiddleware) WithWriteAckWrapper(writeAckWrapper api.WriteAcknowledgementWrapper) { + im.writeAckWrapper = writeAckWrapper +} + +// GetWriteAckWrapper returns the WriteAckWrapper +func (im *IBCMiddleware) GetWriteAckWrapper() api.WriteAcknowledgementWrapper { + return im.writeAckWrapper +} +``` + +### `WriteAcknowledgement` + +This is where the middleware acknowledgement handling is finalised. An example is shown in the [callbacks middleware](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L369-L454) + +```go +func (im IBCMiddleware) WriteAcknowledgement( + ctx sdk.Context, + clientID string, + sequence uint64, + ack channeltypesv2.Acknowledgement, +) error { + // packet and payload handling and validation + + // custom middleware logic, for example callbacks. + +return nil +} +``` + +## Integrate IBC v2 Middleware + +Middleware should be registered within the module manager in `app.go`. + +The order of middleware **matters**, function calls from IBC to the application travel from top-level middleware to the bottom middleware and then to the application. Function calls from the application to IBC goes through the bottom middleware in order to the top middleware and then to core IBC handlers. Thus the same set of middleware put in different orders may produce different effects. + +### Example Integration + +The example integration is detailed for an IBC v2 stack using transfer and the callbacks middleware. + +```go +// Middleware Stacks +// initialising callbacks middleware + maxCallbackGas := uint64(10_000_000) + wasmStackIBCHandler := wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper) + +// Create the transferv2 stack with transfer and callbacks middleware + var ibcv2TransferStack ibcapi.IBCModule + ibcv2TransferStack = transferv2.NewIBCModule(app.TransferKeeper) + ibcv2TransferStack = ibccallbacksv2.NewIBCMiddleware(transferv2.NewIBCModule(app.TransferKeeper), app.IBCKeeper.ChannelKeeperV2, wasmStackIBCHandler, app.IBCKeeper.ChannelKeeperV2, maxCallbackGas) + +// Create static IBC v2 router, add app routes, then set and seal it + ibcRouterV2 := ibcapi.NewRouter() + ibcRouterV2.AddRoute(ibctransfertypes.PortID, ibcv2TransferStack) + app.IBCKeeper.SetRouterV2(ibcRouterV2) +``` diff --git a/docs/docs/01-ibc/04-middleware/03-integration.md b/docs/docs/01-ibc/04-middleware/03-integration.md index f049555e544..3d1ed88b83a 100644 --- a/docs/docs/01-ibc/04-middleware/03-integration.md +++ b/docs/docs/01-ibc/04-middleware/03-integration.md @@ -1,7 +1,7 @@ --- title: Integrating IBC middleware into a chain sidebar_label: Integrating IBC middleware into a chain -sidebar_position: 3 +sidebar_position: 4 slug: /ibc/middleware/integration --- @@ -21,6 +21,8 @@ The order of middleware **matters**, function calls from IBC to the application ```go // app.go pseudocode + + // middleware 1 and middleware 3 are stateful middleware, // perhaps implementing separate sdk.Msg and Handlers mw1Keeper := mw1.NewKeeper(storeKey1, ..., ics4Wrapper: channelKeeper, ...) // in stack 1 & 3 @@ -39,19 +41,15 @@ app.moduleManager = module.NewManager( custom.NewAppModule(customKeeper) ) -scopedKeeperTransfer := capabilityKeeper.NewScopedKeeper("transfer") -scopedKeeperCustom1 := capabilityKeeper.NewScopedKeeper("custom1") -scopedKeeperCustom2 := capabilityKeeper.NewScopedKeeper("custom2") - // NOTE: IBC Modules may be initialized any number of times provided they use a separate -// scopedKeeper and underlying port. +// Keeper and underlying port. -customKeeper1 := custom.NewKeeper(..., scopedKeeperCustom1, ...) -customKeeper2 := custom.NewKeeper(..., scopedKeeperCustom2, ...) +customKeeper1 := custom.NewKeeper(..., KeeperCustom1, ...) +customKeeper2 := custom.NewKeeper(..., KeeperCustom2, ...) // initialize base IBC applications // if you want to create two different stacks with the same base application, -// they must be given different scopedKeepers and assigned different ports. +// they must be given different Keepers and assigned different ports. transferIBCModule := transfer.NewIBCModule(transferKeeper) customIBCModule1 := custom.NewIBCModule(customKeeper1, "portCustom1") customIBCModule2 := custom.NewIBCModule(customKeeper2, "portCustom2") @@ -65,7 +63,7 @@ stack2 := mw3.NewIBCMiddleware(mw2.NewIBCMiddleware(customIBCModule1), mw3Keeper // stack 3 contains mw2 -> mw1 -> custom2 stack3 := mw2.NewIBCMiddleware(mw1.NewIBCMiddleware(customIBCModule2, mw1Keeper)) -// associate each stack with the moduleName provided by the underlying scopedKeeper +// associate each stack with the moduleName provided by the underlying Keeper ibcRouter := porttypes.NewRouter() ibcRouter.AddRoute("transfer", stack1) ibcRouter.AddRoute("custom1", stack2) From 63f7ea510c55d7078a3305fa274bf747d97a2fe5 Mon Sep 17 00:00:00 2001 From: Susannah Evans <65018876+womensrights@users.noreply.github.com> Date: Wed, 19 Mar 2025 17:25:24 +0100 Subject: [PATCH 2/4] fix dead links --- docs/docs/01-ibc/03-apps/00-ibcv2apps.md | 2 +- docs/docs/01-ibc/03-apps/01-apps.md | 2 +- docs/docs/01-ibc/07-relayer.md | 2 +- docs/docs/03-light-clients/01-developer-guide/08-proposals.md | 2 +- docs/docs/05-migrations/02-sdk-to-v1.md | 2 +- docs/versioned_docs/version-v4.6.x/01-ibc/07-relayer.md | 2 +- docs/versioned_docs/version-v5.4.x/01-ibc/07-relayer.md | 2 +- docs/versioned_docs/version-v6.3.x/01-ibc/07-relayer.md | 2 +- docs/versioned_docs/version-v7.8.x/01-ibc/07-relayer.md | 2 +- docs/versioned_docs/version-v8.5.x/01-ibc/08-relayer.md | 2 +- docs/versioned_docs/version-v9.0.x/01-ibc/08-relayer.md | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/docs/01-ibc/03-apps/00-ibcv2apps.md b/docs/docs/01-ibc/03-apps/00-ibcv2apps.md index 51f1b639118..73d251940f4 100644 --- a/docs/docs/01-ibc/03-apps/00-ibcv2apps.md +++ b/docs/docs/01-ibc/03-apps/00-ibcv2apps.md @@ -311,6 +311,6 @@ It is also possible to define your own custom success acknowledgement which will ## Routing -More information on implementing the IBC Router can be found in the [routing section](/docs/docs/01-ibc/03-apps/06-routing.md). +More information on implementing the IBC Router can be found in the [routing section](../03-apps/06-routing.md). [porttypes.IBCModule]: https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/module.go diff --git a/docs/docs/01-ibc/03-apps/01-apps.md b/docs/docs/01-ibc/03-apps/01-apps.md index 47958944c0c..2b7089171dd 100644 --- a/docs/docs/01-ibc/03-apps/01-apps.md +++ b/docs/docs/01-ibc/03-apps/01-apps.md @@ -1,7 +1,7 @@ --- title: IBC Applications sidebar_label: IBC Applications -sidebar_position: 2 +sidebar_position: 1 slug: /ibc/apps/apps --- diff --git a/docs/docs/01-ibc/07-relayer.md b/docs/docs/01-ibc/07-relayer.md index b5b37f5a4f2..4aff0849f0b 100644 --- a/docs/docs/01-ibc/07-relayer.md +++ b/docs/docs/01-ibc/07-relayer.md @@ -34,7 +34,7 @@ a module event emission with the attribute value `ibc_` (02-clien ### Subscribing with Tendermint -Calling the Tendermint RPC method `Subscribe` via [Tendermint's Websocket](https://docs.tendermint.com/main/rpc/) will return events using +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using Tendermint's internal representation of them. Instead of receiving back a list of events as they were emitted, Tendermint will return the type `map[string][]string` which maps a string in the form `.` to `attribute_value`. This causes extraction of the event diff --git a/docs/docs/03-light-clients/01-developer-guide/08-proposals.md b/docs/docs/03-light-clients/01-developer-guide/08-proposals.md index 3e2cd6731d4..8f71d774a0a 100644 --- a/docs/docs/03-light-clients/01-developer-guide/08-proposals.md +++ b/docs/docs/03-light-clients/01-developer-guide/08-proposals.md @@ -8,7 +8,7 @@ slug: /ibc/light-clients/proposals # Handling proposals -It is possible to update the client with the state of the substitute client through a governance proposal. [This type of governance proposal](../../01-ibc/06-proposals.md) is typically used to recover an expired or frozen client, as it can recover the entire state and therefore all existing channels built on top of the client. `RecoverClient` should be implemented to handle the proposal. +It is possible to update the client with the state of the substitute client through a governance proposal. This type of governance proposal is typically used to recover an expired or frozen client, as it can recover the entire state and therefore all existing channels built on top of the client. `RecoverClient` should be implemented to handle the proposal. ## Implementing `RecoverClient` diff --git a/docs/docs/05-migrations/02-sdk-to-v1.md b/docs/docs/05-migrations/02-sdk-to-v1.md index e24c0f62bb6..25a45ffb961 100644 --- a/docs/docs/05-migrations/02-sdk-to-v1.md +++ b/docs/docs/05-migrations/02-sdk-to-v1.md @@ -112,7 +112,7 @@ app.IBCKeeper = ibckeeper.NewKeeper( ### UpdateClientProposal -The `UpdateClient` has been modified to take in two client-identifiers and one initial height. Please see the [documentation](../01-ibc/06-proposals.md) for more information. +The `UpdateClient` has been modified to take in two client-identifiers and one initial height. ### UpgradeProposal diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/07-relayer.md b/docs/versioned_docs/version-v4.6.x/01-ibc/07-relayer.md index b5b37f5a4f2..4aff0849f0b 100644 --- a/docs/versioned_docs/version-v4.6.x/01-ibc/07-relayer.md +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/07-relayer.md @@ -34,7 +34,7 @@ a module event emission with the attribute value `ibc_` (02-clien ### Subscribing with Tendermint -Calling the Tendermint RPC method `Subscribe` via [Tendermint's Websocket](https://docs.tendermint.com/main/rpc/) will return events using +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using Tendermint's internal representation of them. Instead of receiving back a list of events as they were emitted, Tendermint will return the type `map[string][]string` which maps a string in the form `.` to `attribute_value`. This causes extraction of the event diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/07-relayer.md b/docs/versioned_docs/version-v5.4.x/01-ibc/07-relayer.md index b5b37f5a4f2..4aff0849f0b 100644 --- a/docs/versioned_docs/version-v5.4.x/01-ibc/07-relayer.md +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/07-relayer.md @@ -34,7 +34,7 @@ a module event emission with the attribute value `ibc_` (02-clien ### Subscribing with Tendermint -Calling the Tendermint RPC method `Subscribe` via [Tendermint's Websocket](https://docs.tendermint.com/main/rpc/) will return events using +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using Tendermint's internal representation of them. Instead of receiving back a list of events as they were emitted, Tendermint will return the type `map[string][]string` which maps a string in the form `.` to `attribute_value`. This causes extraction of the event diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/07-relayer.md b/docs/versioned_docs/version-v6.3.x/01-ibc/07-relayer.md index b5b37f5a4f2..4aff0849f0b 100644 --- a/docs/versioned_docs/version-v6.3.x/01-ibc/07-relayer.md +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/07-relayer.md @@ -34,7 +34,7 @@ a module event emission with the attribute value `ibc_` (02-clien ### Subscribing with Tendermint -Calling the Tendermint RPC method `Subscribe` via [Tendermint's Websocket](https://docs.tendermint.com/main/rpc/) will return events using +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using Tendermint's internal representation of them. Instead of receiving back a list of events as they were emitted, Tendermint will return the type `map[string][]string` which maps a string in the form `.` to `attribute_value`. This causes extraction of the event diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/07-relayer.md b/docs/versioned_docs/version-v7.8.x/01-ibc/07-relayer.md index b5b37f5a4f2..4aff0849f0b 100644 --- a/docs/versioned_docs/version-v7.8.x/01-ibc/07-relayer.md +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/07-relayer.md @@ -34,7 +34,7 @@ a module event emission with the attribute value `ibc_` (02-clien ### Subscribing with Tendermint -Calling the Tendermint RPC method `Subscribe` via [Tendermint's Websocket](https://docs.tendermint.com/main/rpc/) will return events using +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using Tendermint's internal representation of them. Instead of receiving back a list of events as they were emitted, Tendermint will return the type `map[string][]string` which maps a string in the form `.` to `attribute_value`. This causes extraction of the event diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/08-relayer.md b/docs/versioned_docs/version-v8.5.x/01-ibc/08-relayer.md index 7caf072c275..b4d40fc1a93 100644 --- a/docs/versioned_docs/version-v8.5.x/01-ibc/08-relayer.md +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/08-relayer.md @@ -34,7 +34,7 @@ a module event emission with the attribute value `ibc_` (02-clien ### Subscribing with Tendermint -Calling the Tendermint RPC method `Subscribe` via [Tendermint's Websocket](https://docs.tendermint.com/main/rpc/) will return events using +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using Tendermint's internal representation of them. Instead of receiving back a list of events as they were emitted, Tendermint will return the type `map[string][]string` which maps a string in the form `.` to `attribute_value`. This causes extraction of the event diff --git a/docs/versioned_docs/version-v9.0.x/01-ibc/08-relayer.md b/docs/versioned_docs/version-v9.0.x/01-ibc/08-relayer.md index 7caf072c275..b4d40fc1a93 100644 --- a/docs/versioned_docs/version-v9.0.x/01-ibc/08-relayer.md +++ b/docs/versioned_docs/version-v9.0.x/01-ibc/08-relayer.md @@ -34,7 +34,7 @@ a module event emission with the attribute value `ibc_` (02-clien ### Subscribing with Tendermint -Calling the Tendermint RPC method `Subscribe` via [Tendermint's Websocket](https://docs.tendermint.com/main/rpc/) will return events using +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using Tendermint's internal representation of them. Instead of receiving back a list of events as they were emitted, Tendermint will return the type `map[string][]string` which maps a string in the form `.` to `attribute_value`. This causes extraction of the event From b38388237e040cb831641de084d652656586dc3e Mon Sep 17 00:00:00 2001 From: Aditya Sripal <14364734+AdityaSripal@users.noreply.github.com> Date: Thu, 20 Mar 2025 14:09:50 +0100 Subject: [PATCH 3/4] improve psuedocode, add security model and design principles section --- .../01-ibc/04-middleware/02-developIBCv2.md | 85 +++++++++++++++---- 1 file changed, 69 insertions(+), 16 deletions(-) diff --git a/docs/docs/01-ibc/04-middleware/02-developIBCv2.md b/docs/docs/01-ibc/04-middleware/02-developIBCv2.md index ad51d54d881..750c168f30a 100644 --- a/docs/docs/01-ibc/04-middleware/02-developIBCv2.md +++ b/docs/docs/01-ibc/04-middleware/02-developIBCv2.md @@ -10,7 +10,7 @@ slug: /ibc/middleware/developIBCv2 IBC middleware will wrap over an underlying IBC application (a base application or downstream middleware) and sits between core IBC and the base application. -The interfaces a middleware must implement are found in [core/api](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/module.go#L11). Note that this interface has chanhged from IBC classic. +The interfaces a middleware must implement are found in [core/api](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/module.go#L11). Note that this interface has changed from IBC classic. An `IBCMiddleware` struct implementing the `Middleware` interface, can be defined with its constructor as follows: @@ -72,11 +72,22 @@ func (im IBCMiddleware) OnRecvPacket( payload channeltypesv2.Payload, relayer sdk.AccAddress, ) channeltypesv2.RecvPacketResult { - recvResult := im.app.OnRecvPacket(ctx, sourceClient, destinationClient, sequence, payload, relayer) + // Middleware may choose to do custom preprocessing logic before calling the underlying app OnRecvPacket + // Middleware may choose to error early and return a RecvPacketResult Failure + // Middleware may choose to modify the payload before passing on to OnRecvPacket though this + // should only be done to support very advanced custom behavior + // Middleware MUST NOT modify client identifiers and sequence + doCustomPreProcessLogic() + + // call underlying app OnRecvPacket + recvResult := im.app.OnRecvPacket(ctx, sourceClient, destinationClient, sequence, payload, relayer) + if recvResult.Status == PACKET_STATUS_FAILURE { + return recvResult + } - doCustomLogic(recvResult) // middleware may modify success acknowledgment + doCustomPostProcessLogic(recvResult) // middleware may modify recvResult - return recvResult + return recvResult } ``` @@ -94,14 +105,27 @@ func (im IBCMiddleware) OnAcknowledgementPacket( payload channeltypesv2.Payload, relayer sdk.AccAddress, ) error { - - doCustomLogic(payload, acknowledgement) - - return nil + // preprocessing logic may modify the acknowledgement before passing to + // the underlying app though this should only be done in advanced cases + // Middleware may return error early + // it MUST NOT change the identifiers of the clients or the sequence + doCustomPreProcessLogic(payload, acknowledgement) + + // call underlying app OnAcknowledgementPacket + err = im.app.OnAcknowledgementPacket( + sourceClient, destinationClient, sequence, + acknowledgement, payload, relayer + ) + if err != nil { + return err + } + + // may perform some post acknowledgement logic and return error here + return doCustomPostProcessLogic() } ``` -See [here](hhttps://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L236-L302) an example implementation of this callback for the Callbacks Middleware module. +See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L236-L302) an example implementation of this callback for the Callbacks Middleware module. #### `OnTimeoutPacket` @@ -114,9 +138,21 @@ func (im IBCMiddleware) OnTimeoutPacket( payload channeltypesv2.Payload, relayer sdk.AccAddress, ) error { - doCustomLogic(payload) - - return nil + // Middleware may choose to do custom preprocessing logic before calling the underlying app OnTimeoutPacket + // Middleware may return error early + doCustomPreProcessLogic(payload) + + // call underlying app OnTimeoutPacket + err = im.app.OnTimeoutPacket( + sourceClient, destinationClient, sequence, + payload, relayer + ) + if err != nil { + return err + } + + // may perform some post timeout logic and return error here + return doCustomPostProcessLogic() } ``` @@ -143,17 +179,20 @@ func (im *IBCMiddleware) GetWriteAckWrapper() api.WriteAcknowledgementWrapper { This is where the middleware acknowledgement handling is finalised. An example is shown in the [callbacks middleware](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L369-L454) ```go +// WriteAcknowledgement facilitates acknowledgment being written asynchronously +// The call stack flows from the IBC application to the IBC core handler +// Thus this function is called by the IBC app or a lower-level middleware func (im IBCMiddleware) WriteAcknowledgement( ctx sdk.Context, clientID string, sequence uint64, ack channeltypesv2.Acknowledgement, ) error { - // packet and payload handling and validation - - // custom middleware logic, for example callbacks. + doCustomPreProcessLogic() // may modify acknowledgement -return nil + return im.writeAckWrapper.WriteAcknowledgement( + ctx, clientId, sequence, ack, + ) } ``` @@ -183,3 +222,17 @@ The example integration is detailed for an IBC v2 stack using transfer and the c ibcRouterV2.AddRoute(ibctransfertypes.PortID, ibcv2TransferStack) app.IBCKeeper.SetRouterV2(ibcRouterV2) ``` + +## Security Model + +IBC Middleware completely wraps all communication between IBC core and the application that it is wired with. Thus, the IBC Middleware has complete control to modify any packets and acknowledgements the underlying application receives or sends. Thus, if a chain chooses to wrap an application with a given middleware, that middleware is **completely trusted** and part of the application's security model. **Do not use middlewares that are untrusted.** + +## Design Principles + +The middleware follows a decorator pattern that wraps an underlying application's connection to the IBC core handlers. Thus, when implementing a middleware for a specific purpose, it is recommended to be as **unintrusive** as possible in the middleware design while still accomplishing the intended behavior. + +The least intrusive middleware is stateless. They simply read the ICS26 callback arguments before calling the underlying app's callback and error if the arguments are not acceptable (e.g. whitelisting packets). Stateful middleware that are used solely for erroring are also very simple to build, an example of this would be a rate-limiting middleware that prevents transfer outflows from getting too high within a certain time frame. + +Middleware that directly interfere with the payload or acknowledgement before passing control to the underlying app are way more intrusive to the underyling app processing. This makes such middleware more error-prone when implementing as incorrect handling can cause the underlying app to break or worse execute unexpected behavior. Moreover, such middleware typically needs to be built for a specific underlying app rather than being generic. An example of this is the packet-forwarding middleware which modifies the payload and is specifically built for transfer. + +Middleware that modifies the payload or acknowledgement such that it is no longer readable by the underlying application is the most complicated middleware. Since it is not readable by the underlying apps, if these middleware write additional state into payloads and acknowledgements that get committed to IBC core provable state, there MUST be an equivalent counterparty middleware that is able to parse and intepret this additional state while also converting the payload and acknowledgment back to a readable form for the underlying application on its side. Thus, such middleware requires deployment on both sides of an IBC connection or the packet processing will break. This is the hardest type of middleware to implement, integrate and deploy. Thus, it is not recommended unless absolutely necessary to fulfill the given use case. From 726c4d7d39fb291d22304317fce4d72a8e46863e Mon Sep 17 00:00:00 2001 From: Susannah Evans <65018876+womensrights@users.noreply.github.com> Date: Thu, 20 Mar 2025 15:04:35 +0100 Subject: [PATCH 4/4] add quick nav --- docs/docs/01-ibc/04-middleware/02-developIBCv2.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/docs/01-ibc/04-middleware/02-developIBCv2.md b/docs/docs/01-ibc/04-middleware/02-developIBCv2.md index 750c168f30a..98d1452e9d7 100644 --- a/docs/docs/01-ibc/04-middleware/02-developIBCv2.md +++ b/docs/docs/01-ibc/04-middleware/02-developIBCv2.md @@ -5,8 +5,16 @@ sidebar_position: 2 slug: /ibc/middleware/developIBCv2 --- +# Quick Navigation -# Create a custom IBC v2 middleware +1. [Create a custom IBC v2 middleware](#create-a-custom-ibc-v2-middleware) +2. [Implement `IBCModule` interface](#implement-ibcmodule-interface) +3. [WriteAckWrapper](#writeackwrapper) +4. [Integrate IBC v2 Middleware](#integrate-ibc-v2-middleware) +5. [Security Model](#security-model) +6. [Design Principles](#design-principles) + +## Create a custom IBC v2 middleware IBC middleware will wrap over an underlying IBC application (a base application or downstream middleware) and sits between core IBC and the base application.