Skip to content

Commit 72ce552

Browse files
Middleware Wiring Improvements (#8528)
Co-authored-by: Gjermund Garaba <[email protected]>
1 parent 4d99a06 commit 72ce552

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+875
-347
lines changed

docs/docs/01-ibc/03-apps/01-apps.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,23 @@ encoded version into each handhshake call as necessary.
178178

179179
ICS20 currently implements basic string matching with a single supported version.
180180

181+
### ICS4Wrapper
182+
183+
The IBC application interacts with core IBC through the `ICS4Wrapper` interface for any application-initiated actions like: `SendPacket` and `WriteAcknowledgement`. This may be directly the IBCChannelKeeper or a middleware that sits between the application and the IBC ChannelKeeper.
184+
185+
If the application is being wired with a custom middleware, the application **must** have its ICS4Wrapper set to the middleware directly above it on the stack through the following call:
186+
187+
```go
188+
// SetICS4Wrapper sets the ICS4Wrapper. This function may be used after
189+
// the module's initialization to set the middleware which is above this
190+
// module in the IBC application stack.
191+
// The ICS4Wrapper **must** be used for sending packets and writing acknowledgements
192+
// to ensure that the middleware can intercept and process these calls.
193+
// Do not use the channel keeper directly to send packets or write acknowledgements
194+
// as this will bypass the middleware.
195+
SetICS4Wrapper(wrapper ICS4Wrapper)
196+
```
197+
181198
### Custom Packets
182199

183200
Modules connected by a channel must agree on what application data they are sending over the

docs/docs/01-ibc/04-middleware/02-develop.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ The interfaces a middleware must implement are found [here](https://github.com/c
3131
type Middleware interface {
3232
IBCModule // middleware has access to an underlying application which may be wrapped by more middleware
3333
ICS4Wrapper // middleware has access to ICS4Wrapper which may be core IBC Channel Handler or a higher-level middleware that wraps this middleware.
34+
35+
// SetUnderlyingModule sets the underlying IBC module. This function may be used after
36+
// the middleware's initialization to set the ibc module which is below this middleware.
37+
SetUnderlyingApplication(IBCModule)
3438
}
3539
```
3640

@@ -42,14 +46,12 @@ An `IBCMiddleware` struct implementing the `Middleware` interface, can be define
4246
// IBCMiddleware implements the ICS26 callbacks and ICS4Wrapper for the fee middleware given the
4347
// fee keeper and the underlying application.
4448
type IBCMiddleware struct {
45-
app porttypes.IBCModule
46-
keeper keeper.Keeper
49+
keeper *keeper.Keeper
4750
}
4851

4952
// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application
50-
func NewIBCMiddleware(app porttypes.IBCModule, k keeper.Keeper) IBCMiddleware {
53+
func NewIBCMiddleware(k *keeper.Keeper) IBCMiddleware {
5154
return IBCMiddleware{
52-
app: app,
5355
keeper: k,
5456
}
5557
}
@@ -476,3 +478,26 @@ func GetAppVersion(
476478
```
477479

478480
See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/keeper/relay.go#L58-L74) an example implementation of this function for the ICS-29 Fee Middleware module.
481+
482+
## Wiring Interface Requirements
483+
484+
Middleware must also implement the following functions so that they can be called in the stack builder in order to correctly wire the application stack together: `SetUnderlyingApplication` and `SetICS4Wrapper`.
485+
486+
```go
487+
// SetUnderlyingModule sets the underlying IBC module. This function may be used after
488+
// the middleware's initialization to set the ibc module which is below this middleware.
489+
SetUnderlyingApplication(IBCModule)
490+
491+
// SetICS4Wrapper sets the ICS4Wrapper. This function may be used after
492+
// the module's initialization to set the middleware which is above this
493+
// module in the IBC application stack.
494+
// The ICS4Wrapper **must** be used for sending packets and writing acknowledgements
495+
// to ensure that the middleware can intercept and process these calls.
496+
// Do not use the channel keeper directly to send packets or write acknowledgements
497+
// as this will bypass the middleware.
498+
SetICS4Wrapper(wrapper ICS4Wrapper)
499+
```
500+
501+
The middleware itself should have access to the `underlying app` (note this may be a base app or an application wrapped by layers of lower-level middleware(s)) and access to the higher layer `ICS4wrapper`. The `underlying app` gets called during the relayer initiated actions: `recvPacket`, `acknowledgePacket`, and `timeoutPacket`. The `ics4Wrapper` gets called on user-initiated actions like `sendPacket` and `writeAcknowledgement`.
502+
503+
The functions above are used by the `StackBuilder` during application setup to wire the stack correctly. The stack must be wired first and have all of the wrappers and applications set correctly before transaction execution starts and packet processing begins.

docs/docs/01-ibc/04-middleware/03-integration.md

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,13 @@ The order of middleware **matters**, function calls from IBC to the application
2525

2626
// middleware 1 and middleware 3 are stateful middleware,
2727
// perhaps implementing separate sdk.Msg and Handlers
28-
mw1Keeper := mw1.NewKeeper(storeKey1, ..., ics4Wrapper: channelKeeper, ...) // in stack 1 & 3
28+
// NOTE: NewKeeper returns a pointer so that we can modify
29+
// the keepers later after initialization
30+
// They are all initialized to use the channelKeeper directly at the start
31+
mw1Keeper := mw1.NewKeeper(storeKey1, ..., channelKeeper) // in stack 1 & 3
2932
// middleware 2 is stateless
30-
mw3Keeper1 := mw3.NewKeeper(storeKey3,..., ics4Wrapper: mw1Keeper, ...) // in stack 1
31-
mw3Keeper2 := mw3.NewKeeper(storeKey3,..., ics4Wrapper: channelKeeper, ...) // in stack 2
33+
mw3Keeper1 := mw3.NewKeeper(storeKey3,..., channelKeeper) // in stack 1
34+
mw3Keeper2 := mw3.NewKeeper(storeKey3,..., channelKeeper) // in stack 2
3235

3336
// Only create App Module **once** and register in app module
3437
// if the module maintains independent state and/or processes sdk.Msgs
@@ -55,13 +58,26 @@ customIBCModule1 := custom.NewIBCModule(customKeeper1, "portCustom1")
5558
customIBCModule2 := custom.NewIBCModule(customKeeper2, "portCustom2")
5659

5760
// create IBC stacks by combining middleware with base application
61+
// IBC Stack builders are initialized with the IBC ChannelKeeper which is the top-level ICS4Wrapper
5862
// NOTE: since middleware2 is stateless it does not require a Keeper
5963
// stack 1 contains mw1 -> mw3 -> transfer
60-
stack1 := mw1.NewIBCMiddleware(mw3.NewIBCMiddleware(transferIBCModule, mw3Keeper1), mw1Keeper)
64+
stack1 := porttypes.NewStackBuilder(ibcChannelKeeper).
65+
Base(transferIBCModule).
66+
Next(mw3).
67+
Next(mw1).
68+
Build()
6169
// stack 2 contains mw3 -> mw2 -> custom1
62-
stack2 := mw3.NewIBCMiddleware(mw2.NewIBCMiddleware(customIBCModule1), mw3Keeper2)
70+
stack2 := porttypes.NewStackBuilder(ibcChannelKeeper).
71+
Base(customIBCModule1).
72+
Next(mw2).
73+
Next(mw3).
74+
Build()
6375
// stack 3 contains mw2 -> mw1 -> custom2
64-
stack3 := mw2.NewIBCMiddleware(mw1.NewIBCMiddleware(customIBCModule2, mw1Keeper))
76+
stack3 := porttypes.NewStackBuilder(ibcChannelKeeper).
77+
Base(customIBCModule2).
78+
Next(mw1).
79+
Next(mw2).
80+
Build()
6581

6682
// associate each stack with the moduleName provided by the underlying Keeper
6783
ibcRouter := porttypes.NewRouter()
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
---
2+
title: Support the new StackBuilder primitive for Wiring Middlewares in the chain application
3+
sidebar_label: Support StackBuilder Wiring
4+
sidebar_position: 1
5+
slug: /migrations/support-stackbuilder
6+
---
7+
8+
# Migration for Chains wishing to use StackBuilder
9+
10+
The StackBuilder struct is a new primitive for wiring middleware in a simpler and less error-prone manner. It is not a breaking change thus the existing method of wiring middleware still works, though it is highly recommended to transition to the new wiring method.
11+
12+
Refer to the [integration guide](../01-ibc/04-middleware/03-integration.md) to understand how to use this new middleware to improve middleware wiring in the chain application setup.
13+
14+
# Migrations for Application Developers
15+
16+
In order to be wired with the new StackBuilder primitive, applications and middlewares must implement new methods as part of their respective interfaces.
17+
18+
IBC Applications must implement a new `SetICS4Wrapper` which will set the `ICS4Wrapper` through which the application will call `SendPacket` and `WriteAcknowledgement`. It is recommended that IBC applications are initialized first with the IBC ChannelKeeper directly, and then modified with a middleware ICS4Wrapper during the stack wiring.
19+
20+
```go
21+
// SetICS4Wrapper sets the ICS4Wrapper. This function may be used after
22+
// the module's initialization to set the middleware which is above this
23+
// module in the IBC application stack.
24+
// The ICS4Wrapper **must** be used for sending packets and writing acknowledgements
25+
// to ensure that the middleware can intercept and process these calls.
26+
// Do not use the channel keeper directly to send packets or write acknowledgements
27+
// as this will bypass the middleware.
28+
SetICS4Wrapper(wrapper ICS4Wrapper)
29+
```
30+
31+
Many applications have a stateful keeper that executes the logic for sending packets and writing acknowledgements. In this case, the keeper in the application must be a **pointer** reference so that it can be modified in place after initialization.
32+
33+
The initialization should be modified to no longer take in an addition `ics4Wrapper` as this gets modified later by `SetICS4Wrapper`. The constructor function must also return a **pointer** reference so that it may be modified in-place by the stack builder.
34+
35+
Below is an example IBCModule that supports the stack builder wiring.
36+
37+
E.g.
38+
39+
```go
40+
type IBCModule struct {
41+
keeper *keeper.Keeper
42+
}
43+
44+
// NewIBCModule creates a new IBCModule given the keeper
45+
func NewIBCModule(k *keeper.Keeper) *IBCModule {
46+
return &IBCModule{
47+
keeper: k,
48+
}
49+
}
50+
51+
// SetICS4Wrapper sets the ICS4Wrapper. This function may be used after
52+
// the module's initialization to set the middleware which is above this
53+
// module in the IBC application stack.
54+
func (im IBCModule) SetICS4Wrapper(wrapper porttypes.ICS4Wrapper) {
55+
if wrapper == nil {
56+
panic("ICS4Wrapper cannot be nil")
57+
}
58+
59+
im.keeper.WithICS4Wrapper(wrapper)
60+
}
61+
62+
/// Keeper file that has ICS4Wrapper internal to its own struct
63+
64+
// Keeper defines the IBC fungible transfer keeper
65+
type Keeper struct {
66+
...
67+
ics4Wrapper porttypes.ICS4Wrapper
68+
69+
// Keeper is initialized with ICS4Wrapper
70+
// being equal to the top-level channelKeeper
71+
// this can be changed by calling WithICS4Wrapper
72+
// with a different middleware ICS4Wrapper
73+
channelKeeper types.ChannelKeeper
74+
...
75+
}
76+
77+
// WithICS4Wrapper sets the ICS4Wrapper. This function may be used after
78+
// the keepers creation to set the middleware which is above this module
79+
// in the IBC application stack.
80+
func (k *Keeper) WithICS4Wrapper(wrapper porttypes.ICS4Wrapper) {
81+
k.ics4Wrapper = wrapper
82+
}
83+
```
84+
85+
# Migration for Middleware Developers
86+
87+
Since Middleware is itself implement the IBC application interface, it must also implement `SetICS4Wrapper` in the same way as IBC applications.
88+
89+
Additionally, IBC Middleware has an underlying IBC application that it calls into as well. Previously this application would be set in the middleware upon construction. With the stack builder primitive, the application is only set during upon calling `stack.Build()`. Thus, middleware is additionally responsible for implementing the new method: `SetUnderlyingApplication`:
90+
91+
```go
92+
// SetUnderlyingModule sets the underlying IBC module. This function may be used after
93+
// the middleware's initialization to set the ibc module which is below this middleware.
94+
SetUnderlyingApplication(IBCModule)
95+
```
96+
97+
The initialization should not include the ICS4Wrapper and application as this gets set later. The constructor function for Middlewares **must** be modified to return a **pointer** reference so that it can be modified in place by the stack builder.
98+
99+
Below is an example middleware setup:
100+
101+
```go
102+
// IBCMiddleware implements the ICS26 callbacks
103+
type IBCMiddleware struct {
104+
app porttypes.PacketUnmarshalerModule
105+
ics4Wrapper porttypes.ICS4Wrapper
106+
107+
// this is a stateful middleware with its own internal keeper
108+
mwKeeper *keeper.MiddlewareKeeper
109+
110+
// this is a middleware specific field
111+
mwField any
112+
}
113+
114+
// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application.
115+
// NOTE: It **must** return a pointer reference so it can be
116+
// modified in place by the stack builder
117+
// NOTE: We do not pass in the underlying app and ICS4Wrapper here as this happens later
118+
func NewIBCMiddleware(
119+
mwKeeper *keeper.MiddlewareKeeper, mwField any,
120+
) *IBCMiddleware {
121+
return &IBCMiddleware{
122+
mwKeeper: mwKeeper,
123+
mwField, mwField,
124+
}
125+
}
126+
127+
// SetICS4Wrapper sets the ICS4Wrapper. This function may be used after the
128+
// middleware's creation to set the middleware which is above this module in
129+
// the IBC application stack.
130+
func (im *IBCMiddleware) SetICS4Wrapper(wrapper porttypes.ICS4Wrapper) {
131+
if wrapper == nil {
132+
panic("ICS4Wrapper cannot be nil")
133+
}
134+
im.mwKeeper.WithICS4Wrapper(wrapper)
135+
}
136+
137+
// SetUnderlyingApplication sets the underlying IBC module. This function may be used after
138+
// the middleware's creation to set the ibc module which is below this middleware.
139+
func (im *IBCMiddleware) SetUnderlyingApplication(app porttypes.IBCModule) {
140+
if app == nil {
141+
panic(errors.New("underlying application cannot be nil"))
142+
}
143+
if im.app != nil {
144+
panic(errors.New("underlying application already set"))
145+
}
146+
im.app = app
147+
}
148+
```

e2e/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ module github.com/cosmos/ibc-go/e2e
22

33
go 1.24.3
44

5+
// TODO: Remove when v11 release of interchaintest is available (that is where this one is coming from)
6+
replace github.com/cosmos/interchain-security/v7 => github.com/cosmos/interchain-security/v7 v7.0.0-20250622154438-73c73cf686e5
7+
58
replace (
69
github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10 => ../modules/light-clients/08-wasm
710
// uncomment to use the local version of ibc-go, you will need to run `go mod tidy` in e2e directory.

e2e/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -856,8 +856,8 @@ github.com/cosmos/iavl v1.2.4 h1:IHUrG8dkyueKEY72y92jajrizbkZKPZbMmG14QzsEkw=
856856
github.com/cosmos/iavl v1.2.4/go.mod h1:GiM43q0pB+uG53mLxLDzimxM9l/5N9UuSY3/D0huuVw=
857857
github.com/cosmos/ics23/go v0.11.0 h1:jk5skjT0TqX5e5QJbEnwXIS2yI2vnmLOgpQPeM5RtnU=
858858
github.com/cosmos/ics23/go v0.11.0/go.mod h1:A8OjxPE67hHST4Icw94hOxxFEJMBG031xIGF/JHNIY0=
859-
github.com/cosmos/interchain-security/v7 v7.0.0-20250408210344-06e0dc6bf6d6 h1:SzJ/+uqrTsJmI+f/GqPdC4lGxgDQKYvtRCMXFdJljNM=
860-
github.com/cosmos/interchain-security/v7 v7.0.0-20250408210344-06e0dc6bf6d6/go.mod h1:W7JHsNaZ5XoH88cKT+wuCRsXkx/Fcn2kEwzpeGdJBxI=
859+
github.com/cosmos/interchain-security/v7 v7.0.0-20250622154438-73c73cf686e5 h1:6LnlaeVk/wHPicQG8NqElL1F1FDVGEl7xF0JXGGhgEs=
860+
github.com/cosmos/interchain-security/v7 v7.0.0-20250622154438-73c73cf686e5/go.mod h1:9EIcx4CzKt/5/2KHtniyzt7Kz8Wgk6fdvyr+AFIUGHc=
861861
github.com/cosmos/interchaintest/v10 v10.0.0 h1:DEsXOS10x191Q3EU4RkOnyqahGCTnLaBGEN//C2MvUQ=
862862
github.com/cosmos/interchaintest/v10 v10.0.0/go.mod h1:caS4BRkAg8NkiZ8BsHEzjNBibt2OVdTctW5Ezz+Jqxs=
863863
github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE=

0 commit comments

Comments
 (0)