概述

本标准文档规定了一个模块为了在核心 IBC 与底层应用之间充当中间件而必须实现的接口和状态机逻辑。IBC 中间件将能够在不修改应用或核心 IBC 的前提下,对应用功能进行任意扩展。

动机

IBC 应用被设计为自包含模块,通过一组与核心 IBC 处理程序交互的接口来实现各自的应用特定逻辑。而这些核心 IBC 处理程序则被设计为在保证 IBC 正确性属性(传输、认证、有序性)的同时,将所有应用特定处理委托给 IBC 应用模块。然而,在某些情况下,一些功能可能会被许多应用所需要,但并不适合放入核心 IBC。对此最典型的例子就是通用手续费支付协议。大多数应用都会希望选择接入一种协议,用于激励中继器在其通道上中继数据包。然而,也有一些应用可能不希望启用这一特性,还有一些则希望实现自定义的手续费处理器。 如果没有中间件方案,开发者就必须在两种方式之间做选择:要么将这一扩展放入每个相关应用的应用逻辑中;要么将这部分逻辑放入核心 IBC。将其放入每个应用中既重复又容易出错。将逻辑放入核心 IBC,则要求所有应用都选择接入,并且会破坏核心 IBC(TAO)与应用之间的抽象边界。随着扩展数量增加,这两种做法都不具备可扩展性,因为它们只会导致应用或核心 IBC 处理程序中的代码膨胀。 中间件允许开发者将这些扩展定义为独立模块,并包裹在基础应用之上。因此,中间件可以执行自身的自定义逻辑,并将数据传递给应用,使应用在无需感知中间件存在的情况下运行其自身逻辑。这使得应用和中间件都可以实现各自隔离的逻辑,同时仍能作为单一数据包流的一部分运行。

定义

Middleware:一种自包含模块,在数据包执行期间位于核心 IBC 与底层 IBC 应用之间。核心 IBC 与底层应用之间的所有消息都必须经过中间件流转,中间件可以执行自身的自定义逻辑。 Underlying Application:底层应用是与当前中间件直接连接的应用。这个底层应用本身也可能是中间件,并与某个基础应用形成链式连接。 Base Application:基础应用是不包含任何中间件的 IBC 应用。它可以被 0 个或多个中间件嵌套,从而形成应用栈。 Application Stack (or stack):栈是连接到核心 IBC 的完整应用逻辑集合(中间件 + 基础应用)。一个栈可以只是基础应用,也可以是一系列嵌套基础应用的中间件。

期望属性

  • 中间件支持对应用逻辑进行任意扩展
  • 中间件可以任意嵌套,以创建应用扩展链
  • 核心 IBC 无需修改
  • 基础应用逻辑无需修改

技术规范

通用设计

为了能够作为 IBC 中间件运行,一个模块必须实现 IBC 应用回调,并将预处理后的数据传递给嵌套应用。它还必须实现 WriteAcknowledgement 和 SendPacket,因为终端应用会调用它们,以便在将数据继续传递给核心 ibc 之前,对信息进行后处理。 在嵌套一个应用时,模块必须确保它在核心 IBC 与应用之间双向通信路径的中间。开发者应通过将顶层模块直接注册到 IBC 路由器来实现这一点(而不是注册任何嵌套应用)。反过来,嵌套应用只能获得中间件的 WriteAcknowledgement 和 SendPacket 访问能力,而不能直接访问核心 IBC 处理程序。 此外,中间件还必须确保应用逻辑能够在不受嵌套中间件干扰的情况下执行其自身的版本协商。为此,中间件会将版本格式化为一个 JSON 编码字符串,其中包含中间件版本和应用版本(以及可能的其他自定义参数字段)。如果应用栈由多个包裹基础应用的中间件构成,那么应用版本本身也可以是一个 JSON 编码字符串,其中可能进一步包含更多中间件和应用版本。版本字符串格式如下:
{
    "<middleware_version_key>": "<middleware_version_value>",
    "app_version": "<application_version_value>",
    // ... other custom parameter fields
}
JSON 结构中的 <middleware_version_key> 键应替换为对应中间件实际使用的键名(例如,ICS-29 fee middleware 使用 fee_version)。 在应用回调中,中间件可以反序列化版本字符串,并获取中间件版本和应用版本。它必须基于 <middleware_version_value> 执行自己的版本协商,然后将 <application_version_value> 交给嵌套应用的回调。只有当该中间件预期在对手方栈的同一层级存在兼容的对手方中间件时,这一点才相关。仅在通道单侧执行的中间件绝不能修改通道版本。 每个应用栈都必须向核心 IBC 预留其自身唯一的端口。因此,具有相同基础应用的两个栈必须绑定到不同的端口。

接口

// Middleware implements the ICS26 Module interface
interface Middleware extends ICS26Module {
    // middleware has access to an underlying application which may be wrapped 
    // by more middleware.
    app: ICS26Module
    // middleware has access to ICS4Wrapper which may be core IBC Channel Handler 
    // or a higher-level middleware that wraps this middleware.
    ics4Wrapper: ICS4Wrapper 
}
// This is implemented by ICS4 and all middleware that are wrapping base application.
// The base application will call `sendPacket` or `writeAcknowledgement` of the
// middleware directly above them which will call the next middleware until it reaches
// the core IBC handler.
interface ICS4Wrapper {
    sendPacket(
      capability: CapabilityKey,
      sourcePort: Identifier,
      sourceChannel: Identifier,
      timeoutHeight: Height,
      timeoutTimestamp: uint64,
      data: bytes): uint64
    writeAcknowledgement(packet: Packet, ack: Acknowledgement)
}

握手回调

function onChanOpenInit(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  version: string): (version: string, err: Error) {
    if version != "" {
        // try to unmarshal JSON-encoded version string and pass 
        // the app-specific version to app callback.
        // otherwise, pass version directly to app callback.
        metadata, err = UnmarshalJSON(version)
        if err != nil {
            // call the underlying application's onChanOpenInit callback
            return app.onChanOpenInit(
                order,
                connectionHops,
                portIdentifier,
                channelIdentifier,
                counterpartyPortIdentifier,
                counterpartyChannelIdentifier,
                version,
            )
        }
    } else {
        metadata = {
            // set middleware version to default value
            middlewareVersion: defaultMiddlewareVersion,
            // allow application to return its default version
            appVersion: "",
        }
    }

    doCustomLogic()
    
    // call the underlying application's OnChanOpenInit callback.
    // if the version string is empty, OnChanOpenInit is expected to return
    // a default version string representing the version(s) it supports
    appVersion, err = app.OnChanOpenInit(
        order,
        connectionHops,
        portIdentifier,
        channelIdentifier,
        counterpartyPortIdentifier,
        counterpartyChannelIdentifier,
        metadata.appVersion, // note we only pass app version here
    )
    abortTransactionUnless(err != nil)

    // a new version string is constructed with the app version returned 
    // by the underlying application, in case it is different than the 
    // one passed by the caller
    metadata = {
        // note this should have a different field name specific to middleware
        middlewareVersion: metadata.middlewareVersion,
        appVersion: appVersion,
    }

    return MarshallJSON(metadata), nil
}

function onChanOpenTry(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  counterpartyVersion: string): (version: string, err: Error) {
    // try to unmarshal JSON-encoded version string and pass 
    // the app-specific version to app callback.
    // otherwise, pass version directly to app callback.
    cpMetadata, err = UnmarshalJSON(counterpartyVersion)
    if err != nil {
        // call the underlying application's OnChanOpenTry callback
        return app.onChanOpenTry(
            order,
            connectionHops,
            portIdentifier,
            channelIdentifier,
            counterpartyPortIdentifier,
            counterpartyChannelIdentifier,
            counterpartyVersion,
        )
    }

    // select mutually compatible middleware version
    if !isCompatible(cpMetadata.middlewareVersion) {
        return "", error
    }
    middlewareVersion = selectMiddlewareVersion(cpMetadata.middlewareVersion)

    doCustomLogic()

    // call the underlying application's OnChanOpenTry callback
    appVersion, err = app.OnChanOpenTry(
        order,
        connectionHops,
        portIdentifier,
        channelIdentifier,
        counterpartyPortIdentifier,
        counterpartyChannelIdentifier,
        cpMetadata.appVersion, // note we only pass counterparty app version here
    )
    abortTransactionUnless(err != nil)

    // a new version string is constructed with the final middleware version
    // that is selected and the app version returned by the underlying
    // application (which may be different than the one passed by the caller)
    metadata = {
        // note this should have a different field name specific to middleware
        middlewareVersion: middlewareVersion,
        appVersion: appVersion,
    }

    return MarshalJSON(metadata), nil
}

function onChanOpenAck(
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  counterpartyVersion: string) {
    cpMetadata, err = UnmarshalJSON(counterpartyVersion)
    if err != nil {
        // call the underlying application's OnChanOpenAck callback
        return app.onChanOpenAck(
            portIdentifier, 
            channelIdentifier, 
            counterpartyChannelIdentifier,
            counterpartyVersion,
        )
    }

    if !isSupported(cpMetadata.middlewareVersion) {
        return error
    } 
    doCustomLogic()
    
    // call the underlying application's OnChanOpenAck callback
    return app.onChanOpenAck(
        portIdentifier, 
        channelIdentifier, 
        counterpartyChannelIdentifier,
        cpMetadata.appVersion,
    )
}

function onChanOpenConfirm(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    doCustomLogic()
    app.OnChanOpenConfirm(portIdentifier, channelIdentifier)
}
注:不需要与远端栈上的对手方中间件进行协商的中间件,不会实现版本反序列化和协商逻辑,而只会在这些回调中执行自身的自定义逻辑,而不依赖对手方具有类似行为。

数据包回调

function onRecvPacket(packet: Packet, relayer: string): bytes {
    doCustomLogic()

    app_acknowledgement = app.onRecvPacket(packet, relayer)

    // middleware may modify ack
    ack = doCustomLogic(app_acknowledgement)
   
    return marshal(ack)
}

function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) {
    doCustomLogic()

    // middleware may modify ack
    app_ack = getAppAcknowledgement(acknowledgement)

    app.onAcknowledgePacket(packet, app_ack, relayer)

    doCustomLogic()
}

function onTimeoutPacket(packet: Packet, relayer: string) {
    doCustomLogic()

    app.onTimeoutPacket(packet, relayer)

    doCustomLogic()
}

function onTimeoutPacketClose(packet: Packet, relayer: string) {
    doCustomLogic()

    app.onTimeoutPacketClose(packet, relayer)

    doCustomLogic()
}
注:对于 ICS-26 中定义的所有 IBC 模块回调,中间件都可以对底层应用数据进行前处理和后处理。

ICS-4 包装器

function writeAcknowledgement(
  packet: Packet,
  acknowledgement: bytes) {
    // middleware may modify acknowledgement
    ack_bytes = doCustomLogic(acknowledgement)

    return ics4Wrapper.writeAcknowledgement(packet, ack_bytes)
}
function sendPacket(
  capability: CapabilityKey,
  sourcePort: Identifier,
  sourceChannel: Identifier,
  timeoutHeight: Height,
  timeoutTimestamp: uint64,
  app_data: bytes): uint64 {
    // middleware may modify packet
    data = doCustomLogic(app_data)

    return ics4Wrapper.sendPacket(
      capability,
      sourcePort,
      sourceChannel,
      timeoutHeight,
      timeoutTimestamp,
      data)
}

用户交互

当中间件需要某些用户输入才能修改来自底层应用的出站数据包消息时,中间件必须在接收到该底层应用的数据包消息之前,从用户处获取这些信息。随后,它必须自行对用户输入进行认证,并确保提供给中间件的用户输入与正确的出站数据包消息相匹配。中间件可以通过要求发送给中间件的用户输入与发送给底层应用的数据包消息以原子方式提交,并按照从最外层中间件到底层应用的顺序进行处理,来实现这一点。

安全模型

如上所示,IBC 中间件可以任意修改来自底层应用的任何入站或出站数据。因此,开发者不应在其应用栈中使用任何不受信任的中间件。

向后兼容性

中间件方法是一种当前 IBC 已经支持的设计模式。本 ICS 旨在为 IBC 中间件标准化一种特定的设计模式。不需要对 IBC 核心或任何现有应用做出修改。

向前兼容性

不适用。

示例实现

  • 按照 ICS 30 设计模式实现的 Go 版 ICS 29 可在 ibc-go 仓库 中找到。

历史

2021 年 6 月 22 日 - 提交草案 2022 年 7 月 6 日 - 根据实现中的最新变更更新

版权

此处所有内容均依据 Apache 2.0 许可。

Synopsis

This standard documents specifies the interfaces and state machine logic that a module must implement in order to act as middleware between core IBC and an underlying application(s). IBC Middleware will enable arbitrary extensions to an application’s functionality without requiring changes to the application or core IBC.

Motivation

IBC applications are designed to be self-contained modules that implement their own application-specific logic through a set of interfaces with the core IBC handlers. These core IBC handlers, in turn, are designed to enforce the correctness properties of IBC (transport, authentication, ordering) while delegating all application-specific handling to the IBC application modules. However, there are cases where some functionality may be desired by many applications, yet not appropriate to place in core IBC. The most prescient example of this, is the generalized fee payment protocol. Most applications will want to opt in to a protocol that incentivizes relayers to relay packets on their channel. However, some may not wish to enable this feature and yet others will want to implement their own custom fee handler. Without a middleware approach, developers must choose whether to place this extension in application logic inside each relevant application; or place the logic in core IBC. Placing it in each application is redundant and prone to error. Placing the logic in core IBC requires an opt-in from all applications and violates the abstraction barrier between core IBC (TAO) and the application. Either case is not scalable as the number of extensions increase, since this must either increase code bloat in applications or core IBC handlers. Middleware allows developers to define the extensions as separate modules that can wrap over the base application. This middleware can thus perform its own custom logic, and pass data into the application so that it may run its logic without being aware of the middleware’s existence. This allows both the application and the middleware to implement its own isolated logic while still being able to run as part of a single packet flow.

Definitions

Middleware: A self-contained module that sits between core IBC and an underlying IBC application during packet execution. All messages between core IBC and underlying application must flow through middleware, which may perform its own custom logic. Underlying Application: An underlying application is the application that is directly connected to the middleware in question. This underlying application may itself be middleware that is chained to a base application. Base Application: A base application is an IBC application that does not contain any middleware. It may be nested by 0 or multiple middleware to form an application stack. Application Stack (or stack): A stack is the complete set of application logic (middleware(s) + base application) that gets connected to core IBC. A stack may be just a base application, or it may be a series of middlewares that nest a base application.

Desired Properties

  • Middleware enables arbitrary extensions of application logic
  • Middleware can be arbitrarily nested to create a chain of app extensions
  • Core IBC does not need to change
  • Base Application logic does not need to change

Technical Specification

General Design

In order to function as IBC Middleware, a module must implement the IBC application callbacks and pass along the pre-processed data to the nested application. It must also implement WriteAcknowledgement and SendPacket, which will be called by the end application, so that it may post-process the information before passing data along to core ibc. When nesting an application, the module must make sure that it is in the middle of communication between core IBC and the application in both directions. Developers should do this by registering the top-level module directly with the IBC router (not any nested applications). The nested applications in turn, must be given access only to the middleware’s WriteAcknowledgement and SendPacket rather than to the core IBC handlers directly. Additionally, the middleware must take care to ensure that the application logic can execute its own version negotiation without interference from the nesting middleware. In order to do this, the middleware will format the version in a JSON-encoded string containing the middleware version and the application version (and potentially also other custom parameter fields). 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 string is as follows:
{
    "<middleware_version_key>": "<middleware_version_value>",
    "app_version": "<application_version_value>",
    // ... other custom parameter fields
}
The <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 for ICS-29 fee middleware). In the application callbacks, the middleware can unmarshal the version string and retrieve the middleware and application versions. It must do its own version negotiation on <middleware_version_value> and then hand over <application_version_value> to the nested application’s callback. This is only relevant if the middleware expects a compatible counterparty middleware at the same level on the counterparty stack. Middleware that only executes on a single side of the channel MUST NOT modify the channel version. Each application stack must reserve its own unique port with core IBC. Thus two stacks with the same base application must bind to separate ports.

Interfaces

// Middleware implements the ICS26 Module interface
interface Middleware extends ICS26Module {
    // middleware has access to an underlying application which may be wrapped 
    // by more middleware.
    app: ICS26Module
    // middleware has access to ICS4Wrapper which may be core IBC Channel Handler 
    // or a higher-level middleware that wraps this middleware.
    ics4Wrapper: ICS4Wrapper 
}
// This is implemented by ICS4 and all middleware that are wrapping base application.
// The base application will call `sendPacket` or `writeAcknowledgement` of the
// middleware directly above them which will call the next middleware until it reaches
// the core IBC handler.
interface ICS4Wrapper {
    sendPacket(
      capability: CapabilityKey,
      sourcePort: Identifier,
      sourceChannel: Identifier,
      timeoutHeight: Height,
      timeoutTimestamp: uint64,
      data: bytes): uint64
    writeAcknowledgement(packet: Packet, ack: Acknowledgement)
}

Handshake Callbacks

function onChanOpenInit(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  version: string): (version: string, err: Error) {
    if version != "" {
        // try to unmarshal JSON-encoded version string and pass 
        // the app-specific version to app callback.
        // otherwise, pass version directly to app callback.
        metadata, err = UnmarshalJSON(version)
        if err != nil {
            // call the underlying application's onChanOpenInit callback
            return app.onChanOpenInit(
                order,
                connectionHops,
                portIdentifier,
                channelIdentifier,
                counterpartyPortIdentifier,
                counterpartyChannelIdentifier,
                version,
            )
        }
    } else {
        metadata = {
            // set middleware version to default value
            middlewareVersion: defaultMiddlewareVersion,
            // allow application to return its default version
            appVersion: "",
        }
    }

    doCustomLogic()
    
    // call the underlying application's OnChanOpenInit callback.
    // if the version string is empty, OnChanOpenInit is expected to return
    // a default version string representing the version(s) it supports
    appVersion, err = app.OnChanOpenInit(
        order,
        connectionHops,
        portIdentifier,
        channelIdentifier,
        counterpartyPortIdentifier,
        counterpartyChannelIdentifier,
        metadata.appVersion, // note we only pass app version here
    )
    abortTransactionUnless(err != nil)

    // a new version string is constructed with the app version returned 
    // by the underlying application, in case it is different than the 
    // one passed by the caller
    metadata = {
        // note this should have a different field name specific to middleware
        middlewareVersion: metadata.middlewareVersion,
        appVersion: appVersion,
    }

    return MarshallJSON(metadata), nil
}

function onChanOpenTry(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  counterpartyVersion: string): (version: string, err: Error) {
    // try to unmarshal JSON-encoded version string and pass 
    // the app-specific version to app callback.
    // otherwise, pass version directly to app callback.
    cpMetadata, err = UnmarshalJSON(counterpartyVersion)
    if err != nil {
        // call the underlying application's OnChanOpenTry callback
        return app.onChanOpenTry(
            order,
            connectionHops,
            portIdentifier,
            channelIdentifier,
            counterpartyPortIdentifier,
            counterpartyChannelIdentifier,
            counterpartyVersion,
        )
    }

    // select mutually compatible middleware version
    if !isCompatible(cpMetadata.middlewareVersion) {
        return "", error
    }
    middlewareVersion = selectMiddlewareVersion(cpMetadata.middlewareVersion)

    doCustomLogic()

    // call the underlying application's OnChanOpenTry callback
    appVersion, err = app.OnChanOpenTry(
        order,
        connectionHops,
        portIdentifier,
        channelIdentifier,
        counterpartyPortIdentifier,
        counterpartyChannelIdentifier,
        cpMetadata.appVersion, // note we only pass counterparty app version here
    )
    abortTransactionUnless(err != nil)

    // a new version string is constructed with the final middleware version
    // that is selected and the app version returned by the underlying
    // application (which may be different than the one passed by the caller)
    metadata = {
        // note this should have a different field name specific to middleware
        middlewareVersion: middlewareVersion,
        appVersion: appVersion,
    }

    return MarshalJSON(metadata), nil
}

function onChanOpenAck(
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  counterpartyVersion: string) {
    cpMetadata, err = UnmarshalJSON(counterpartyVersion)
    if err != nil {
        // call the underlying application's OnChanOpenAck callback
        return app.onChanOpenAck(
            portIdentifier, 
            channelIdentifier, 
            counterpartyChannelIdentifier,
            counterpartyVersion,
        )
    }

    if !isSupported(cpMetadata.middlewareVersion) {
        return error
    } 
    doCustomLogic()
    
    // call the underlying application's OnChanOpenAck callback
    return app.onChanOpenAck(
        portIdentifier, 
        channelIdentifier, 
        counterpartyChannelIdentifier,
        cpMetadata.appVersion,
    )
}

function onChanOpenConfirm(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    doCustomLogic()
    app.OnChanOpenConfirm(portIdentifier, channelIdentifier)
}
NOTE: Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version unmarshaling and negotiation, and will simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly.

Packet Callbacks

function onRecvPacket(packet: Packet, relayer: string): bytes {
    doCustomLogic()

    app_acknowledgement = app.onRecvPacket(packet, relayer)

    // middleware may modify ack
    ack = doCustomLogic(app_acknowledgement)
   
    return marshal(ack)
}

function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) {
    doCustomLogic()

    // middleware may modify ack
    app_ack = getAppAcknowledgement(acknowledgement)

    app.onAcknowledgePacket(packet, app_ack, relayer)

    doCustomLogic()
}

function onTimeoutPacket(packet: Packet, relayer: string) {
    doCustomLogic()

    app.onTimeoutPacket(packet, relayer)

    doCustomLogic()
}

function onTimeoutPacketClose(packet: Packet, relayer: string) {
    doCustomLogic()

    app.onTimeoutPacketClose(packet, relayer)

    doCustomLogic()
}
NOTE: Middleware may do pre- and post-processing on underlying application data for all IBC Module callbacks defined in ICS-26.

ICS-4 Wrappers

function writeAcknowledgement(
  packet: Packet,
  acknowledgement: bytes) {
    // middleware may modify acknowledgement
    ack_bytes = doCustomLogic(acknowledgement)

    return ics4Wrapper.writeAcknowledgement(packet, ack_bytes)
}
function sendPacket(
  capability: CapabilityKey,
  sourcePort: Identifier,
  sourceChannel: Identifier,
  timeoutHeight: Height,
  timeoutTimestamp: uint64,
  app_data: bytes): uint64 {
    // middleware may modify packet
    data = doCustomLogic(app_data)

    return ics4Wrapper.sendPacket(
      capability,
      sourcePort,
      sourceChannel,
      timeoutHeight,
      timeoutTimestamp,
      data)
}

User Interaction

In the case where the middleware requires some user input in order to modify the outgoing packet messages from the underlying application, the middleware MUST get this information from the user before it receives the packet message from the underlying application. It must then do its own authentication of the user input, and ensure that the user input provided to the middleware is matched to the correct outgoing packet message. The middleware MAY accomplish this by requiring that the user input to middleware, and packet message to underlying application are sent atomically and ordered from outermost middleware to base application.

Security Model

As seen above, IBC middleware may arbitrarily modify any incoming or outgoing data from an underlying application. Thus, developers should not use any untrusted middleware in their application stacks.

Backwards Compatibility

The Middleware approach is a design pattern already enabled by current IBC. This ICS seeks to standardize a particular design pattern for IBC middleware. There are no changes required to core IBC or any existing application.

Forwards Compatibility

Not applicable.

Example Implementations

  • Implementation of ICS 29 in Go following ICS 30 design pattern can be found in ibc-go repository.

History

June 22, 2021 - Draft submitted July 6, 2022 - Update with latest changes from implementation All content herein is licensed under Apache 2.0.