Skip to content

Commit

Permalink
docs: documentation added for callbacks middleware (backport cosmos#4370
Browse files Browse the repository at this point in the history
) (cosmos#4458)

* docs: documentation added for callbacks middleware (cosmos#4370)

* docs: added callbacks middleware overview

* docs: added interfaces.md to callbacks

* docs: added usage to callback docs

* docs: added events to callbacks docs

* docs: integration docs for callbacks added

* docs: added user defined gas limit to callbacks/usage

* docs: renamed usage.md -> end-users.md

* docs: added callbacks docs to config.js

* docs: improved interfaces slightly

* docs: added order frontmatter to markdown files

* 2-space tabs for better readability

* docs: improved callbacks/interfaces.md

* docs: improved callbacks/overview.md

* docs: improved callbacks/integration.md

* docs: improved callbacks/end-users.md

* docs: improved callbacks/interfaces.md

* docs: added callbacks diagrams

* docs: added the new diagrams to overview.md

* docs: added improvements to callback docs'

* docs: gas.md added to callbacks docs

* docs: minor grammar improvement

* docs: fixed another grammar error in callbacks

* docs: implemented review items

* code formatting and fix typo

* docs: improved the callback diagram

---------

Co-authored-by: Carlos Rodriguez <[email protected]>
(cherry picked from commit d0f7773)

* add ADR 8 files

---------

Co-authored-by: srdtrk <[email protected]>
Co-authored-by: Carlos Rodriguez <[email protected]>
  • Loading branch information
3 people authored Aug 25, 2023
1 parent 23b4a24 commit 812a364
Show file tree
Hide file tree
Showing 11 changed files with 1,239 additions and 0 deletions.
37 changes: 37 additions & 0 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,43 @@ module.exports = {
},
],
},
{
title: "Callbacks Middleware",
directory: true,
path: "/middleware",
children: [
{
title: "Overview",
directory: false,
path: "/middleware/callbacks/overview.html",
},
{
title: "Integration",
directory: false,
path: "/middleware/callbacks/integration.html",
},
{
title: "Interfaces",
directory: false,
path: "/middleware/callbacks/interfaces.html",
},
{
title: "Events",
directory: false,
path: "/middleware/callbacks/events.html",
},
{
title: "End Users",
directory: false,
path: "/middleware/callbacks/end-users.html",
},
{
title: "Gas Management",
directory: false,
path: "/middleware/callbacks/gas.html",
},
],
},
],
},
{
Expand Down
421 changes: 421 additions & 0 deletions docs/architecture/adr-008-app-caller-cbs/adr-008-app-caller-cbs.md

Large diffs are not rendered by default.

223 changes: 223 additions & 0 deletions docs/architecture/adr-008-app-caller-cbs/transfer-callback-scaffold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
# Scaffold for ICS20 Callback Middleware

The following is a very simple scaffold for middleware that implements actor callbacks for transfer channels. Since a channel does not need to be owned by a single actor, the handshake callbacks are no-ops. The packet callbacks will call into the relevant actor. For `OnRecvPacket`, this will be `data.Receiver`, and for `OnAcknowledgePacket` and `OnTimeoutPacket` this will be `data.Sender`.

The exact nature of the callbacks to the smart contract will depend on the environment in question (e.g. cosmwasm, evm). Thus the place where the callbacks to the smart contract are stubbed out and commented so that it can be completed by implementers for the specific environment they are targetting.

Implementers may wish to support callbacks to more IBC applications by adding a switch statement to unmarshal the specific packet data types they wish to support and passing them into the smart contract callback functions.

### Scaffold Middleware

```go
package callbacks

import (
sdk "github.com/cosmos/cosmos-sdk/types"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"

transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types"
clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types"
porttypes "github.com/cosmos/ibc-go/v6/modules/core/05-port/types"
"github.com/cosmos/ibc-go/v6/modules/core/exported"
)

var _ porttypes.Middleware = &IBCMiddleware{}

// IBCMiddleware implements the ICS26 callbacks for the fee middleware given the
// fee keeper and the underlying application.
type IBCMiddleware struct {
app porttypes.IBCModule
chanWrapper porttypes.ICS4Wrapper
}

// NewIBCMiddleware creates a new IBCMiddlware given the keeper and underlying application
func NewIBCMiddleware(app porttypes.IBCModule, chanWrapper porttypes.ICS4Wrapper) IBCMiddleware {
return IBCMiddleware{
app: app,
chanWrapper: chanWrapper,
}
}

// OnChanOpenInit implements the IBCMiddleware interface
func (im IBCMiddleware) OnChanOpenInit(
ctx sdk.Context,
order channeltypes.Order,
connectionHops []string,
portID string,
channelID string,
chanCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
version string,
) (string, error) {
// call underlying app's OnChanOpenInit callback
return im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID,
chanCap, counterparty, version)
}

// OnChanOpenTry implements the IBCMiddleware interface
// If the channel is not fee enabled the underlying application version will be returned
// If the channel is fee enabled we merge the underlying application version with the ics29 version
func (im IBCMiddleware) OnChanOpenTry(
ctx sdk.Context,
order channeltypes.Order,
connectionHops []string,
portID,
channelID string,
chanCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
counterpartyVersion string,
) (string, error) {
// call underlying app's OnChanOpenTry callback
return im.app.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, counterpartyVersion)
}

// OnChanOpenAck implements the IBCMiddleware interface
func (im IBCMiddleware) OnChanOpenAck(
ctx sdk.Context,
portID,
channelID string,
counterpartyChannelID string,
counterpartyVersion string,
) error {
// call underlying app's OnChanOpenAck callback
return im.app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion)
}

// OnChanOpenConfirm implements the IBCMiddleware interface
func (im IBCMiddleware) OnChanOpenConfirm(
ctx sdk.Context,
portID,
channelID string,
) error {
// call underlying app's OnChanOpenConfirm callback.
return im.app.OnChanOpenConfirm(ctx, portID, channelID)
}

// OnChanCloseInit implements the IBCMiddleware interface
func (im IBCMiddleware) OnChanCloseInit(
ctx sdk.Context,
portID,
channelID string,
) error {
// call underlying app's OnChanCloseInit callback.
return im.app.OnChanCloseInit(ctx, portID, channelID)
}

// OnChanCloseConfirm implements the IBCMiddleware interface
func (im IBCMiddleware) OnChanCloseConfirm(
ctx sdk.Context,
portID,
channelID string,
) error {
// call underlying app's OnChanCloseConfirm callback.
return im.app.OnChanCloseConfirm(ctx, portID, channelID)
}

// OnRecvPacket implements the IBCMiddleware interface.
// If fees are not enabled, this callback will default to the ibc-core packet callback
func (im IBCMiddleware) OnRecvPacket(
ctx sdk.Context,
packet channeltypes.Packet,
relayer sdk.AccAddress,
) exported.Acknowledgement {
// first do the underlying ibc app callback
ack := im.app.OnRecvPacket(ctx, packet, relayer)

// postprocess the ibc application by executing a callback to the receiver smart contract
// if the receiver of the transfer packet is a smart contract
var data transfertypes.FungibleTokenPacketData
if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err == nil {
// check if data.Receiver is a smart contract address
// if it is a smart contract address
// then call the smart-contract defined callback for RecvPacket and pass in the transfer packet data
// if the callback returns an error, then return an error acknowledgement
if isSmartContract(data.Receiver) {
err := data.Receiver.recvPacketCallback(data)
if err != nil {
return channeltypes.NewErrorAcknowledgement(err)
}
}
}
return ack
}

// OnAcknowledgementPacket implements the IBCMiddleware interface
// If fees are not enabled, this callback will default to the ibc-core packet callback
func (im IBCMiddleware) OnAcknowledgementPacket(
ctx sdk.Context,
packet channeltypes.Packet,
acknowledgement []byte,
relayer sdk.AccAddress,
) error {
// call underlying callback
err := im.app.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer)

// postprocess the ibc application by executing a callback to the sender smart contract
// if the sender of the transfer packet is a smart contract
var data transfertypes.FungibleTokenPacketData
if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err == nil {
// check if data.Sender is a smart contract address
// if it is a smart contract address
// then call the smart-contract defined callback for RecvPacket and pass in the transfer packet data
// if the callback returns an error, then return an error
if isSmartContract(data.Sender) {
data.Sender.ackPacketCallback(data)
}
}
return err
}

// OnTimeoutPacket implements the IBCMiddleware interface
// If fees are not enabled, this callback will default to the ibc-core packet callback
func (im IBCMiddleware) OnTimeoutPacket(
ctx sdk.Context,
packet channeltypes.Packet,
relayer sdk.AccAddress,
) error {
// call underlying callback
err := im.app.OnTimeoutPacket(ctx, packet, relayer)

// postprocess the ibc application by executing a callback to the sender smart contract
// if the sender of the transfer packet is a smart contract
var data transfertypes.FungibleTokenPacketData
if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err == nil {
// check if data.Sender is a smart contract address
// if it is a smart contract address
// then call the smart-contract defined callback for RecvPacket and pass in the transfer packet data
// if the callback returns an error, then return an error
if isSmartContract(data.Sender) {
data.Sender.timeoutPacketCallback(data)
}
}
return err
}

// SendPacket implements the ICS4 Wrapper interface
func (im IBCMiddleware) SendPacket(
ctx sdk.Context,
chanCap *capabilitytypes.Capability,
sourcePort string,
sourceChannel string,
timeoutHeight clienttypes.Height,
timeoutTimestamp uint64,
data []byte,
) (uint64, error) {
return im.chanWrapper.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data)
}

// WriteAcknowledgement implements the ICS4 Wrapper interface
func (im IBCMiddleware) WriteAcknowledgement(
ctx sdk.Context,
chanCap *capabilitytypes.Capability,
packet exported.PacketI,
ack exported.Acknowledgement,
) error {
return im.chanWrapper.WriteAcknowledgement(ctx, chanCap, packet, ack)
}

// GetAppVersion returns the application version of the underlying application
func (im IBCMiddleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) {
return im.chanWrapper.GetAppVersion(ctx, portID, channelID)
}
```
43 changes: 43 additions & 0 deletions docs/assets/callbacks-mw/callbackflow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions docs/assets/callbacks-mw/ics4-callbackflow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
93 changes: 93 additions & 0 deletions docs/middleware/callbacks/end-users.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<!--
order: 5
-->

# Usage

This section explains how to use the callbacks middleware from the perspective of an IBC Actor. Callbacks middleware provides two types of callbacks:

- Source callbacks:
- `SendPacket` callback
- `OnAcknowledgementPacket` callback
- `OnTimeoutPacket` callback
- Destination callbacks:
- `ReceivePacket` callback

For a given channel, the source callbacks are supported if the source chain has the callbacks middleware wired up in the channel's IBC stack. Similarly, the destination callbacks are supported if the destination chain has the callbacks middleware wired up in the channel's IBC stack.

::: tip
Callbacks are always executed after the packet has been processed by the underlying IBC module.
:::

::: warning
If the underlying application module is doing an asynchronous acknowledgement on packet receive (for example, if the [packet forward middleware](https://github.com/cosmos/ibc-apps/tree/main/middleware/packet-forward-middleware) is in the stack, and is being used by this packet), then the callbacks middleware will execute the `ReceivePacket` callback after the acknowledgement has been received.
:::

## Source Callbacks

Source callbacks are natively supported in the following ibc modules (if they are wrapped by the callbacks middleware):

- `transfer`
- `icacontroller`

To have your source callbacks be processed by the callbacks middleware, you must set the memo in the application's packet data to the following format:

```jsonc
{
"src_callback": {
"address": "callbackAddressString",
// optional
"gas_limit": "userDefinedGasLimitString",
}
}
```

## Destination Callbacks

Destination callbacks are natively only supported in the transfer module. Note that wrapping icahost is not supported. This is because icahost should be able to execute an arbitrary transaction anyway, and can call contracts or modules directly.

To have your destination callbacks processed by the callbacks middleware, you must set the memo in the application's packet data to the following format:

```jsonc
{
"dest_callback": {
"address": "callbackAddressString",
// optional
"gas_limit": "userDefinedGasLimitString",
}
}
```

Note that a packet can have both a source and destination callback.

```jsonc
{
"src_callback": {
"address": "callbackAddressString",
// optional
"gas_limit": "userDefinedGasLimitString",
},
"dest_callback": {
"address": "callbackAddressString",
// optional
"gas_limit": "userDefinedGasLimitString",
}
}
```

# User Defined Gas Limit

User defined gas limit was added for the following reasons:

- To prevent callbacks from blocking packet lifecycle.
- To prevent relayers from being able to DOS the callback execution by sending a packet with a low amount of gas.

::: tip
There is a chain wide parameter that sets the maximum gas limit that a user can set for a callback. This is to prevent a user from setting a gas limit that is too high for relayers. If the `"gas_limit"` is not set in the packet memo, then the maximum gas limit is used.
:::

These goals are achieved by creating a minimum gas amount required for callback execution. If the relayer provides at least the minimum gas limit for the callback execution, then the packet lifecycle will not be blocked if the callback runs out of gas during execution, and the callback cannot be retried. If the relayer does not provided the minimum amount of gas and the callback executions runs out of gas, the entire tx is reverted and it may be executed again.

::: tip
`SendPacket` callback is always reverted if the callback execution fails or returns an error for any reason. This is so that the packet is not sent if the callback execution fails.
:::
36 changes: 36 additions & 0 deletions docs/middleware/callbacks/events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!--
order: 4
-->

# Events

An overview of all events related to the callbacks middleware. There are two types of events, `"ibc_src_callback"` and `"ibc_dest_callback"`.

## Shared Attributes

Both of these event types share the following attributes:

| **Attribute Key** | **Attribute Values** | **Optional** |
|:-------------------------:|:---------------------------------------------------------------------------------------:|:------------------:|
| module | "ibccallbacks" | |
| callback_type | **One of**: "send_packet", "acknowledgement_packet", "timeout_packet", "receive_packet" | |
| callback_address | string | |
| callback_exec_gas_limit | string (parsed from uint64) | |
| callback_commit_gas_limit | string (parsed from uint64) | |
| packet_sequence | string (parsed from uint64) | |
| callback_result | **One of**: "success", "failure" | |
| callback_error | string (parsed from callback err) | Yes, if err != nil |

## `ibc_src_callback` Attributes

| **Attribute Key** | **Attribute Values** |
|:------------------:|:------------------------:|
| packet_src_port | string (sourcePortID) |
| packet_src_channel | string (sourceChannelID) |

## `ibc_dest_callback` Attributes

| **Attribute Key** | **Attribute Values** |
|:-------------------:|:------------------------:|
| packet_dest_port | string (destPortID) |
| packet_dest_channel | string (destChannelID) |
Loading

0 comments on commit 812a364

Please sign in to comment.