- Create a custom IBC v2 middleware
- Implement
IBCModule interface
- WriteAckWrapper
- Integrate IBC v2 Middleware
- Security Model
- 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.
middleware developers must use the same serialization and deserialization method as in ibc-go’s codec: transfertypes.ModuleCdc.[Must]MarshalJSON
For middleware builders this means:
The interfaces a middleware must implement are found in core/api. Note that this interface has changed from IBC classic.
An IBCMiddleware struct implementing the Middleware interface, can be defined with its constructor as follows:
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). 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 custom 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 received.
OnRecvPacket
See here an example implementation of this callback for the Callbacks Middleware module.
OnAcknowledgementPacket
See here an example implementation of this callback for the Callbacks Middleware module.
OnTimeoutPacket
See here 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.
WriteAcknowledgement
This is where the middleware acknowledgement handling is finalised. An example is shown in the callbacks middleware
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.
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 underlying 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 interpret 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.