StackBuilder 结构体是一种新的基础原语,用于以更简单且更不易出错的方式组装中间件。它不是破坏性变更,因此现有的中间件组装方式仍然可用,但强烈建议迁移到新的组装方式。 请参考集成指南,了解如何使用这一新的中间件机制来改进链应用初始化中的中间件组装。

面向应用开发者的迁移说明

为了能够接入新的 StackBuilder 原语,应用和中间件必须在各自接口中实现新的方法。 IBC 应用必须实现新的 SetICS4Wrapper,用于设置应用调用 SendPacket 和 WriteAcknowledgement 时所经过的 ICS4Wrapper。建议 IBC 应用在初始化时先直接使用 IBC ChannelKeeper,然后在栈组装过程中再通过中间件的 ICS4Wrapper 进行修改。
/ SetICS4Wrapper sets the ICS4Wrapper. This function may be used after
/ the module's initialization to set the middleware which is above this
/ module in the IBC application stack.
/ The ICS4Wrapper **must** be used for sending packets and writing acknowledgements
/ to ensure that the middleware can intercept and process these calls.
/ Do not use the channel keeper directly to send packets or write acknowledgements
/ as this will bypass the middleware.
SetICS4Wrapper(wrapper ICS4Wrapper)
许多应用都包含一个带状态的 keeper,由它负责执行发送数据包和写入确认的逻辑。在这种情况下,应用中的 keeper 必须是指针引用,这样才能在初始化之后原地修改。 初始化逻辑应修改为不再接收额外的 ics4Wrapper,因为它会在后续通过 SetICS4Wrapper 再进行设置。构造函数也必须返回指针引用,以便栈构建器可以原地修改它。 下面是一个支持栈构建器组装方式的 IBCModule 示例。 例如:
type IBCModule struct {
    keeper *keeper.Keeper
}

/ NewIBCModule creates a new IBCModule given the keeper
func NewIBCModule(k *keeper.Keeper) *IBCModule {
    return &IBCModule{
    keeper: k,
}
}

/ SetICS4Wrapper sets the ICS4Wrapper. This function may be used after
/ the module's initialization to set the middleware which is above this
/ module in the IBC application stack.
func (im IBCModule)

SetICS4Wrapper(wrapper porttypes.ICS4Wrapper) {
    if wrapper == nil {
    panic("ICS4Wrapper cannot be nil")
}

im.keeper.WithICS4Wrapper(wrapper)
}

/ Keeper file that has ICS4Wrapper internal to its own struct

/ Keeper defines the IBC fungible transfer keeper
type Keeper struct {
	...
	ics4Wrapper   porttypes.ICS4Wrapper

    / Keeper is initialized with ICS4Wrapper
    / being equal to the top-level channelKeeper
    / this can be changed by calling WithICS4Wrapper
    / with a different middleware ICS4Wrapper
	channelKeeper types.ChannelKeeper
	...
}

/ WithICS4Wrapper sets the ICS4Wrapper. This function may be used after
/ the keepers creation to set the middleware which is above this module
/ in the IBC application stack.
func (k *Keeper)

WithICS4Wrapper(wrapper porttypes.ICS4Wrapper) {
    k.ics4Wrapper = wrapper
}

面向中间件开发者的迁移说明

由于中间件本身也实现了 IBC 应用接口,因此它也必须像 IBC 应用一样实现 SetICS4Wrapper。 此外,IBC 中间件本身还会调用其底层的 IBC 应用。此前,这个应用通常会在中间件构造时传入并设置。引入栈构建器原语后,这个应用只会在调用 stack.Build() 时才被设置。因此,中间件还需要额外实现一个新方法:SetUnderlyingApplication:
/ SetUnderlyingModule sets the underlying IBC module. This function may be used after
/ the middleware's initialization to set the ibc module which is below this middleware.
SetUnderlyingApplication(IBCModule)
初始化逻辑不应再包含 ICS4Wrapper 和应用参数,因为它们会在后续再被设置。中间件的构造函数必须修改为返回指针引用,以便栈构建器可以原地修改它。 下面是一个中间件配置示例:
/ IBCMiddleware implements the ICS26 callbacks
type IBCMiddleware struct {
    app         porttypes.PacketUnmarshalerModule
	ics4Wrapper porttypes.ICS4Wrapper

    / this is a stateful middleware with its own internal keeper
	mwKeeper *keeper.MiddlewareKeeper

	/ this is a middleware specific field
	mwField any
}

/ NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application.
/ NOTE: It **must** return a pointer reference so it can be
/ modified in place by the stack builder
/ NOTE: We do not pass in the underlying app and ICS4Wrapper here as this happens later
func NewIBCMiddleware(
	mwKeeper *keeper.MiddlewareKeeper, mwField any,
) *IBCMiddleware {
    return &IBCMiddleware{
    mwKeeper: mwKeeper,
        mwField, mwField,
}
}

/ SetICS4Wrapper sets the ICS4Wrapper. This function may be used after the
/ middleware's creation to set the middleware which is above this module in
/ the IBC application stack.
func (im *IBCMiddleware)

SetICS4Wrapper(wrapper porttypes.ICS4Wrapper) {
    if wrapper == nil {
    panic("ICS4Wrapper cannot be nil")
}

im.mwKeeper.WithICS4Wrapper(wrapper)
}

/ SetUnderlyingApplication sets the underlying IBC module. This function may be used after
/ the middleware's creation to set the ibc module which is below this middleware.
func (im *IBCMiddleware)

SetUnderlyingApplication(app porttypes.IBCModule) {
    if app == nil {
    panic(errors.New("underlying application cannot be nil"))
}
    if im.app != nil {
    panic(errors.New("underlying application already set"))
}

im.app = app
}

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. Refer to the integration guide to understand how to use this new middleware to improve middleware wiring in the chain application setup.

Migrations for Application Developers

In order to be wired with the new StackBuilder primitive, applications and middlewares must implement new methods as part of their respective interfaces. 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.
/ SetICS4Wrapper sets the ICS4Wrapper. This function may be used after
/ the module's initialization to set the middleware which is above this
/ module in the IBC application stack.
/ The ICS4Wrapper **must** be used for sending packets and writing acknowledgements
/ to ensure that the middleware can intercept and process these calls.
/ Do not use the channel keeper directly to send packets or write acknowledgements
/ as this will bypass the middleware.
SetICS4Wrapper(wrapper ICS4Wrapper)
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. 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. Below is an example IBCModule that supports the stack builder wiring. E.g.
type IBCModule struct {
    keeper *keeper.Keeper
}

/ NewIBCModule creates a new IBCModule given the keeper
func NewIBCModule(k *keeper.Keeper) *IBCModule {
    return &IBCModule{
    keeper: k,
}
}

/ SetICS4Wrapper sets the ICS4Wrapper. This function may be used after
/ the module's initialization to set the middleware which is above this
/ module in the IBC application stack.
func (im IBCModule)

SetICS4Wrapper(wrapper porttypes.ICS4Wrapper) {
    if wrapper == nil {
    panic("ICS4Wrapper cannot be nil")
}

im.keeper.WithICS4Wrapper(wrapper)
}

/ Keeper file that has ICS4Wrapper internal to its own struct

/ Keeper defines the IBC fungible transfer keeper
type Keeper struct {
	...
	ics4Wrapper   porttypes.ICS4Wrapper

    / Keeper is initialized with ICS4Wrapper
    / being equal to the top-level channelKeeper
    / this can be changed by calling WithICS4Wrapper
    / with a different middleware ICS4Wrapper
	channelKeeper types.ChannelKeeper
	...
}

/ WithICS4Wrapper sets the ICS4Wrapper. This function may be used after
/ the keepers creation to set the middleware which is above this module
/ in the IBC application stack.
func (k *Keeper)

WithICS4Wrapper(wrapper porttypes.ICS4Wrapper) {
    k.ics4Wrapper = wrapper
}

Migration for Middleware Developers

Since Middleware is itself implement the IBC application interface, it must also implement SetICS4Wrapper in the same way as IBC applications. 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:
/ SetUnderlyingModule sets the underlying IBC module. This function may be used after
/ the middleware's initialization to set the ibc module which is below this middleware.
SetUnderlyingApplication(IBCModule)
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. Below is an example middleware setup:
/ IBCMiddleware implements the ICS26 callbacks
type IBCMiddleware struct {
    app         porttypes.PacketUnmarshalerModule
	ics4Wrapper porttypes.ICS4Wrapper

    / this is a stateful middleware with its own internal keeper
	mwKeeper *keeper.MiddlewareKeeper

	/ this is a middleware specific field
	mwField any
}

/ NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application.
/ NOTE: It **must** return a pointer reference so it can be
/ modified in place by the stack builder
/ NOTE: We do not pass in the underlying app and ICS4Wrapper here as this happens later
func NewIBCMiddleware(
	mwKeeper *keeper.MiddlewareKeeper, mwField any,
) *IBCMiddleware {
    return &IBCMiddleware{
    mwKeeper: mwKeeper,
        mwField, mwField,
}
}

/ SetICS4Wrapper sets the ICS4Wrapper. This function may be used after the
/ middleware's creation to set the middleware which is above this module in
/ the IBC application stack.
func (im *IBCMiddleware)

SetICS4Wrapper(wrapper porttypes.ICS4Wrapper) {
    if wrapper == nil {
    panic("ICS4Wrapper cannot be nil")
}

im.mwKeeper.WithICS4Wrapper(wrapper)
}

/ SetUnderlyingApplication sets the underlying IBC module. This function may be used after
/ the middleware's creation to set the ibc module which is below this middleware.
func (im *IBCMiddleware)

SetUnderlyingApplication(app porttypes.IBCModule) {
    if app == nil {
    panic(errors.New("underlying application cannot be nil"))
}
    if im.app != nil {
    panic(errors.New("underlying application already set"))
}

im.app = app
}