了解 Callbacks Middleware 是什么,以及如何构建利用 Callbacks Middleware 功能的自定义模块。

什么是 Callbacks Middleware?

IBC 在设计时就考虑了核心 IBC 与 IBC 应用之间的回调机制。IBC 应用会向核心 IBC 发送数据包,并在该数据包生命周期的每一个阶段接收回调。这使得 IBC 应用能够构建在核心 IBC 之上,并在数据包生命周期事件发生时执行自定义逻辑(例如为 ICS-20 解除代币托管)。 这种设计对于与 IBC 应用交互的链下用户来说运行良好。然而,现在我们看到越来越多的需求,希望二级应用(例如智能合约、模块)能够作为其状态机逻辑的一部分调用 IBC 应用,并在数据包生命周期事件发生时执行后续操作。 Callbacks Middleware 通过允许底层 IBC 应用的数据包为生命周期事件向二级应用注册回调,来提供这项能力。当对应的数据包生命周期事件发生时,这些回调会由 Callbacks Middleware 执行。 经过大量讨论后,这一设计被扩展为一份 ADR,而 Callbacks Middleware 正是该 ADR 的一个实现。

概念

Callbacks Middleware 在设计时主要考虑了智能合约场景,但它也可以被任何希望让 IBC 数据包回调进入自身的二级应用使用。可以将 Callbacks Middleware 理解为核心 IBC 与二级应用之间的一座桥梁。 我们使用以下定义:
  • Underlying IBC application:被 Callbacks Middleware 包裹的 IBC 应用。这是真正与核心 IBC 交互、发送和接收数据包生命周期事件的 IBC 应用。例如 transfer 模块,或 ICA controller 子模块。
  • IBC Actor:IBC Actor 是能够在底层 IBC 应用上发起数据包的链上或链下实体。例如,智能合约、链下用户,或发送 transfer 数据包的模块,都属于 IBC Actor。
  • Secondary application:在数据包生命周期事件发生时,由 Callbacks Middleware 调用的应用。它是直接从 Callbacks Middleware 模块接收回调的应用。例如 x/wasm 模块。
  • Callback Actor:在二级应用中注册、用于接收回调的链上智能合约或模块。例如 Wasm 智能合约(由 x/wasm 模块进行访问控制)。注意,Callback Actor 不一定与 IBC Actor 是同一个实体。例如,链下用户可以在底层 IBC 应用上发起一个数据包,但 Callback Actor 可能是一个智能合约。二级应用可能需要检查 IBC Actor 是否有权调用 Callback Actor,例如通过检查 IBC Actor 是否与 Callback Actor 相同。
  • Callback Address:Callback Actor 的地址。这是当数据包生命周期事件发生时,二级应用将要调用的地址。例如 Wasm 智能合约的地址。
  • Maximum gas limit:Callbacks Middleware 允许二级应用在执行其自定义逻辑时使用的最大 gas 数量。
  • User defined gas limit:IBC Actor 希望允许二级应用在执行其自定义逻辑时使用的 gas 数量。这是 IBC Actor 在向底层 IBC 应用发送数据包时指定的 gas 上限。它不能大于最大 gas 限制。
可以将二级应用视为 Callbacks Middleware 与 Callback Actor 之间的桥梁。二级应用负责在数据包生命周期事件发生时执行 Callback Actor 的自定义逻辑。二级应用还负责检查 IBC Actor 是否有权调用 Callback Actor。 请注意,IBC Actor、Secondary Application 和 Callback Actor 也可能全部是同一个实体。在这种情况下,Callback Address 应当是该二级应用的模块地址。 下图展示了典型的 RecvPacket、AcknowledgementPacket 和 TimeoutPacket 执行流程: callbacks-middleware 下图展示了典型的 SendPacket 和 WriteAcknowledgement 执行流程: callbacks-middleware

已知限制

  • 回调总是在底层 IBC 应用执行完其逻辑之后才会执行。
  • 最大 gas 限制是在接线配置时手动硬编码的。若要修改最大 gas 限制,需要一次协调一致的升级。
  • 接收数据包回调不会将 relayer 地址传递给二级应用。这是为了能够对同步确认和异步确认复用同一种回调。
  • 接收数据包回调不会传递 IBC Actor 的地址,因为 IBC Actor 位于对手链上,不能被信任。

Learn about what the Callbacks Middleware is, and how to build custom modules that utilize the Callbacks Middleware functionality

What is the Callbacks Middleware?

IBC was designed with callbacks between core IBC and IBC applications. IBC apps would send a packet to core IBC, and receive a callback on every step of that packet’s lifecycle. This allows IBC applications to be built on top of core IBC, and to be able to execute custom logic on packet lifecycle events (e.g. unescrow tokens for ICS-20). This setup worked well for off-chain users interacting with IBC applications. However, we are now seeing the desire for secondary applications (e.g. smart contracts, modules) to call into IBC apps as part of their state machine logic and then do some actions on packet lifecycle events. The Callbacks Middleware allows for this functionality by allowing the packets of the underlying IBC applications to register callbacks to secondary applications for lifecycle events. These callbacks are then executed by the Callbacks Middleware when the corresponding packet lifecycle event occurs. After much discussion, the design was expanded to an ADR, and the Callbacks Middleware is an implementation of that ADR.

Concepts

Callbacks Middleware was built with smart contracts in mind, but can be used by any secondary application that wants to allow IBC packets to call into it. Think of the Callbacks Middleware as a bridge between core IBC and a secondary application. We have the following definitions:
  • Underlying IBC application: The IBC application that is wrapped by the Callbacks Middleware. This is the IBC application that is actually sending and receiving packet lifecycle events from core IBC. For example, the transfer module, or the ICA controller submodule.
  • IBC Actor: IBC Actor is an on-chain or off-chain entity that can initiate a packet on the underlying IBC application. For example, a smart contract, an off-chain user, or a module that sends a transfer packet are all IBC Actors.
  • Secondary application: The application that is being called into by the Callbacks Middleware for packet lifecycle events. This is the application that is receiving the callback directly from the Callbacks Middleware module. For example, the x/wasm module.
  • Callback Actor: The on-chain smart contract or module that is registered to receive callbacks from the secondary application. For example, a Wasm smart contract (gatekeeped by the x/wasm module). Note that the Callback Actor is not necessarily the same as the IBC Actor. For example, an off-chain user can initiate a packet on the underlying IBC application, but the Callback Actor could be a smart contract. The secondary application may want to check that the IBC Actor is allowed to call into the Callback Actor, for example, by checking that the IBC Actor is the same as the Callback Actor.
  • Callback Address: Address of the Callback Actor. This is the address that the secondary application will call into when a packet lifecycle event occurs. For example, the address of the Wasm smart contract.
  • Maximum gas limit: The maximum amount of gas that the Callbacks Middleware will allow the secondary application to use when it executes its custom logic.
  • User defined gas limit: The amount of gas that the IBC Actor wants to allow the secondary application to use when it executes its custom logic. This is the gas limit that the IBC Actor specifies when it sends a packet to the underlying IBC application. This cannot be greater than the maximum gas limit.
Think of the secondary application as a bridge between the Callbacks Middleware and the Callback Actor. The secondary application is responsible for executing the custom logic of the Callback Actor when a packet lifecycle event occurs. The secondary application is also responsible for checking that the IBC Actor is allowed to call into the Callback Actor. Note that it is possible that the IBC Actor, Secondary Application, and Callback Actor are all the same entity. In which case, the Callback Address should be the secondary application’s module address. The following diagram shows how a typical RecvPacket, AcknowledgementPacket, and TimeoutPacket execution flow would look like: callbacks-middleware And the following diagram shows how a typical SendPacket and WriteAcknowledgement execution flow would look like: callbacks-middleware

Known Limitations

  • Callbacks are always executed after the underlying IBC application has executed its logic.
  • Maximum gas limit is hardcoded manually during wiring. It requires a coordinated upgrade to change the maximum gas limit.
  • The receive packet callback does not pass the relayer address to the secondary application. This is so that we can use the same callback for both synchronous and asynchronous acknowledgements.
  • The receive packet callback does not pass IBC Actor’s address, this is because the IBC Actor lives in the counterparty chain and cannot be trusted.