middleware developers must use the same serialization and deserialization method as in ibc-go’s codec: transfertypes.ModuleCdc.[Must]MarshalJSON
IBCMiddleware
struct implementing the Middleware
interface, can be defined with its constructor as follows:
Implement IBCModule
interface
IBCMiddleware
is a struct that implements the ICS-26 IBCModule
interface (porttypes.IBCModule
). 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.As will be mentioned in the integration section, this struct should be different than the struct that implements
AppModule
in case the middleware maintains its own internal state and processes separate SDK messages.
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 channel handshake callbacks and packet callbacks. Most of the custom logic will be performed in the packet callbacks, in the case of the channel handshake callbacks, introducing the middleware requires consideration to the version negotiation.
Channel handshake callbacks
Version negotiation
In the case where the IBC middleware expects to speak to a compatible IBC middleware on the counterparty chain, they must use the channel handshake to negotiate the middleware version without interfering in the version negotiation of the underlying application. 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:<middleware_version_key>
key in the JSON struct should be replaced by the actual name of the key for the corresponding middleware (e.g. fee_version
).
During the handshake callbacks, the middleware can unmarshal the version string and retrieve the middleware and application versions. It can do its negotiation logic on <middleware_version_value>
, and pass the <application_version_value>
to the underlying application.
NOTE: Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version unmarshalling and negotiation, and will simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly.
OnChanOpenInit
OnChanOpenTry
OnChanOpenAck
OnChanOpenConfirm
OnChanCloseInit
OnChanCloseConfirm
Packet callbacks
The packet callbacks just like the handshake callbacks wrap the application’s 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.OnRecvPacket
OnAcknowledgementPacket
OnTimeoutPacket
ICS-04 wrappers
Middleware must also wrap ICS-04 so that any communication from the application to thechannelKeeper
goes through the middleware first. Similar to the packet callbacks, the middleware may modify outgoing acknowledgements and packets in any way it wishes.
To ensure optimal generalisability, the ICS4Wrapper
abstraction serves to abstract away whether a middleware is the topmost middleware (and thus directly calling into the ICS-04 channelKeeper
) or itself being wrapped by another middleware.
Remember that middleware can be stateful or stateless. When defining the stateful middleware’s keeper, the ics4Wrapper
field is included. Then the appropriate keeper can be passed when instantiating the middleware’s keeper in app.go
ics4Wrapper
can be passed on directly without having to instantiate a keeper struct for the middleware.
The interface looks as follows:
ics4Wrapper
can be accessed through the keeper.
Check out the references provided for an actual implementation to clarify, where the ics4Wrapper
methods in ibc_middleware.go
simply call the equivalent keeper methods where the actual logic resides.