回调中间件要求底层 IBC 应用和次级应用实现某些接口。如果你只是将回调中间件接入现有的 IBC 应用栈以及诸如 icacontroller 和 x/wasm 之类的次级应用,可以跳过本节。

开发底层 IBC 应用所需的接口

PacketDataUnmarshaler

/ PacketDataUnmarshaler defines an optional interface which allows a middleware to
/ request the packet data to be unmarshaled by the base application.
type PacketDataUnmarshaler interface {
  / UnmarshalPacketData unmarshals the packet data into a concrete type
  / ctx, portID, channelID are provided as arguments, so that (if needed)
  / the packet data can be unmarshaled based on the channel version.
  / The version of the underlying app is also returned.
  UnmarshalPacketData(ctx sdk.Context, portID, channelID string, bz []byte) (interface{
}, string, error)
}
回调中间件要求底层 ibc 应用实现 PacketDataUnmarshaler 接口,以便它能够将数据包数据字节反序列化为合适的数据包数据类型。这样就可以使用该数据包数据类型所实现的接口函数。这里预期数据包数据类型会实现 PacketDataProvider 接口(见下文),该接口用于解析当前以 JSON 字符串形式存储在 transfer 和 ica 数据包 memo 字段中的回调数据。可参考其在 transfer 和 icacontroller 模块中的实现。 如果底层应用本身也是一个中间件,那么它可以通过简单地将函数调用传递给其底层应用来实现该接口。可参考 fee middleware 中的实现。

PacketDataProvider

/ PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application.
/ An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information.
/ A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application.
/ This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time.
type PacketDataProvider interface {
  / GetCustomPacketData returns the packet data held on behalf of another application.
  / The name the information is stored under should be provided as the key.
  / If no custom packet data exists for the key, nil should be returned.
  GetCustomPacketData(key string)

interface{
}
}
回调中间件还要求底层 ibc 应用的数据包数据类型实现 PacketDataProvider 接口。该接口用于从数据包数据中获取回调数据(对于 transfer 和 ica,使用的是 memo 字段)。例如,可参考其在 transfer 模块中的实现。 由于中间件没有数据包类型,因此不需要实现该接口。

PacketData

/ PacketData defines an optional interface which an application's packet data structure may implement.
type PacketData interface {
  / GetPacketSender returns the sender address of the packet data.
  / If the packet sender is unknown or undefined, an empty string should be returned.
  GetPacketSender(sourcePortID string)

string
}
PacketData 是一个可选接口,底层 ibc 应用的数据包数据类型可以实现它。它用于从数据包数据中获取数据包发送方地址。回调中间件会在源端回调期间使用该接口获取数据包发送方地址,并将其传递给回调函数。如果未实现该接口,则回调中间件会将空字符串作为发送方地址传递。例如,可参考其在 transfer 和 ica 模块中的实现。 添加该接口是为了让次级应用在需要时能够获取数据包发送方地址,以执行自定义授权逻辑。 由于中间件没有数据包类型,因此不需要实现该接口。

开发次级应用所需的接口

ContractKeeper

callbacks 中间件要求次级应用实现 ContractKeeper 接口。合约 keeper 会在数据包生命周期的每个阶段被调用。当数据包被发送时,如果提供了回调信息,合约 keeper 将通过 IBCSendPacketCallback 被调用。这使合约 keeper 能够在提供回调信息时阻止数据包发送,例如发送者未被授权基于给定信息执行回调。如果数据包发送成功,则目标链上的合约 keeper(如果存在)会在数据包被接收且确认写入后被调用,这会通过 IBCReceivePacketCallback 发生。在数据包生命周期的最后阶段,处理确认或超时时,源链合约 keeper 会通过 IBCOnAcknowledgementPacket 或 IBCOnTimeoutPacket 被调用。数据包一旦发送,只要 relayer 将 gas 限制设置为大于或等于所需的 CommitGasLimit,生命周期中的每个步骤都可以被处理。在回调中执行的状态变更只有在成功执行后才会被提交。
/ ContractKeeper defines the entry points exposed to the VM module which invokes a smart contract
type ContractKeeper interface {
	/ IBCSendPacketCallback is called in the source chain when a PacketSend is executed. The
	/ packetSenderAddress is determined by the underlying module, and may be empty if the sender is
	/ unknown or undefined. The contract is expected to handle the callback within the user defined
	/ gas limit, and handle any errors, or panics gracefully.
	/ This entry point is called with a cached context. If an error is returned, then the changes in
	/ this context will not be persisted, and the error will be propagated to the underlying IBC
	/ application, resulting in a packet send failure.
	/
	/ Implementations are provided with the packetSenderAddress and MAY choose to use this to perform
	/ validation on the origin of a given packet. It is recommended to perform the same validation
	/ on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This
	/ defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks.
	/
	/ The version provided is the base application version for the given packet send. This allows
	/ contracts to determine how to unmarshal the packetData.
	IBCSendPacketCallback(
		cachedCtx sdk.Context,
		sourcePort string,
		sourceChannel string,
		timeoutHeight clienttypes.Height,
		timeoutTimestamp uint64,
		packetData []byte,
		contractAddress,
		packetSenderAddress string,
		version string,
	)

error
	/ IBCOnAcknowledgementPacketCallback is called in the source chain when a packet acknowledgement
	/ is received. The packetSenderAddress is determined by the underlying module, and may be empty if
	/ the sender is unknown or undefined. The contract is expected to handle the callback within the
	/ user defined gas limit, and handle any errors, or panics gracefully.
	/ This entry point is called with a cached context. If an error is returned, then the changes in
	/ this context will not be persisted, but the packet lifecycle will not be blocked.
	/
	/ Implementations are provided with the packetSenderAddress and MAY choose to use this to perform
	/ validation on the origin of a given packet. It is recommended to perform the same validation
	/ on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This
	/ defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks.
	/
	/ The version provided is the base application version for the given packet send. This allows
	/ contracts to determine how to unmarshal the packetData.
	IBCOnAcknowledgementPacketCallback(
		cachedCtx sdk.Context,
		packet channeltypes.Packet,
		acknowledgement []byte,
		relayer sdk.AccAddress,
		contractAddress,
		packetSenderAddress string,
		version string,
	)

error
	/ IBCOnTimeoutPacketCallback is called in the source chain when a packet is not received before
	/ the timeout height. The packetSenderAddress is determined by the underlying module, and may be
	/ empty if the sender is unknown or undefined. The contract is expected to handle the callback
	/ within the user defined gas limit, and handle any error, out of gas, or panics gracefully.
	/ This entry point is called with a cached context. If an error is returned, then the changes in
	/ this context will not be persisted, but the packet lifecycle will not be blocked.
	/
	/ Implementations are provided with the packetSenderAddress and MAY choose to use this to perform
	/ validation on the origin of a given packet. It is recommended to perform the same validation
	/ on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This
	/ defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks.
	/
	/ The version provided is the base application version for the given packet send. This allows
	/ contracts to determine how to unmarshal the packetData.
	IBCOnTimeoutPacketCallback(
		cachedCtx sdk.Context,
		packet channeltypes.Packet,
		relayer sdk.AccAddress,
		contractAddress,
		packetSenderAddress string,
		version string,
	)

error
	/ IBCReceivePacketCallback is called in the destination chain when a packet acknowledgement is written.
	/ The contract is expected to handle the callback within the user defined gas limit, and handle any errors,
	/ out of gas, or panics gracefully.
	/ This entry point is called with a cached context. If an error is returned, then the changes in
	/ this context will not be persisted, but the packet lifecycle will not be blocked.
	/
	/ The version provided is the base application version for the given packet send. This allows
	/ contracts to determine how to unmarshal the packetData.
	IBCReceivePacketCallback(
		cachedCtx sdk.Context,
		packet ibcexported.PacketI,
		ack ibcexported.Acknowledgement,
		contractAddress string,
		version string,
	)

error
}
这些是暴露给次级应用的回调入口点。次级应用应当在这些入口点中执行其自定义逻辑。callbacks 中间件将负责执行这些回调,并在需要时回滚状态。
请注意,源链回调入口点会提供 packetSenderAddress,实现方可以选择利用它对给定数据包的来源执行校验。建议在所有源链回调(SendPacket、AcknowledgePacket、TimeoutPacket)上执行相同的校验。这是一种防御性措施,可防止因 IBC 栈中 SendPacket 顺序连接错误而导致的利用。

The callbacks middleware requires certain interfaces to be implemented by the underlying IBC applications and the secondary application. If you’re simply wiring up the callbacks middleware to an existing IBC application stack and a secondary application such as icacontroller and x/wasm, you can skip this section.

Interfaces for developing the Underlying IBC Application

PacketDataUnmarshaler

/ PacketDataUnmarshaler defines an optional interface which allows a middleware to
/ request the packet data to be unmarshaled by the base application.
type PacketDataUnmarshaler interface {
  / UnmarshalPacketData unmarshals the packet data into a concrete type
  / ctx, portID, channelID are provided as arguments, so that (if needed)
  / the packet data can be unmarshaled based on the channel version.
  / The version of the underlying app is also returned.
  UnmarshalPacketData(ctx sdk.Context, portID, channelID string, bz []byte) (interface{
}, string, error)
}
The callbacks middleware requires the underlying ibc application to implement the PacketDataUnmarshaler interface so that it can unmarshal the packet data bytes into the appropriate packet data type. This allows usage of interface functions implemented by the packet data type. The packet data type is expected to implement the PacketDataProvider interface (see section below), which is used to parse the callback data that is currently stored in the packet memo field for transfer and ica packets as a JSON string. See its implementation in the transfer and icacontroller modules for reference. If the underlying application is a middleware itself, then it can implement this interface by simply passing the function call to its underlying application. See its implementation in the fee middleware for reference.

PacketDataProvider

/ PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application.
/ An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information.
/ A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application.
/ This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time.
type PacketDataProvider interface {
  / GetCustomPacketData returns the packet data held on behalf of another application.
  / The name the information is stored under should be provided as the key.
  / If no custom packet data exists for the key, nil should be returned.
  GetCustomPacketData(key string)

interface{
}
}
The callbacks middleware also requires the underlying ibc application’s packet data type to implement the PacketDataProvider interface. This interface is used to retrieve the callback data from the packet data (using the memo field in the case of transfer and ica). For example, see its implementation in the transfer module. Since middlewares do not have packet types, they do not need to implement this interface.

PacketData

/ PacketData defines an optional interface which an application's packet data structure may implement.
type PacketData interface {
  / GetPacketSender returns the sender address of the packet data.
  / If the packet sender is unknown or undefined, an empty string should be returned.
  GetPacketSender(sourcePortID string)

string
}
PacketData is an optional interface that can be implemented by the underlying ibc application’s packet data type. It is used to retrieve the packet sender address from the packet data. The callbacks middleware uses this interface to retrieve the packet sender address and pass it to the callback function during a source callback. If this interface is not implemented, then the callbacks middleware passes and empty string as the sender address. For example, see its implementation in the transfer and ica module. This interface was added so that secondary applications can retrieve the packet sender address to perform custom authorization logic if needed. Since middlewares do not have packet types, they do not need to implement this interface.

Interfaces for developing the Secondary Application

ContractKeeper

The callbacks middleware requires the secondary application to implement the ContractKeeper interface. The contract keeper will be invoked at each step of the packet lifecycle. When a packet is sent, if callback information is provided, the contract keeper will be invoked via the IBCSendPacketCallback. This allows the contract keeper to prevent packet sends when callback information is provided, for example if the sender is unauthorized to perform callbacks on the given information. If the packet send is successful, the contract keeper on the destination (if present) will be invoked when a packet has been received and the acknowledgement is written, this will occur via IBCReceivePacketCallback. At the end of the packet lifecycle, when processing acknowledgements or timeouts, the source contract keeper will be invoked either via IBCOnAcknowledgementPacket or IBCOnTimeoutPacket. Once a packet has been sent, each step of the packet lifecycle can be processed given that a relayer sets the gas limit to be more than or equal to the required CommitGasLimit. State changes performed in the callback will only be committed upon successful execution.
/ ContractKeeper defines the entry points exposed to the VM module which invokes a smart contract
type ContractKeeper interface {
	/ IBCSendPacketCallback is called in the source chain when a PacketSend is executed. The
	/ packetSenderAddress is determined by the underlying module, and may be empty if the sender is
	/ unknown or undefined. The contract is expected to handle the callback within the user defined
	/ gas limit, and handle any errors, or panics gracefully.
	/ This entry point is called with a cached context. If an error is returned, then the changes in
	/ this context will not be persisted, and the error will be propagated to the underlying IBC
	/ application, resulting in a packet send failure.
	/
	/ Implementations are provided with the packetSenderAddress and MAY choose to use this to perform
	/ validation on the origin of a given packet. It is recommended to perform the same validation
	/ on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This
	/ defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks.
	/
	/ The version provided is the base application version for the given packet send. This allows
	/ contracts to determine how to unmarshal the packetData.
	IBCSendPacketCallback(
		cachedCtx sdk.Context,
		sourcePort string,
		sourceChannel string,
		timeoutHeight clienttypes.Height,
		timeoutTimestamp uint64,
		packetData []byte,
		contractAddress,
		packetSenderAddress string,
		version string,
	)

error
	/ IBCOnAcknowledgementPacketCallback is called in the source chain when a packet acknowledgement
	/ is received. The packetSenderAddress is determined by the underlying module, and may be empty if
	/ the sender is unknown or undefined. The contract is expected to handle the callback within the
	/ user defined gas limit, and handle any errors, or panics gracefully.
	/ This entry point is called with a cached context. If an error is returned, then the changes in
	/ this context will not be persisted, but the packet lifecycle will not be blocked.
	/
	/ Implementations are provided with the packetSenderAddress and MAY choose to use this to perform
	/ validation on the origin of a given packet. It is recommended to perform the same validation
	/ on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This
	/ defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks.
	/
	/ The version provided is the base application version for the given packet send. This allows
	/ contracts to determine how to unmarshal the packetData.
	IBCOnAcknowledgementPacketCallback(
		cachedCtx sdk.Context,
		packet channeltypes.Packet,
		acknowledgement []byte,
		relayer sdk.AccAddress,
		contractAddress,
		packetSenderAddress string,
		version string,
	)

error
	/ IBCOnTimeoutPacketCallback is called in the source chain when a packet is not received before
	/ the timeout height. The packetSenderAddress is determined by the underlying module, and may be
	/ empty if the sender is unknown or undefined. The contract is expected to handle the callback
	/ within the user defined gas limit, and handle any error, out of gas, or panics gracefully.
	/ This entry point is called with a cached context. If an error is returned, then the changes in
	/ this context will not be persisted, but the packet lifecycle will not be blocked.
	/
	/ Implementations are provided with the packetSenderAddress and MAY choose to use this to perform
	/ validation on the origin of a given packet. It is recommended to perform the same validation
	/ on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This
	/ defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks.
	/
	/ The version provided is the base application version for the given packet send. This allows
	/ contracts to determine how to unmarshal the packetData.
	IBCOnTimeoutPacketCallback(
		cachedCtx sdk.Context,
		packet channeltypes.Packet,
		relayer sdk.AccAddress,
		contractAddress,
		packetSenderAddress string,
		version string,
	)

error
	/ IBCReceivePacketCallback is called in the destination chain when a packet acknowledgement is written.
	/ The contract is expected to handle the callback within the user defined gas limit, and handle any errors,
	/ out of gas, or panics gracefully.
	/ This entry point is called with a cached context. If an error is returned, then the changes in
	/ this context will not be persisted, but the packet lifecycle will not be blocked.
	/
	/ The version provided is the base application version for the given packet send. This allows
	/ contracts to determine how to unmarshal the packetData.
	IBCReceivePacketCallback(
		cachedCtx sdk.Context,
		packet ibcexported.PacketI,
		ack ibcexported.Acknowledgement,
		contractAddress string,
		version string,
	)

error
}
These are the callback entry points exposed to the secondary application. The secondary application is expected to execute its custom logic within these entry points. The callbacks middleware will handle the execution of these callbacks and revert the state if needed.
Note that the source callback entry points are provided with the packetSenderAddress and MAY choose to use this to perform validation on the origin of a given packet. It is recommended to perform the same validation on all source chain callbacks (SendPacket, AcknowledgePacket, TimeoutPacket). This defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks.