From 4b570750c95e8a7620c44e793b5aa3ac81545771 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 1 Jun 2021 16:41:05 +0200 Subject: [PATCH 01/22] Start defining general relayer fee payment protocol --- spec/app/ics-029-fee-payment/README.md | 89 ++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 spec/app/ics-029-fee-payment/README.md diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md new file mode 100644 index 000000000..6dede84aa --- /dev/null +++ b/spec/app/ics-029-fee-payment/README.md @@ -0,0 +1,89 @@ +--- +ics: 29 +title: General Fee Payment +stage: draft +category: IBC/APP +requires: 20, 25, 26 +kind: instantiation +author: Ethan Frey +created: 2021-06-01 +modified: 2021-06-01 +--- + +## Synopsis + +This standard document specifies packet data structure, state machine handling logic, and encoding details for handling fee +payments on top of any ICS application protocol. It requires some standard packet changes, but can be adopted by any +application, without forcing other applications to use this implementation. + +### Motivation + +There has been much discussion on a general incentivization mechanism for relayers. A simple proposal was created to +[extend ICS20 to incentivize relaying](https://github.com/cosmos/ibc/pull/577) on the destination chain. However, +it was very specific to ICS20 and would not work for other protocols. This was then extended to a more +[general fee payment design](https://github.com/cosmos/ibc/issues/578) that could be adopted by any ICS application +protocol. + +In general, the Interchain dream will never scale unless there is a clear way to incentivize relayers. We seek to +define a clear interface that can be easily adopted by any application, but not preclude chains that don't use tokens. + +### Desired Properties + +- Incentivize timely delivery of the packet (`OnReceivePacket` called) +- Incentivize relaying acks for these packets (`OnAcknowledgement` called) +- Incentivize relaying timeouts for these packets when the receive fee was too low (`OnTimeout` called) +- Produces no extra IBC packets +- One direction works, even when one chain does not support concept of fungible tokens +- Opt-in for each chain implementing this. eg. ICS27 with fee support on chain A could connect to ICS27 without fee support on chain B. +- Standardized interface for each chain implementing this extension +- Permissionless relaying + +## Technical Specification + +### General Design + +In order to avoid extra packets on the order of the number of fee packets, as well as provide an opt-in approach, we +store all fee payment info only on the source chain. The source chain is the one location where the sender can provide tokens +to incentivize the packet. + +We require that the [relayer address is exposed to application modules](https://github.com/cosmos/ibc/pull/579) for +all packet-related messages, so the modules are able to incentivize the packet relayer + + +#### Reasoning + +##### Correctness + +#### Optional addenda + +## Backwards Compatibility + +This can be added to any existing protocol without break it on the other side. + +## Forwards Compatibility + +## Example Implementation + +Coming soon. + +## Other Implementations + +Coming soon. + +## History + +Jul 15, 2019 - Draft written + +Jul 29, 2019 - Major revisions; cleanup + +Aug 25, 2019 - Major revisions, more cleanup + +Feb 3, 2020 - Revisions to handle acknowledgements of success & failure + +Feb 24, 2020 - Revisions to infer source field, inclusion of version string + +July 27, 2020 - Re-addition of source field + +## Copyright + +All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). From b1d51144480b7c519091cc0d950f4bba2bacf409 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 1 Jun 2021 16:56:58 +0200 Subject: [PATCH 02/22] Provide high-level implementation details --- spec/app/ics-029-fee-payment/README.md | 33 ++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 6dede84aa..612668f03 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -44,11 +44,40 @@ define a clear interface that can be easily adopted by any application, but not In order to avoid extra packets on the order of the number of fee packets, as well as provide an opt-in approach, we store all fee payment info only on the source chain. The source chain is the one location where the sender can provide tokens -to incentivize the packet. +to incentivize the packet. The fee distribution may be implementation specific and thus does not need to be in the ibc spec +(just high-level requirements are needed in this doc). We require that the [relayer address is exposed to application modules](https://github.com/cosmos/ibc/pull/579) for -all packet-related messages, so the modules are able to incentivize the packet relayer +all packet-related messages, so the modules are able to incentivize the packet relayer. `OnAcknowledgement`, `OnTimeout`, +and `OnTimeoutClose` messages will therefore have the relayer address and be capable of sending escrowed tokens to such address. +However, we need a way to reliably get the address of the relayer that submitted `OnReceivePacket` on the destination chain to +the source chain. In fact, we need a *source address* for this relayer to pay out to, not the *destination address* that signed +the packet. +Given this, the flow would be: + +1. User/module submits a send packet on the `source` chain, along with some tokens and fee information on how to distribute them +2. Relayer submits `OnReceivePacket` on the `destination` chain. Along with this message, the *forward relayer* will submit a `payToOnSource` address where payment should be sent. +3. Destination application includes this `payToOnSource` in the acknowledgement (there are multiple approaches to discuss below) +4. Relayer submits `OnAcknowledgement` which provides the *return relayer* address on the source chain, along with the `payToOnSource` address +5. Source application can distribute the tokens escrowed in (1) to both the *forward* and the *return* relayers. + +Alternate flow: + +1. User/module submits a send packet on the `source` chain, along with some tokens and fee information on how to distribute them +2. Relayer submits `OnTimeout` which provides their address on the source chain +3. Source application can distribute the tokens escrowed in (1) to this relayer, and potentially return extra tokens to the original packet sender. + +### Fee details + +For an example implementation in the Cosmos SDK, we consider 3 potential fee payments, which may be defined. Each one may be +paid out in a different token. Imagine a connection between IrisNet and the Cosmos Hub. They may define: + +- ReceiveFee: 0.003 channel-7/ATOM vouchers (ATOMs already on IrisNet via ICS20) +- AckFee: 0.001 IRIS +- TimeoutFee: 0.002 IRIS + +Ideally the fees can easily be redeemed in native tokens on both sides, but relayers may select others. In this example, the relayer collects a fair bit of IRIS, covering it's costs there and more. It also collects channel-7/ATOM vouchers from many packets. After relaying a few thousand packets, the account on the Cosmos Hub is running low, so the relayer will send those channel-7/ATOM vouchers back over channel-7 to it's account on the Hub to replenish the supply there. #### Reasoning From 98d35cea1b07478b1601cd40a1ec96d2c73ba26c Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 2 Jun 2021 09:47:55 -0400 Subject: [PATCH 03/22] add to example --- spec/app/ics-029-fee-payment/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 612668f03..0347da733 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -79,6 +79,8 @@ paid out in a different token. Imagine a connection between IrisNet and the Cosm Ideally the fees can easily be redeemed in native tokens on both sides, but relayers may select others. In this example, the relayer collects a fair bit of IRIS, covering it's costs there and more. It also collects channel-7/ATOM vouchers from many packets. After relaying a few thousand packets, the account on the Cosmos Hub is running low, so the relayer will send those channel-7/ATOM vouchers back over channel-7 to it's account on the Hub to replenish the supply there. +The sender chain will escrow 0.003 channel-7/ATOM and 0.002 IRIS. In the case that a forward relayer submits the `MsgRecvPacket` and a reverse relayer submits the `MsgAckPacket`, the forward relayer is reqarded 0.003 channel-7/ATOM and the reverse relayer is rewarded 0.001 IRIS. In the case where the packet times out, the timeout relayer receives 0.002 IRIS and 0.003 channel-7/ATOM is refunded to the original fee payer. + #### Reasoning ##### Correctness From 9eaaadbc16aba601de3df281f429152ec420ef2d Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 2 Jun 2021 12:28:24 -0400 Subject: [PATCH 04/22] writeup interfaces --- spec/app/ics-029-fee-payment/README.md | 66 +++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 0347da733..568caf71b 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -81,6 +81,70 @@ Ideally the fees can easily be redeemed in native tokens on both sides, but rela The sender chain will escrow 0.003 channel-7/ATOM and 0.002 IRIS. In the case that a forward relayer submits the `MsgRecvPacket` and a reverse relayer submits the `MsgAckPacket`, the forward relayer is reqarded 0.003 channel-7/ATOM and the reverse relayer is rewarded 0.001 IRIS. In the case where the packet times out, the timeout relayer receives 0.002 IRIS and 0.003 channel-7/ATOM is refunded to the original fee payer. +The logic involved in collecting fees from users and then paying it out to the relevant relayers is encapsulated by a separate fee module and may vary between implementations. However, all fee modules must implement a uniform interface such that the ICS-4 handlers can correctly pay out fees to the right relayers, and so that relayers themselves can easily determine the fees they can expect for relaying a packet. + +```typescript +function PayFee(packet: Packet, forward_relayer: string, reverse_relayer: string) { + // pay the forward fee to the forward relayer address + // pay the reverse fee to the reverse relayer address + // refund extra tokens to original fee payer(s) +} + +function PayTimeoutFee(packet: Packet, timeout_relayer: string) { + // pay the timeout fee to the timeout relayer address + // refund extra tokens to original fee payer(s) +} +``` + +These functions will then be utilized in the ICS-4 handlers like so: + +```typescript +function acknowledgePacket( + packet: OpaquePacket, + acknowledgement: bytes, + proof: CommitmentProof, + proofHeight: Height, + relayer: string): Packet { + // ... + // get the forward relayer from the acknowledgement + // and pay fees to forward and reverse relayers. + // reverse_relayer is submitter of acknowledgement message + // provided in function arguments + // NOTE: Fee may be zero + forward_relayer = getForwardRelayer(acknowledgement) + PayFee(packet, forward_relayer, relayer) + // ... +} + +function timeoutPacket( + packet: OpaquePacket, + proof: CommitmentProof, + proofHeight: Height, + nextSequenceRecv: Maybe, + relayer: string): Packet { + // ... + // get the timeout relayer from function arguments + // and pay timeout fee. + // NOTE: Fee may be zero + PayTimeoutFee(packet, relayer) +} +``` + +The fee module should also expose the following queries so that relayers may query their expected fee: + +```typescript +// Gets the fee expected for submitting ReceivePacket msg for this packet +function GetReceiveFee(packet) Fee + +// Gets the fee expected for submitting AcknowledgePacket msg for this packet +function GetAckFee(packet) Fee + +// Gets the fee expected for submitting TimeoutPacket msg for this packet +function GetTimeoutFee(packet) Fee +``` + +Since different chains may have different representations for fungible tokens and this information is not being sent to other chains; this ICS does not specify a particular representation for the `Fee`. Each chain may choose its own representation, it is incumbent on relayers to interpret the Fee correctly. + #### Reasoning ##### Correctness @@ -89,7 +153,7 @@ The sender chain will escrow 0.003 channel-7/ATOM and 0.002 IRIS. In the case th ## Backwards Compatibility -This can be added to any existing protocol without break it on the other side. +This can be added to any existing protocol without breaking it on the other side. ## Forwards Compatibility From 8f6d4c0eaa983baca1d68e95e5e3b89310a2f7f3 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 2 Jun 2021 17:06:06 -0400 Subject: [PATCH 05/22] writeup connection/channel changes --- spec/app/ics-029-fee-payment/README.md | 53 +++++++++++++++++--------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 568caf71b..76e4672fa 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -36,6 +36,7 @@ define a clear interface that can be easily adopted by any application, but not - One direction works, even when one chain does not support concept of fungible tokens - Opt-in for each chain implementing this. eg. ICS27 with fee support on chain A could connect to ICS27 without fee support on chain B. - Standardized interface for each chain implementing this extension +- Allow each chain/application to customize fee-handling logic - Permissionless relaying ## Technical Specification @@ -83,6 +84,8 @@ The sender chain will escrow 0.003 channel-7/ATOM and 0.002 IRIS. In the case th The logic involved in collecting fees from users and then paying it out to the relevant relayers is encapsulated by a separate fee module and may vary between implementations. However, all fee modules must implement a uniform interface such that the ICS-4 handlers can correctly pay out fees to the right relayers, and so that relayers themselves can easily determine the fees they can expect for relaying a packet. +### Fee Module Contract + ```typescript function PayFee(packet: Packet, forward_relayer: string, reverse_relayer: string) { // pay the forward fee to the forward relayer address @@ -96,7 +99,33 @@ function PayTimeoutFee(packet: Packet, timeout_relayer: string) { } ``` -These functions will then be utilized in the ICS-4 handlers like so: + +The fee module should also expose the following queries so that relayers may query their expected fee: + +```typescript +// Gets the fee expected for submitting ReceivePacket msg for this packet +function GetReceiveFee(packet) Fee + +// Gets the fee expected for submitting AcknowledgePacket msg for this packet +function GetAckFee(packet) Fee + +// Gets the fee expected for submitting TimeoutPacket msg for this packet +function GetTimeoutFee(packet) Fee +``` + +Since different chains may have different representations for fungible tokens and this information is not being sent to other chains; this ICS does not specify a particular representation for the `Fee`. Each chain may choose its own representation, it is incumbent on relayers to interpret the Fee correctly. + +### Connection Negotiation + +The chains must agree to enable the incentivization feature during the connection handshake. This can be done by adding an incentivization feature to the connection version. + +```{"1", ["ORDER_ORDERED", "ORDER_UNORDERED", "INCENTIVE_V1"]}``` + +If the negotiated connection includes the incentivization feature, then the ICS-4 `WriteAcknowledgement` function must write the forward relayer address into a structured acknowledgement and the ICS-4 handlers for `AcknowledgePacket` and `TimeoutPacket` must pay fees through the ibc fee module callbacks. If the negotiated connection does not include the incentivization feature, then the ICS-4 handlers must not modify the acknowledgement provided by the application and should not call any fee callbacks even if relayer incentivization is enabled on the chain. + +### Channel Changes + +The ibc-fee callbacks will then be utilized in the ICS-4 handlers like so: ```typescript function acknowledgePacket( @@ -105,7 +134,6 @@ function acknowledgePacket( proof: CommitmentProof, proofHeight: Height, relayer: string): Packet { - // ... // get the forward relayer from the acknowledgement // and pay fees to forward and reverse relayers. // reverse_relayer is submitter of acknowledgement message @@ -113,7 +141,6 @@ function acknowledgePacket( // NOTE: Fee may be zero forward_relayer = getForwardRelayer(acknowledgement) PayFee(packet, forward_relayer, relayer) - // ... } function timeoutPacket( @@ -122,7 +149,6 @@ function timeoutPacket( proofHeight: Height, nextSequenceRecv: Maybe, relayer: string): Packet { - // ... // get the timeout relayer from function arguments // and pay timeout fee. // NOTE: Fee may be zero @@ -130,24 +156,17 @@ function timeoutPacket( } ``` -The fee module should also expose the following queries so that relayers may query their expected fee: - -```typescript -// Gets the fee expected for submitting ReceivePacket msg for this packet -function GetReceiveFee(packet) Fee +#### Reasoning -// Gets the fee expected for submitting AcknowledgePacket msg for this packet -function GetAckFee(packet) Fee +This proposal satisfies the desired properties. All parts of the packet flow (receive/acknowledge/timeout) can be properly incentivized and rewarded. The protocol does not specify the relayer beforehand, thus the incentivization is permissionless. The escrowing and distribution of funds is completely handled on source chain, thus there is no need for additional IBC packets or the use of ICS-20 in the fee protocol. The fee protocol only assumes existence of fungible tokens on the source chain. Using the connection version, the protocol enables opt-in incentivization and backwards compatibility. The fee module can implement arbitrary custom logic so long as it respects the callback interfaces that IBC expects. -// Gets the fee expected for submitting TimeoutPacket msg for this packet -function GetTimeoutFee(packet) Fee -``` +##### Correctness -Since different chains may have different representations for fungible tokens and this information is not being sent to other chains; this ICS does not specify a particular representation for the `Fee`. Each chain may choose its own representation, it is incumbent on relayers to interpret the Fee correctly. +The fee module is responsible for correctly escrowing and distributing funds to the provided relayers. It is IBC's responsibility to provide the fee module with the correct relayers. The ack and timeout relayers are trivially retrievable since they are the senders of the acknowledgment and timeout message. -#### Reasoning +The receive relayer submits the message to the counterparty chain. Thus the counterparty chain must communicate the knowledge of who relayed the receive packet to the source chain using the acknowledgement. The address that is sent back **must** be the address of the forward relayer on the source chain. -##### Correctness +With the forward relayer embedded in the acknowledgement, and the reverse and timeout relayers available directly in the message; IBC is able to provide the correct relayer addresses to the fee module for each step of the packet flow. #### Optional addenda From 5372351427fd77ff572b637bff4cbb105c9467f5 Mon Sep 17 00:00:00 2001 From: Aditya Date: Thu, 3 Jun 2021 12:38:40 -0400 Subject: [PATCH 06/22] Apply suggestions from code review Co-authored-by: Zarko Milosevic --- spec/app/ics-029-fee-payment/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 76e4672fa..73fdcc8f3 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -31,7 +31,7 @@ define a clear interface that can be easily adopted by any application, but not - Incentivize timely delivery of the packet (`OnReceivePacket` called) - Incentivize relaying acks for these packets (`OnAcknowledgement` called) -- Incentivize relaying timeouts for these packets when the receive fee was too low (`OnTimeout` called) +- Incentivize relaying timeouts for these packets when the timeout has expired before packet is delivered (for example as receive fee was too low) (`OnTimeout` called) - Produces no extra IBC packets - One direction works, even when one chain does not support concept of fungible tokens - Opt-in for each chain implementing this. eg. ICS27 with fee support on chain A could connect to ICS27 without fee support on chain B. @@ -66,8 +66,8 @@ Given this, the flow would be: Alternate flow: 1. User/module submits a send packet on the `source` chain, along with some tokens and fee information on how to distribute them -2. Relayer submits `OnTimeout` which provides their address on the source chain -3. Source application can distribute the tokens escrowed in (1) to this relayer, and potentially return extra tokens to the original packet sender. +2. Relayer submits `OnTimeout` which provides its address on the source chain +3. Source application can distribute the tokens escrowed in (1) to this relayer, and potentially return remainder tokens to the original packet sender. ### Fee details From 6f0c93bbb2720feb3ab6346e039614f650f23913 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Thu, 3 Jun 2021 17:10:07 -0400 Subject: [PATCH 07/22] address reviews --- spec/app/ics-029-fee-payment/README.md | 58 +++++++++++++++++--------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 76e4672fa..483f6bdee 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -37,6 +37,7 @@ define a clear interface that can be easily adopted by any application, but not - Opt-in for each chain implementing this. eg. ICS27 with fee support on chain A could connect to ICS27 without fee support on chain B. - Standardized interface for each chain implementing this extension - Allow each chain/application to customize fee-handling logic +- Relayer addresses should not be forgable - Permissionless relaying ## Technical Specification @@ -57,7 +58,7 @@ the packet. Given this, the flow would be: -1. User/module submits a send packet on the `source` chain, along with some tokens and fee information on how to distribute them +1. User/module submits a send packet on the `source` chain, along with some tokens and fee information on how to distribute them. The fee tokens are all escrowed by the fee module. 2. Relayer submits `OnReceivePacket` on the `destination` chain. Along with this message, the *forward relayer* will submit a `payToOnSource` address where payment should be sent. 3. Destination application includes this `payToOnSource` in the acknowledgement (there are multiple approaches to discuss below) 4. Relayer submits `OnAcknowledgement` which provides the *return relayer* address on the source chain, along with the `payToOnSource` address @@ -103,14 +104,14 @@ function PayTimeoutFee(packet: Packet, timeout_relayer: string) { The fee module should also expose the following queries so that relayers may query their expected fee: ```typescript -// Gets the fee expected for submitting ReceivePacket msg for this packet -function GetReceiveFee(packet) Fee +// Gets the fee expected for submitting ReceivePacket msg for the given packet +function GetReceiveFee(portID, channelID, sequence) Fee -// Gets the fee expected for submitting AcknowledgePacket msg for this packet -function GetAckFee(packet) Fee +// Gets the fee expected for submitting AcknowledgePacket msg for the given packet +function GetAckFee(portID, channelID, sequence) Fee -// Gets the fee expected for submitting TimeoutPacket msg for this packet -function GetTimeoutFee(packet) Fee +// Gets the fee expected for submitting TimeoutPacket msg for the given packet +function GetTimeoutFee(portID, channelID, sequence) Fee ``` Since different chains may have different representations for fungible tokens and this information is not being sent to other chains; this ICS does not specify a particular representation for the `Fee`. Each chain may choose its own representation, it is incumbent on relayers to interpret the Fee correctly. @@ -123,6 +124,8 @@ The chains must agree to enable the incentivization feature during the connectio If the negotiated connection includes the incentivization feature, then the ICS-4 `WriteAcknowledgement` function must write the forward relayer address into a structured acknowledgement and the ICS-4 handlers for `AcknowledgePacket` and `TimeoutPacket` must pay fees through the ibc fee module callbacks. If the negotiated connection does not include the incentivization feature, then the ICS-4 handlers must not modify the acknowledgement provided by the application and should not call any fee callbacks even if relayer incentivization is enabled on the chain. +Thus, a chain can support incentivization while still maintaining connections that do not have the incentivization feature. This is crucial to enable a chain with incentivization to connect with a chain that does not have incentivization feature, as the acknowledgements need to be sent over the wire without the forward relayer. + ### Channel Changes The ibc-fee callbacks will then be utilized in the ICS-4 handlers like so: @@ -134,13 +137,18 @@ function acknowledgePacket( proof: CommitmentProof, proofHeight: Height, relayer: string): Packet { - // get the forward relayer from the acknowledgement - // and pay fees to forward and reverse relayers. - // reverse_relayer is submitter of acknowledgement message - // provided in function arguments - // NOTE: Fee may be zero - forward_relayer = getForwardRelayer(acknowledgement) - PayFee(packet, forward_relayer, relayer) + // get the underlying connection for this channel + connection = getConnection() + // only call the fee module callbacks if the connection supports incentivization. + if IsSupportedFeature(connection, "INCENTIVE_V1") { + // get the forward relayer from the acknowledgement + // and pay fees to forward and reverse relayers. + // reverse_relayer is submitter of acknowledgement message + // provided in function arguments + // NOTE: Fee may be zero + forward_relayer = getForwardRelayer(acknowledgement) + PayFee(packet, forward_relayer, relayer) + } } function timeoutPacket( @@ -149,10 +157,15 @@ function timeoutPacket( proofHeight: Height, nextSequenceRecv: Maybe, relayer: string): Packet { - // get the timeout relayer from function arguments - // and pay timeout fee. - // NOTE: Fee may be zero - PayTimeoutFee(packet, relayer) + // get the underlying connection for this channel + connection = getConnection() + // only call the fee module callbacks if the connection supports incentivization. + if IsSupportedFeature(connection, "INCENTIVE_V1") { + // get the timeout relayer from function arguments + // and pay timeout fee. + // NOTE: Fee may be zero + PayTimeoutFee(packet, relayer) + } } ``` @@ -166,7 +179,14 @@ The fee module is responsible for correctly escrowing and distributing funds to The receive relayer submits the message to the counterparty chain. Thus the counterparty chain must communicate the knowledge of who relayed the receive packet to the source chain using the acknowledgement. The address that is sent back **must** be the address of the forward relayer on the source chain. -With the forward relayer embedded in the acknowledgement, and the reverse and timeout relayers available directly in the message; IBC is able to provide the correct relayer addresses to the fee module for each step of the packet flow. +The source chain will use a "best efforts" approach with regard to the forward relayer address. Since it is not verified directly by the counterparty and is instead just treated as a string to be passed back in the acknowledgement, the forward relayer `payOnSender` address may not be a valid source chain address. In this case, the invalid address is discarded, the receive fee is refunded, and the acknowledgement processing continues. It is incumbent on relayers to pass their `payOnSender` addresses to the counterparty chain correctly. +In the event that the counterparty chain itself incorrectly sends the forward relayer address, this will cause relayers to not collect fees on source chain for relaying packets. The incentivize-driven relayers will stop relaying for the chain until the acknowledgement logic is fixed, however the channel remains functional. + +We cannot return an error on an invalid `payOnSender` address as this would permanently prevent the source chain from processing the acknowledgment of a packet that was otherwise correctly received, processed and acknowledged on the counterparty chain. The IBC protocol requires that incorrect or malicious relayers may at best affect the liveness of a user's packets. Preventing successful acknowledgement in this case would leave the packet flow at a permanently incomplete state, which may be very consequential for certain IBC applications like ICS-20. + +Thus, the forward relayer reward is contingent on it providing the correct `payOnSender` address when it sends the `receive_packet` message. The packet flow will continue processing successfully even if the fee payment is unsuccessful. + +With the forward relayer correctly embedded in the acknowledgement, and the reverse and timeout relayers available directly in the message; IBC is able to provide the correct relayer addresses to the fee module for each step of the packet flow. #### Optional addenda From c85b2bdd685eb976f12ac196711b4e6a91b33a93 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Thu, 3 Jun 2021 17:55:18 -0400 Subject: [PATCH 08/22] writeup fee module requirements --- spec/app/ics-029-fee-payment/README.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 3671c48bb..5d1f309e1 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -36,7 +36,7 @@ define a clear interface that can be easily adopted by any application, but not - One direction works, even when one chain does not support concept of fungible tokens - Opt-in for each chain implementing this. eg. ICS27 with fee support on chain A could connect to ICS27 without fee support on chain B. - Standardized interface for each chain implementing this extension -- Allow each chain/application to customize fee-handling logic +- Support custom fee-handling logic within the same framework - Relayer addresses should not be forgable - Permissionless relaying @@ -87,6 +87,14 @@ The logic involved in collecting fees from users and then paying it out to the r ### Fee Module Contract +While the details may vary between fee modules, all Fee modules **must** ensure it does the following: + +- It must have in escrow the maximum fees that all outstanding packets may pay out (or it must have ability to mint required amount of tokens) +- It must pay the receive fee for a packet to the forward relayer specified in `PayFee` callback +- It must pay the ack fee for a packet to the reverse relayer specified in `PayFee` callback +- It must pay the timeout fee for a packet to the timeout relayer specified in `PayTimeoutFee` callback +- It must refund any remainder fees in escrow to the original fee payer(s) if applicable + ```typescript function PayFee(packet: Packet, forward_relayer: string, reverse_relayer: string) { // pay the forward fee to the forward relayer address @@ -206,17 +214,7 @@ Coming soon. ## History -Jul 15, 2019 - Draft written - -Jul 29, 2019 - Major revisions; cleanup - -Aug 25, 2019 - Major revisions, more cleanup - -Feb 3, 2020 - Revisions to handle acknowledgements of success & failure - -Feb 24, 2020 - Revisions to infer source field, inclusion of version string - -July 27, 2020 - Re-addition of source field +June 1 2020 - Draft written ## Copyright From a8dfa2ca9216e3ec270e6258ff7f62b148a3b9e1 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Thu, 3 Jun 2021 18:06:38 -0400 Subject: [PATCH 09/22] simplify connection version --- spec/app/ics-029-fee-payment/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 5d1f309e1..c82703c95 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -126,11 +126,13 @@ Since different chains may have different representations for fungible tokens an ### Connection Negotiation -The chains must agree to enable the incentivization feature during the connection handshake. This can be done by adding an incentivization feature to the connection version. +The chains must agree to enable the incentivization feature during the connection handshake. This can be done by bumping the connection version. -```{"1", ["ORDER_ORDERED", "ORDER_UNORDERED", "INCENTIVE_V1"]}``` +```{"2", ["ORDER_ORDERED", "ORDER_UNORDERED"]}``` -If the negotiated connection includes the incentivization feature, then the ICS-4 `WriteAcknowledgement` function must write the forward relayer address into a structured acknowledgement and the ICS-4 handlers for `AcknowledgePacket` and `TimeoutPacket` must pay fees through the ibc fee module callbacks. If the negotiated connection does not include the incentivization feature, then the ICS-4 handlers must not modify the acknowledgement provided by the application and should not call any fee callbacks even if relayer incentivization is enabled on the chain. +Since most chains that support incentivization will wish to be compatible with chains that do not, a chain with `V2` enabled will send its possible connection versions: `{{"1", ["ORDER_ORDERED", "ORDER_UNORDERED"]}, {"2", ["ORDER_ORDERED", "ORDER_UNORDERED"]}}` in `ConnOpenInit`. The counterparty chain will select the highest version that it can support. + +If the negotiated connection is `V2`, then the ICS-4 `WriteAcknowledgement` function must write the forward relayer address into a structured acknowledgement and the ICS-4 handlers for `AcknowledgePacket` and `TimeoutPacket` must pay fees through the ibc fee module callbacks. If the negotiated connection is on `V1`, then the ICS-4 handlers must not modify the acknowledgement provided by the application and should not call any fee callbacks even if relayer incentivization is enabled on the chain. Thus, a chain can support incentivization while still maintaining connections that do not have the incentivization feature. This is crucial to enable a chain with incentivization to connect with a chain that does not have incentivization feature, as the acknowledgements need to be sent over the wire without the forward relayer. From eda8453df820662ab4141024cb8d70eb2d38d8e2 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 7 Jun 2021 16:08:40 -0400 Subject: [PATCH 10/22] partially address chris reviews --- spec/app/ics-029-fee-payment/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index c82703c95..df84f25d8 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -33,7 +33,7 @@ define a clear interface that can be easily adopted by any application, but not - Incentivize relaying acks for these packets (`OnAcknowledgement` called) - Incentivize relaying timeouts for these packets when the timeout has expired before packet is delivered (for example as receive fee was too low) (`OnTimeout` called) - Produces no extra IBC packets -- One direction works, even when one chain does not support concept of fungible tokens +- One direction works, even when destination chain does not support concept of fungible tokens - Opt-in for each chain implementing this. eg. ICS27 with fee support on chain A could connect to ICS27 without fee support on chain B. - Standardized interface for each chain implementing this extension - Support custom fee-handling logic within the same framework @@ -59,9 +59,9 @@ the packet. Given this, the flow would be: 1. User/module submits a send packet on the `source` chain, along with some tokens and fee information on how to distribute them. The fee tokens are all escrowed by the fee module. -2. Relayer submits `OnReceivePacket` on the `destination` chain. Along with this message, the *forward relayer* will submit a `payToOnSource` address where payment should be sent. +2. RelayerA submits `OnReceivePacket` on the `destination` chain. Along with this message, the *forward relayer* will submit a `payToOnSource` address where payment should be sent. 3. Destination application includes this `payToOnSource` in the acknowledgement (there are multiple approaches to discuss below) -4. Relayer submits `OnAcknowledgement` which provides the *return relayer* address on the source chain, along with the `payToOnSource` address +4. RelayerB submits `OnAcknowledgement` which provides the *return relayer* address on the source chain, along with the `payToOnSource` address 5. Source application can distribute the tokens escrowed in (1) to both the *forward* and the *return* relayers. Alternate flow: From c276b67dcd8fede94481cda097c1ffc3d7f19edf Mon Sep 17 00:00:00 2001 From: Aditya Date: Mon, 7 Jun 2021 16:09:38 -0400 Subject: [PATCH 11/22] Apply suggestions from code review Co-authored-by: Christopher Goes --- spec/app/ics-029-fee-payment/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index df84f25d8..5029457b1 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -37,7 +37,7 @@ define a clear interface that can be easily adopted by any application, but not - Opt-in for each chain implementing this. eg. ICS27 with fee support on chain A could connect to ICS27 without fee support on chain B. - Standardized interface for each chain implementing this extension - Support custom fee-handling logic within the same framework -- Relayer addresses should not be forgable +- Relayer addresses should not be forgeable - Permissionless relaying ## Technical Specification @@ -46,7 +46,7 @@ define a clear interface that can be easily adopted by any application, but not In order to avoid extra packets on the order of the number of fee packets, as well as provide an opt-in approach, we store all fee payment info only on the source chain. The source chain is the one location where the sender can provide tokens -to incentivize the packet. The fee distribution may be implementation specific and thus does not need to be in the ibc spec +to incentivize the packet. The fee distribution may be implementation specific and thus does not need to be in the IBC spec (just high-level requirements are needed in this doc). We require that the [relayer address is exposed to application modules](https://github.com/cosmos/ibc/pull/579) for @@ -79,7 +79,7 @@ paid out in a different token. Imagine a connection between IrisNet and the Cosm - AckFee: 0.001 IRIS - TimeoutFee: 0.002 IRIS -Ideally the fees can easily be redeemed in native tokens on both sides, but relayers may select others. In this example, the relayer collects a fair bit of IRIS, covering it's costs there and more. It also collects channel-7/ATOM vouchers from many packets. After relaying a few thousand packets, the account on the Cosmos Hub is running low, so the relayer will send those channel-7/ATOM vouchers back over channel-7 to it's account on the Hub to replenish the supply there. +Ideally the fees can easily be redeemed in native tokens on both sides, but relayers may select others. In this example, the relayer collects a fair bit of IRIS, covering its costs there and more. It also collects channel-7/ATOM vouchers from many packets. After relaying a few thousand packets, the account on the Cosmos Hub is running low, so the relayer will send those channel-7/ATOM vouchers back over channel-7 to it's account on the Hub to replenish the supply there. The sender chain will escrow 0.003 channel-7/ATOM and 0.002 IRIS. In the case that a forward relayer submits the `MsgRecvPacket` and a reverse relayer submits the `MsgAckPacket`, the forward relayer is reqarded 0.003 channel-7/ATOM and the reverse relayer is rewarded 0.001 IRIS. In the case where the packet times out, the timeout relayer receives 0.002 IRIS and 0.003 channel-7/ATOM is refunded to the original fee payer. From 811d986a9b82bc92cca4154bb00e69c3b719b0c9 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 7 Jun 2021 17:58:45 -0400 Subject: [PATCH 12/22] address ethan, chris reviews --- spec/app/ics-029-fee-payment/README.md | 35 +++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index df84f25d8..12e216cc8 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -96,12 +96,26 @@ While the details may vary between fee modules, all Fee modules **must** ensure - It must refund any remainder fees in escrow to the original fee payer(s) if applicable ```typescript +// SetPacketFee is an open callback that may be called by any module/user that wishes to escrow funds in order to +// incentivize the relaying of the given packet. They may set a separate receiveFee, ackFee, and timeoutFee to be paid +// for each step in the packet flow. The caller must send max(receiveFee+ackFee, timeoutFee) to the fee module to be locked +// in escrow to provide payout for any potential packet flow. +// The caller may optionally specify an array of relayer addresses. This MAY be used by the fee module to modify fee payment logic +// based on ultimate relayer address. For example, fee module may choose to only pay out relayer if the relayer address was specified in +// the `SetPacketFee`. +function SetPacketFee(packet: Packet, receiveFee: Fee, ackFee: Fee, timeoutFee: Fee, relayers: []string) { + // escrow max(receiveFee+ackFee, timeoutFee) for this packet + // do custom logic with provided relayer addresses if necessary +} + +// PayFee is a callback implemented by fee module called by the ICS-4 AcknowledgePacket handler. function PayFee(packet: Packet, forward_relayer: string, reverse_relayer: string) { // pay the forward fee to the forward relayer address // pay the reverse fee to the reverse relayer address // refund extra tokens to original fee payer(s) } +// PayFee is a callback implemented by fee module called by the ICS-4 TimeoutPacket handler. function PayTimeoutFee(packet: Packet, timeout_relayer: string) { // pay the timeout fee to the timeout relayer address // refund extra tokens to original fee payer(s) @@ -113,13 +127,16 @@ The fee module should also expose the following queries so that relayers may que ```typescript // Gets the fee expected for submitting ReceivePacket msg for the given packet -function GetReceiveFee(portID, channelID, sequence) Fee +// Caller should provide the intended relayer address in case the fee is dependent on specific relayer(s). +function GetReceiveFee(portID, channelID, sequence, relayer) Fee // Gets the fee expected for submitting AcknowledgePacket msg for the given packet -function GetAckFee(portID, channelID, sequence) Fee +// Caller should provide the intended relayer address in case the fee is dependent on specific relayer(s). +function GetAckFee(portID, channelID, sequence, relayer) Fee // Gets the fee expected for submitting TimeoutPacket msg for the given packet -function GetTimeoutFee(portID, channelID, sequence) Fee +// Caller should provide the intended relayer address in case the fee is dependent on specific relayer(s). +function GetTimeoutFee(portID, channelID, sequence, relayer) Fee ``` Since different chains may have different representations for fungible tokens and this information is not being sent to other chains; this ICS does not specify a particular representation for the `Fee`. Each chain may choose its own representation, it is incumbent on relayers to interpret the Fee correctly. @@ -151,6 +168,10 @@ function acknowledgePacket( connection = getConnection() // only call the fee module callbacks if the connection supports incentivization. if IsSupportedFeature(connection, "INCENTIVE_V1") { + // If the INCENTIVE feature is enabled on the connection, then the acknowledgement + // is a marshalled struct containing the forward relayer address as a string (called forward_relayer), + // and the raw acknowledgement bytes returned by the counterparty application module (called app_ack). + // get the forward relayer from the acknowledgement // and pay fees to forward and reverse relayers. // reverse_relayer is submitter of acknowledgement message @@ -158,7 +179,15 @@ function acknowledgePacket( // NOTE: Fee may be zero forward_relayer = getForwardRelayer(acknowledgement) PayFee(packet, forward_relayer, relayer) + + // unwrap the raw acknowledgement bytes sent by counterparty application and pass it to the application callback. + app_ack = getAppAcknowledgement(acknowledgement) + } else { + // If INCENTIVE feature is not enabled for this connection, then the acknowledgement is just the raw + // acknowledgement bytes returned by application, so we can pass it through directly. + app_ack = acknowledgement } + app.OnAcknowledgePacket(packet, app_ack, relayer) } function timeoutPacket( From 7944b5e8af30cba455eff291d71b80b563c4e688 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Tue, 8 Jun 2021 19:09:44 -0400 Subject: [PATCH 13/22] add fee middleware --- spec/app/ics-029-fee-payment/README.md | 209 +++++++++++++++--- .../README.md | 2 +- spec/core/ics-026-routing-module/README.md | 8 +- 3 files changed, 179 insertions(+), 40 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 26bb462db..4dff4fe9f 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -96,14 +96,16 @@ While the details may vary between fee modules, all Fee modules **must** ensure - It must refund any remainder fees in escrow to the original fee payer(s) if applicable ```typescript -// SetPacketFee is an open callback that may be called by any module/user that wishes to escrow funds in order to -// incentivize the relaying of the given packet. They may set a separate receiveFee, ackFee, and timeoutFee to be paid +// EscrowPacketFee is an open callback that may be called by any module/user that wishes to escrow funds in order to +// incentivize the relaying of the given packet. +// NOTE: These fees are escrowed in addition to any previously escrowed amount for the packet. +// They may set a separate receiveFee, ackFee, and timeoutFee to be paid // for each step in the packet flow. The caller must send max(receiveFee+ackFee, timeoutFee) to the fee module to be locked // in escrow to provide payout for any potential packet flow. // The caller may optionally specify an array of relayer addresses. This MAY be used by the fee module to modify fee payment logic // based on ultimate relayer address. For example, fee module may choose to only pay out relayer if the relayer address was specified in -// the `SetPacketFee`. -function SetPacketFee(packet: Packet, receiveFee: Fee, ackFee: Fee, timeoutFee: Fee, relayers: []string) { +// the `EscrowPacketFee`. +function EscrowPacketFee(packet: Packet, receiveFee: Fee, ackFee: Fee, timeoutFee: Fee, relayers: []string) { // escrow max(receiveFee+ackFee, timeoutFee) for this packet // do custom logic with provided relayer addresses if necessary } @@ -141,34 +143,135 @@ function GetTimeoutFee(portID, channelID, sequence, relayer) Fee Since different chains may have different representations for fungible tokens and this information is not being sent to other chains; this ICS does not specify a particular representation for the `Fee`. Each chain may choose its own representation, it is incumbent on relayers to interpret the Fee correctly. -### Connection Negotiation +### IBC Module Wrapper -The chains must agree to enable the incentivization feature during the connection handshake. This can be done by bumping the connection version. +The fee module will implement its own ICS-26 callbacks that wrap the application-specific module callbacks. This fee module middleware will ensure that the counterparty module supports incentivization and will implement all fee-specific logic. It will then pass on the request to the embedded application module for further callback processing. -```{"2", ["ORDER_ORDERED", "ORDER_UNORDERED"]}``` +In this way, custom fee-handling logic can be hooked up to the IBC packet flow logic without placing the code in the ICS-4 handlers or the application code. This is valuable since the ICS-4 handlers should only be concerned with IBC correctness, and the application handlers should not be handling fee logic that is universal amongst all other incentivized applications. In fact, a given application module should be able to be hooked up to any fee module with no further changes to the application itself. -Since most chains that support incentivization will wish to be compatible with chains that do not, a chain with `V2` enabled will send its possible connection versions: `{{"1", ["ORDER_ORDERED", "ORDER_UNORDERED"]}, {"2", ["ORDER_ORDERED", "ORDER_UNORDERED"]}}` in `ConnOpenInit`. The counterparty chain will select the highest version that it can support. +As mentioned above, the fee module will implement the ICS-26 callbacks, and can embed an application IBC Module. All ICS-26 callbacks in the fee module will call into the embedded application's callback. Thus, only the fee callbacks that do something additional are explicitly specified here. -If the negotiated connection is `V2`, then the ICS-4 `WriteAcknowledgement` function must write the forward relayer address into a structured acknowledgement and the ICS-4 handlers for `AcknowledgePacket` and `TimeoutPacket` must pay fees through the ibc fee module callbacks. If the negotiated connection is on `V1`, then the ICS-4 handlers must not modify the acknowledgement provided by the application and should not call any fee callbacks even if relayer incentivization is enabled on the chain. +#### Fee Protocol Negotiation -Thus, a chain can support incentivization while still maintaining connections that do not have the incentivization feature. This is crucial to enable a chain with incentivization to connect with a chain that does not have incentivization feature, as the acknowledgements need to be sent over the wire without the forward relayer. +The fee middleware will negotiate its fee protocol version with the counterparty module by prepending its own version to the application version. -### Channel Changes +Channel Version: `fee_v{fee_protocol_version}:{application_version}` -The ibc-fee callbacks will then be utilized in the ICS-4 handlers like so: +Ex: `fee_v1:ics20-1` + +The fee middleware's handshake callbacks ensure that both modules agree on compatible fee protocol version(s), and then pass the application-specific version string to the embedded application's handshake callbacks. + +It is crucial that this proposal maintains backwards compatibility with a chain that does not support relayer incentivization. Thus, we must support the case where one side of the channel does not support the incentivization feature, the version will only contain the app-specific version. In the case, where `ChanOpenInit` is initiated on the chain that supports incentivization; the relayer must explicitly create the channel with just the app version (e.g. `ics20-1`), otherwise the handshake will fail on counterparty during channel version negotiation since counterparty module does not understand the fee version prefix. In the case, where `ChanOpenInit` is initiated on the chain that does not support incentivization, relayer for `ChanOpenTry` may choose to submit just application version or the fee-prefixed version. The fee middleware will inspect counterparty version and "downgrade" channel to unincentivized logic if the counterparty version does not have fee prefix. + +```typescript +function onChanOpenInit( + order: ChannelOrder, + connectionHops: [Identifier], + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyPortIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + version: string) { + // if the version is prefixed by fee version, + // then remove the prefix and pass the app-specific version to app callback. + // otherwise, pass version directly to app callback. + feeVersion, appVersion = splitFeeVersion(version) + app.OnChanOpenInit(appVersion) +} + +function OnChanOpenTry( + order: ChannelOrder, + connectionHops: [Identifier], + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyPortIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + version: string, + counterpartyVersion: string) { + cpFeeVersion, cpAppVersion = splitFeeVersion(counterpartyVersion) + feeVersion, appVersion = splitFeeVersion(version) + // if counterparty version does not have fee prefix + if !hasPrefix(counterpartyVersion, "fee") { + // create a backwards-compatible unincentivized channel + // set Incentivized flag to false so packet callbacks do not execute fee-handling logic and simply call app callback instead. + channel.Incentivized = false + } else if !isCompatible(cpFeeVersion, feeVersion) { + // both modules have incentivization enabled but are incompatible, so we end handshake. + // relayer must create new channel and either choose compatible versions or disable incentivization by not prefixing fee version. + return error + } else { + // both modules have compatible relayer incentivization protocols + // set the incentivized flag to true, so packet callbacks execute fee-handling logic. + channel.Incentivized = true + } + + // call the underlying applications OnChanOpenTry callback + app.OnChanOpenTry( + order, + connectionHops, + portIdentifier, + channelIdentifier, + counterpartyPortIdentifier, + counterpartyChannelIdentifier, + cpAppVersion, + appVersion, + ) +} + +function onChanOpenAck( + portIdentifier: Identifier, + channelIdentifier: Identifier, + version: string) { + if hasPrefix(version, "fee") { + // both modules have compatible relayer incentivization protocols + // set the incentivized flag to true, so packet callbacks execute fee-handling logic. + channel.Incentivized = true + } else { + // create a backwards-compatible unincentivized channel + // set Incentivized flag to false so packet callbacks do not execute fee-handling logic and simply call app callback instead. + channel.Incentivized = false + } +} + +function splitFeeVersion(version: string): []string { + if hasPrefix(version, "fee") { + splitVersions = split(version, ":") + feeVersion = version[0] + appVersion = join(version[1:], ":") + // if version has fee prefix + // return first split as fee version and the rest of the string as app version + return []string{feeVersion, appVersion} + } + // otherwise return an empty fee version and full version as app version + return []string{"", version} +} +``` + +#### Packet Callbacks ```typescript -function acknowledgePacket( - packet: OpaquePacket, - acknowledgement: bytes, - proof: CommitmentProof, - proofHeight: Height, - relayer: string): Packet { - // get the underlying connection for this channel - connection = getConnection() - // only call the fee module callbacks if the connection supports incentivization. - if IsSupportedFeature(connection, "INCENTIVE_V1") { - // If the INCENTIVE feature is enabled on the connection, then the acknowledgement +function onRecvPacket(packet: Packet, relayer: string): bytes { + app_acknowledgement = app.onRecvPacket(packet, relayer) + + // in case of asynchronous acknowledgement, we must store the relayer address so that we can retrieve it later to write the acknowledgement. + if app_acknowledgement == nil { + privateStore.set(forwardRelayerPath(packet), relayer) + } + + // if channel is incentivized, wrap the acknowledgement with forward relayer and return marshalled bytes + if channel.Incentivized { + // constructIncentivizedAck takes the app-specific acknowledgement and receive-packet relayer (forward relayer) + // and constructs the incentivized acknowledgement struct with the forward relayer and app-specific acknowledgement embedded. + ack = constructIncentivizedAck(app_acknowledgment, relayer) + return marshal(ack) + } + // otherwise just return the raw app acknowledgment. + return app_acknowledgement +} + +function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) { + if channel.Incentivized { + // If incentivization is enabled, then the acknowledgement // is a marshalled struct containing the forward relayer address as a string (called forward_relayer), // and the raw acknowledgement bytes returned by the counterparty application module (called app_ack). @@ -177,34 +280,70 @@ function acknowledgePacket( // reverse_relayer is submitter of acknowledgement message // provided in function arguments // NOTE: Fee may be zero - forward_relayer = getForwardRelayer(acknowledgement) + ack = unmarshal(acknowledgement) + forward_relayer = getForwardRelayer(ack) PayFee(packet, forward_relayer, relayer) // unwrap the raw acknowledgement bytes sent by counterparty application and pass it to the application callback. app_ack = getAppAcknowledgement(acknowledgement) } else { - // If INCENTIVE feature is not enabled for this connection, then the acknowledgement is just the raw + // If incentivization is not enabled, then the acknowledgement is just the raw // acknowledgement bytes returned by application, so we can pass it through directly. app_ack = acknowledgement } app.OnAcknowledgePacket(packet, app_ack, relayer) } -function timeoutPacket( - packet: OpaquePacket, - proof: CommitmentProof, - proofHeight: Height, - nextSequenceRecv: Maybe, - relayer: string): Packet { - // get the underlying connection for this channel - connection = getConnection() - // only call the fee module callbacks if the connection supports incentivization. - if IsSupportedFeature(connection, "INCENTIVE_V1") { +function onTimeoutPacket(packet: Packet, relayer: string) { + if channel.Incentivized { // get the timeout relayer from function arguments // and pay timeout fee. // NOTE: Fee may be zero PayTimeoutFee(packet, relayer) } + app.OnTimeoutPacket(packet, relayer) +} + +function onTimeoutPacketClose(packet: Packet, relayer: string) { +if channel.Incentivized { + // get the timeout relayer from function arguments + // and pay timeout fee. + // NOTE: Fee may be zero + PayTimeoutFee(packet, relayer) + } + app.onTimeoutPacketClose(packet, relayer) +} + +function constructIncentivizedAck(app_ack: bytes, forward_relayer: string): Acknowledgement { + // TODO: see https://github.com/cosmos/ibc/pull/582 +} + +function getForwardRelayer(ack: Acknowledgement): string { + // TODO: see https://github.com/cosmos/ibc/pull/582 +} + +function getAppAcknowledgement(ack: Acknowledgement): bytes { + // TODO: see https://github.com/cosmos/ibc/pull/582 +} +``` + +Note that if the embedded application uses asynchronous acks then, the `WriteAcknowledgement` call in the application must call the fee middleware's `WriteAcknowledgement` rather than calling the ICS-4 handler's `WriteAcknowledgement` function directly. + +```typescript +// Fee Middleware writeAcknowledgement function +function writeAcknowledgement( + packet: Packet, + acknowledgement: bytes) { + // if the channel is incentivized, + // retrieve the forward relayer that was stored in `onRecvPacket` + if channel.Incentivized { + relayer = privateStore.get(forwardRelayerPath(packet)) + ack = constructIncentivizedAck(acknowledgment, relayer) + ack_bytes marshal(ack) + return ics4.writeAcknowledgement(packet, ack_bytes) + } + // otherwise just write the acknowledgement directly + return ics4.writeAcknowledgement(packet, ack_bytes) } ``` diff --git a/spec/core/ics-004-channel-and-packet-semantics/README.md b/spec/core/ics-004-channel-and-packet-semantics/README.md index 2e7b1e212..729f901aa 100644 --- a/spec/core/ics-004-channel-and-packet-semantics/README.md +++ b/spec/core/ics-004-channel-and-packet-semantics/README.md @@ -667,7 +667,7 @@ The IBC handler performs the following steps in order: ```typescript function writeAcknowledgement( packet: Packet, - acknowledgement: bytes): Packet { + acknowledgement: bytes) { // cannot already have written the acknowledgement abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.destPort, packet.destChannel, packet.sequence) === null)) diff --git a/spec/core/ics-026-routing-module/README.md b/spec/core/ics-026-routing-module/README.md index 93963391e..c7a0e75db 100644 --- a/spec/core/ics-026-routing-module/README.md +++ b/spec/core/ics-026-routing-module/README.md @@ -91,19 +91,19 @@ function onChanCloseConfirm( // defined by the module } -function onRecvPacket(packet: Packet): bytes { +function onRecvPacket(packet: Packet, relayer: string): bytes { // defined by the module, returns acknowledgement } -function onTimeoutPacket(packet: Packet) { +function onTimeoutPacket(packet: Packet, relayer: string) { // defined by the module } -function onAcknowledgePacket(packet: Packet) { +function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) { // defined by the module } -function onTimeoutPacketClose(packet: Packet) { +function onTimeoutPacketClose(packet: Packet, relayer: string) { // defined by the module } ``` From dcdd939f6dc8095bd4801d679924d8be7a3316a7 Mon Sep 17 00:00:00 2001 From: Aditya Date: Tue, 15 Jun 2021 12:09:44 +0200 Subject: [PATCH 14/22] Update spec/app/ics-029-fee-payment/README.md Co-authored-by: Christopher Goes --- spec/app/ics-029-fee-payment/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 4dff4fe9f..1b38e7499 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -147,7 +147,7 @@ Since different chains may have different representations for fungible tokens an The fee module will implement its own ICS-26 callbacks that wrap the application-specific module callbacks. This fee module middleware will ensure that the counterparty module supports incentivization and will implement all fee-specific logic. It will then pass on the request to the embedded application module for further callback processing. -In this way, custom fee-handling logic can be hooked up to the IBC packet flow logic without placing the code in the ICS-4 handlers or the application code. This is valuable since the ICS-4 handlers should only be concerned with IBC correctness, and the application handlers should not be handling fee logic that is universal amongst all other incentivized applications. In fact, a given application module should be able to be hooked up to any fee module with no further changes to the application itself. +In this way, custom fee-handling logic can be hooked up to the IBC packet flow logic without placing the code in the ICS-4 handlers or the application code. This is valuable since the ICS-4 handlers should only be concerned with correctness of core IBC (transport, authentication, and ordering), and the application handlers should not be handling fee logic that is universal amongst all other incentivized applications. In fact, a given application module should be able to be hooked up to any fee module with no further changes to the application itself. As mentioned above, the fee module will implement the ICS-26 callbacks, and can embed an application IBC Module. All ICS-26 callbacks in the fee module will call into the embedded application's callback. Thus, only the fee callbacks that do something additional are explicitly specified here. From 7d3693da90144c993c118a18ffcf9490b53b4270 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Fri, 18 Jun 2021 16:45:04 +0200 Subject: [PATCH 15/22] add port discussion, remove backwards compatibility from app and move to chain responsibility --- spec/app/ics-029-fee-payment/README.md | 159 +++++++++++++------------ 1 file changed, 86 insertions(+), 73 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 4dff4fe9f..91211b44d 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -161,7 +161,15 @@ Ex: `fee_v1:ics20-1` The fee middleware's handshake callbacks ensure that both modules agree on compatible fee protocol version(s), and then pass the application-specific version string to the embedded application's handshake callbacks. -It is crucial that this proposal maintains backwards compatibility with a chain that does not support relayer incentivization. Thus, we must support the case where one side of the channel does not support the incentivization feature, the version will only contain the app-specific version. In the case, where `ChanOpenInit` is initiated on the chain that supports incentivization; the relayer must explicitly create the channel with just the app version (e.g. `ics20-1`), otherwise the handshake will fail on counterparty during channel version negotiation since counterparty module does not understand the fee version prefix. In the case, where `ChanOpenInit` is initiated on the chain that does not support incentivization, relayer for `ChanOpenTry` may choose to submit just application version or the fee-prefixed version. The fee middleware will inspect counterparty version and "downgrade" channel to unincentivized logic if the counterparty version does not have fee prefix. +### Port + +The portID will similarly be nested like the channel version to allow all nested modules to negotiate their respective ports. + +Applications must note however, that the portID in its entirety will be contained in the packet. + +PortID: `fee:transfer` + +Once the fee module has done its own logic based on the full port, it will strip the fee prefix and pass along the nested `portID` to the nested module. ```typescript function onChanOpenInit( @@ -172,11 +180,25 @@ function onChanOpenInit( counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, version: string) { - // if the version is prefixed by fee version, - // then remove the prefix and pass the app-specific version to app callback. - // otherwise, pass version directly to app callback. - feeVersion, appVersion = splitFeeVersion(version) - app.OnChanOpenInit(appVersion) + // if the version and port is prefixed, + // then remove the prefix and pass the app-specific version to app callback. + // otherwise, pass version directly to app callback. + // ensure fee ports are compatible (if they exist) + feePort, appPort = splitFeePort(portID) + feeVersion, appVersion = splitFeeVersion(version) + cpFeePort, cpAppPort = splitFeePort(counterpartyPortIdentifier) + if !isCompatible(feePort, cpFeePort) { + return error + } + app.OnChanOpenInit( + order, + connectionHops, + appPort, + channelIdentifier, + cpAppPort, + counterpartyChannelIdentifier, + appVersion, + ) } function OnChanOpenTry( @@ -190,19 +212,13 @@ function OnChanOpenTry( counterpartyVersion: string) { cpFeeVersion, cpAppVersion = splitFeeVersion(counterpartyVersion) feeVersion, appVersion = splitFeeVersion(version) - // if counterparty version does not have fee prefix - if !hasPrefix(counterpartyVersion, "fee") { - // create a backwards-compatible unincentivized channel - // set Incentivized flag to false so packet callbacks do not execute fee-handling logic and simply call app callback instead. - channel.Incentivized = false - } else if !isCompatible(cpFeeVersion, feeVersion) { - // both modules have incentivization enabled but are incompatible, so we end handshake. - // relayer must create new channel and either choose compatible versions or disable incentivization by not prefixing fee version. + feePort, appPort = splitFeePort(portID) + cpFeePort, cpAppPort = splitFeePort(counterpartyPortIdentifier) + if !isCompatible(feePort, cpFeePort) { + return error + } + if !isCompatible(cpFeeVersion, feeVersion) { return error - } else { - // both modules have compatible relayer incentivization protocols - // set the incentivized flag to true, so packet callbacks execute fee-handling logic. - channel.Incentivized = true } // call the underlying applications OnChanOpenTry callback @@ -222,14 +238,8 @@ function onChanOpenAck( portIdentifier: Identifier, channelIdentifier: Identifier, version: string) { - if hasPrefix(version, "fee") { - // both modules have compatible relayer incentivization protocols - // set the incentivized flag to true, so packet callbacks execute fee-handling logic. - channel.Incentivized = true - } else { - // create a backwards-compatible unincentivized channel - // set Incentivized flag to false so packet callbacks do not execute fee-handling logic and simply call app callback instead. - channel.Incentivized = false + if !isCompatible(version) { + return error } } @@ -245,6 +255,10 @@ function splitFeeVersion(version: string): []string { // otherwise return an empty fee version and full version as app version return []string{"", version} } + +function splitFeePort(portID: string) []string { + // identical logic to splitFeeVersion +} ``` #### Packet Callbacks @@ -259,58 +273,45 @@ function onRecvPacket(packet: Packet, relayer: string): bytes { } // if channel is incentivized, wrap the acknowledgement with forward relayer and return marshalled bytes - if channel.Incentivized { - // constructIncentivizedAck takes the app-specific acknowledgement and receive-packet relayer (forward relayer) - // and constructs the incentivized acknowledgement struct with the forward relayer and app-specific acknowledgement embedded. - ack = constructIncentivizedAck(app_acknowledgment, relayer) - return marshal(ack) - } - // otherwise just return the raw app acknowledgment. - return app_acknowledgement + // constructIncentivizedAck takes the app-specific acknowledgement and receive-packet relayer (forward relayer) + // and constructs the incentivized acknowledgement struct with the forward relayer and app-specific acknowledgement embedded. + ack = constructIncentivizedAck(app_acknowledgment, relayer) + return marshal(ack) } function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) { - if channel.Incentivized { - // If incentivization is enabled, then the acknowledgement - // is a marshalled struct containing the forward relayer address as a string (called forward_relayer), - // and the raw acknowledgement bytes returned by the counterparty application module (called app_ack). - - // get the forward relayer from the acknowledgement - // and pay fees to forward and reverse relayers. - // reverse_relayer is submitter of acknowledgement message - // provided in function arguments - // NOTE: Fee may be zero - ack = unmarshal(acknowledgement) - forward_relayer = getForwardRelayer(ack) - PayFee(packet, forward_relayer, relayer) - - // unwrap the raw acknowledgement bytes sent by counterparty application and pass it to the application callback. - app_ack = getAppAcknowledgement(acknowledgement) - } else { - // If incentivization is not enabled, then the acknowledgement is just the raw - // acknowledgement bytes returned by application, so we can pass it through directly. - app_ack = acknowledgement - } + // If incentivization is enabled, then the acknowledgement + // is a marshalled struct containing the forward relayer address as a string (called forward_relayer), + // and the raw acknowledgement bytes returned by the counterparty application module (called app_ack). + + // get the forward relayer from the acknowledgement + // and pay fees to forward and reverse relayers. + // reverse_relayer is submitter of acknowledgement message + // provided in function arguments + // NOTE: Fee may be zero + ack = unmarshal(acknowledgement) + forward_relayer = getForwardRelayer(ack) + PayFee(packet, forward_relayer, relayer) + + // unwrap the raw acknowledgement bytes sent by counterparty application and pass it to the application callback. + app_ack = getAppAcknowledgement(acknowledgement) + app.OnAcknowledgePacket(packet, app_ack, relayer) } function onTimeoutPacket(packet: Packet, relayer: string) { - if channel.Incentivized { - // get the timeout relayer from function arguments - // and pay timeout fee. - // NOTE: Fee may be zero - PayTimeoutFee(packet, relayer) - } + // get the timeout relayer from function arguments + // and pay timeout fee. + // NOTE: Fee may be zero + PayTimeoutFee(packet, relayer) app.OnTimeoutPacket(packet, relayer) } function onTimeoutPacketClose(packet: Packet, relayer: string) { -if channel.Incentivized { - // get the timeout relayer from function arguments - // and pay timeout fee. - // NOTE: Fee may be zero - PayTimeoutFee(packet, relayer) - } + // get the timeout relayer from function arguments + // and pay timeout fee. + // NOTE: Fee may be zero + PayTimeoutFee(packet, relayer) app.onTimeoutPacketClose(packet, relayer) } @@ -327,6 +328,10 @@ function getAppAcknowledgement(ack: Acknowledgement): bytes { } ``` +#### Embedded applications calling into ICS-4 + +Whenever embedded applications call into ICS-4, they must go through their parent application. For example, if ICS-20 wants to bind to the `transfer` port. Rather than calling `BindPort` directly, implementers must take care to ensure that this call passes through the top-level fee module first; which will then prepend `fee:` and cause ICS-4 to bind the top-level fee module to the `fee:transfer` port. + Note that if the embedded application uses asynchronous acks then, the `WriteAcknowledgement` call in the application must call the fee middleware's `WriteAcknowledgement` rather than calling the ICS-4 handler's `WriteAcknowledgement` function directly. ```typescript @@ -336,17 +341,25 @@ function writeAcknowledgement( acknowledgement: bytes) { // if the channel is incentivized, // retrieve the forward relayer that was stored in `onRecvPacket` - if channel.Incentivized { - relayer = privateStore.get(forwardRelayerPath(packet)) - ack = constructIncentivizedAck(acknowledgment, relayer) - ack_bytes marshal(ack) - return ics4.writeAcknowledgement(packet, ack_bytes) - } + relayer = privateStore.get(forwardRelayerPath(packet)) + ack = constructIncentivizedAck(acknowledgment, relayer) + ack_bytes marshal(ack) + return ics4.writeAcknowledgement(packet, ack_bytes) // otherwise just write the acknowledgement directly return ics4.writeAcknowledgement(packet, ack_bytes) } ``` +#### Backwards Compatibility + +Maintaining backwards compatibility with an unincentivized chain directly in the fee module, would require the top-level fee module to negotiate versions that do not contain a fee version and bind to nested ports directly without a fee port prefix. This pattern causes unnecessary complexity as the layers of nested applications increase. + +Instead, the fee module will only connect to a counterparty fee module. This simplifies the fee module logic, and doesn't require it to mimic the underlying nested application(s). + +In order for an incentivized chain to maintain backwards compatibility with an unincentivized chain for a given application (e.g. ICS-20), the incentivized chain should host both a top-level ICS-20 module and a top-level fee module that nests an ICS-20 application. + +Thus, a relayer looking to create an incentivized channel between two incentivized chains can do so by creating channel between their `fee:transfer` modules. A relayer looking to create an unincentivized channel on a backwards-compatible incentivized chain may do so by creating the channel on the `transfer` port. + #### Reasoning This proposal satisfies the desired properties. All parts of the packet flow (receive/acknowledge/timeout) can be properly incentivized and rewarded. The protocol does not specify the relayer beforehand, thus the incentivization is permissionless. The escrowing and distribution of funds is completely handled on source chain, thus there is no need for additional IBC packets or the use of ICS-20 in the fee protocol. The fee protocol only assumes existence of fungible tokens on the source chain. Using the connection version, the protocol enables opt-in incentivization and backwards compatibility. The fee module can implement arbitrary custom logic so long as it respects the callback interfaces that IBC expects. From aa3e2c72e7669ada8ee479579e3b8517b3947f6c Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Fri, 18 Jun 2021 16:48:17 +0200 Subject: [PATCH 16/22] update metadata --- spec/app/ics-029-fee-payment/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 91211b44d..05f4c4ddc 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -5,9 +5,9 @@ stage: draft category: IBC/APP requires: 20, 25, 26 kind: instantiation -author: Ethan Frey +author: Aditya Sripal , Ethan Frey created: 2021-06-01 -modified: 2021-06-01 +modified: 2021-06-18 --- ## Synopsis @@ -397,7 +397,8 @@ Coming soon. ## History -June 1 2020 - Draft written +June 8 2021 - Switched to middleware solution from implementing callbacks in ICS-4 directly. +June 1 2021 - Draft written ## Copyright From 4ead1f394486d39e2d72b1552d250d131f9acf84 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Mon, 21 Jun 2021 18:09:54 +0200 Subject: [PATCH 17/22] add middleware spec --- spec/app/ics-029-fee-payment/README.md | 2 +- spec/app/ics-030-middleware/README.md | 190 +++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 spec/app/ics-030-middleware/README.md diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 8e3438bb3..20f4786c4 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -3,7 +3,7 @@ ics: 29 title: General Fee Payment stage: draft category: IBC/APP -requires: 20, 25, 26 +requires: 4, 25, 26, 30 kind: instantiation author: Aditya Sripal , Ethan Frey created: 2021-06-01 diff --git a/spec/app/ics-030-middleware/README.md b/spec/app/ics-030-middleware/README.md new file mode 100644 index 000000000..457d667b5 --- /dev/null +++ b/spec/app/ics-030-middleware/README.md @@ -0,0 +1,190 @@ +--- +ics: 30 +title: IBC Middleware +stage: draft +category: IBC/APP +requires: 4, 25, 26 +kind: instantiation +author: Aditya Sripal , Ethan Frey +created: 2021-06-01 +modified: 2021-06-18 +--- + +## 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 middleware 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 logic. + +Without a middleware approach, developers must choose whether to place this extension to 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 seperate modules that can wrap over the end 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. + +### 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 port and version negotiation without interference from the nesting middleware. In order to do this, the middleware will prepend the portID and version with its own portID and version. In the application callbacks, the middleware must do its own version and port negotation and then strip out the prefixes before handing over the data to the nested application's callback. Middleware SHOULD always prepend the portID with its own port. This will allow the original application to also exist as a top-level module connected to the IBC Router. + +PortID: `{middleware_port}:{app_port}` + +Version: `{middleware_version}:{app_version}` + +#### Handshake Callbacks + +```typescript +function onChanOpenInit( + order: ChannelOrder, + connectionHops: [Identifier], + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyPortIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + version: string) { + middlewarePort, appPort = splitMiddlewarePort(portID) + middlewareVersion, appVersion = splitMiddlewareVersion(version) + cpMiddlewarePort, cpAppPort = splitMiddlewarePort(counterpartyPortIdentifier) + if !isCompatible(middlewarePort, cpMiddlewarePort) { + return error + } + app.OnChanOpenInit( + order, + connectionHops, + appPort, + channelIdentifier, + cpAppPort, + counterpartyChannelIdentifier, + appVersion, + ) +} + +function OnChanOpenTry( + order: ChannelOrder, + connectionHops: [Identifier], + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyPortIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + version: string, + counterpartyVersion: string) { + cpMiddlewareVersion, cpAppVersion = splitMiddlewareVersion(counterpartyVersion) + middlewareVersion, appVersion = splitMiddlewareVersion(version) + middlewarePort, appPort = splitMiddlewarePort(portID) + cpMiddlewarePort, cpAppPort = splitMiddlewarePort(counterpartyPortIdentifier) + if !isCompatible(middlewarePort, cpMiddlewarePort) { + return error + } + if !isCompatible(cpMiddlewareVersion, middlewareVersion) { + return error + } + + // call the underlying applications OnChanOpenTry callback + app.OnChanOpenTry( + order, + connectionHops, + portIdentifier, + channelIdentifier, + counterpartyPortIdentifier, + counterpartyChannelIdentifier, + cpAppVersion, + appVersion, + ) +} + +function onChanOpenAck( + portIdentifier: Identifier, + channelIdentifier: Identifier, + version: string) { + if !isCompatible(version) { + return error + } +} + +function splitMiddlewareVersion(version: string): []string { + splitVersions = split(version, ":") + middlewareVersion = version[0] + appVersion = join(version[1:], ":") + return []string{middlewareVersion, appVersion} +} + +function splitMiddlewarePort(portID: string) []string { + // identical logic to splitMiddlewareVersion +} +``` + +#### Packet Callbacks + +```typescript +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-4 Wrappers + +```typescript +function writeAcknowledgement( + packet: Packet, + acknowledgement: bytes) { + // middleware may modify acknowledgement + ack_bytes = doCustomLogic(acknowledgement) + + return ics4.writeAcknowledgement(packet, ack_bytes) +} +``` + +```typescript +function sendPacket(app_packet: Packet) { + // middleware may modify packet + packet = doCustomLogic(app_packet) + + return ics4.sendPacket(packet) +} +``` \ No newline at end of file From a2b17cb7cdc8052fc1f5c2e3ebd712bf146029ed Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 23 Jun 2021 17:21:27 +0200 Subject: [PATCH 18/22] cleanup and complete specs --- spec/app/ics-029-fee-payment/README.md | 154 ++++++++++++++----------- spec/app/ics-030-middleware/README.md | 101 +++++++++++----- 2 files changed, 159 insertions(+), 96 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 20f4786c4..f3daf6cce 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -13,7 +13,7 @@ modified: 2021-06-18 ## Synopsis This standard document specifies packet data structure, state machine handling logic, and encoding details for handling fee -payments on top of any ICS application protocol. It requires some standard packet changes, but can be adopted by any +payments on top of any ICS application protocol. It requires some changes to the acknowledgement, but can be adopted by any application, without forcing other applications to use this implementation. ### Motivation @@ -29,39 +29,41 @@ define a clear interface that can be easily adopted by any application, but not ### Desired Properties -- Incentivize timely delivery of the packet (`OnReceivePacket` called) -- Incentivize relaying acks for these packets (`OnAcknowledgement` called) -- Incentivize relaying timeouts for these packets when the timeout has expired before packet is delivered (for example as receive fee was too low) (`OnTimeout` called) +- Incentivize timely delivery of the packet (`recvPacket` called) +- Incentivize relaying acks for these packets (`acknowledgePacket` called) +- Incentivize relaying timeouts for these packets when the timeout has expired before packet is delivered (for example as receive fee was too low) (`timeoutPacket` called) - Produces no extra IBC packets - One direction works, even when destination chain does not support concept of fungible tokens -- Opt-in for each chain implementing this. eg. ICS27 with fee support on chain A could connect to ICS27 without fee support on chain B. +- Opt-in for each chain implementing this. e.g. ICS27 with fee support on chain A could connect to ICS27 without fee support on chain B. - Standardized interface for each chain implementing this extension - Support custom fee-handling logic within the same framework - Relayer addresses should not be forgeable -- Permissionless relaying +- Enable permissionless or permissioned relaying ## Technical Specification ### General Design -In order to avoid extra packets on the order of the number of fee packets, as well as provide an opt-in approach, we +In order to avoid extra fee packets on the order of the number of application packets, as well as provide an opt-in approach, we store all fee payment info only on the source chain. The source chain is the one location where the sender can provide tokens to incentivize the packet. The fee distribution may be implementation specific and thus does not need to be in the IBC spec (just high-level requirements are needed in this doc). We require that the [relayer address is exposed to application modules](https://github.com/cosmos/ibc/pull/579) for -all packet-related messages, so the modules are able to incentivize the packet relayer. `OnAcknowledgement`, `OnTimeout`, -and `OnTimeoutClose` messages will therefore have the relayer address and be capable of sending escrowed tokens to such address. -However, we need a way to reliably get the address of the relayer that submitted `OnReceivePacket` on the destination chain to +all packet-related messages, so the modules are able to incentivize the packet relayer. `acknowledgePacket`, `timeoutPacket`, +and `timeoutOnClose` messages will therefore have the relayer address and be capable of sending escrowed tokens to such address. +However, we need a way to reliably get the address of the relayer that submitted `recvPacket` on the destination chain to the source chain. In fact, we need a *source address* for this relayer to pay out to, not the *destination address* that signed the packet. +The fee payment mechanism will be implemented as IBC Middleware (see ICS-30) in order to provide maximum flexibility for application developers and blockchains. + Given this, the flow would be: -1. User/module submits a send packet on the `source` chain, along with some tokens and fee information on how to distribute them. The fee tokens are all escrowed by the fee module. -2. RelayerA submits `OnReceivePacket` on the `destination` chain. Along with this message, the *forward relayer* will submit a `payToOnSource` address where payment should be sent. -3. Destination application includes this `payToOnSource` in the acknowledgement (there are multiple approaches to discuss below) -4. RelayerB submits `OnAcknowledgement` which provides the *return relayer* address on the source chain, along with the `payToOnSource` address +1. User/module submits a send packet on the `source` chain, along with a message to the fee middleware module with some tokens and fee information on how to distribute them. The fee tokens are all escrowed by the fee module. +2. RelayerA submits `RecvPacket` on the `destination` chain. Along with the `RecvPacket` message, the *forward relayer* will submit a message to the fee middleware with the `payToOnSource` address where payment should be sent. The middleware message must be ordered first and both messages should commit atomically. +3. Destination application includes this `payToOnSource` in the acknowledgement +4. RelayerB submits `AcknowledgePacket` which provides the *return relayer* address on the source chain, along with the `payToOnSource` address 5. Source application can distribute the tokens escrowed in (1) to both the *forward* and the *return* relayers. Alternate flow: @@ -81,16 +83,16 @@ paid out in a different token. Imagine a connection between IrisNet and the Cosm Ideally the fees can easily be redeemed in native tokens on both sides, but relayers may select others. In this example, the relayer collects a fair bit of IRIS, covering its costs there and more. It also collects channel-7/ATOM vouchers from many packets. After relaying a few thousand packets, the account on the Cosmos Hub is running low, so the relayer will send those channel-7/ATOM vouchers back over channel-7 to it's account on the Hub to replenish the supply there. -The sender chain will escrow 0.003 channel-7/ATOM and 0.002 IRIS. In the case that a forward relayer submits the `MsgRecvPacket` and a reverse relayer submits the `MsgAckPacket`, the forward relayer is reqarded 0.003 channel-7/ATOM and the reverse relayer is rewarded 0.001 IRIS. In the case where the packet times out, the timeout relayer receives 0.002 IRIS and 0.003 channel-7/ATOM is refunded to the original fee payer. +The sender chain will escrow 0.003 channel-7/ATOM and 0.002 IRIS from the fee payers' account. In the case that a forward relayer submits the `recvPacket` and a reverse relayer submits the `ackPacket`, the forward relayer is rewarded 0.003 channel-7/ATOM and the reverse relayer is rewarded 0.001 IRIS while 0.001 IRIS is refunded to the original fee payer. In the case where the packet times out, the timeout relayer receives 0.002 IRIS and 0.003 channel-7/ATOM is refunded to the original fee payer. The logic involved in collecting fees from users and then paying it out to the relevant relayers is encapsulated by a separate fee module and may vary between implementations. However, all fee modules must implement a uniform interface such that the ICS-4 handlers can correctly pay out fees to the right relayers, and so that relayers themselves can easily determine the fees they can expect for relaying a packet. -### Fee Module Contract +### Fee Middleware Contract While the details may vary between fee modules, all Fee modules **must** ensure it does the following: - It must have in escrow the maximum fees that all outstanding packets may pay out (or it must have ability to mint required amount of tokens) -- It must pay the receive fee for a packet to the forward relayer specified in `PayFee` callback +- It must pay the receive fee for a packet to the forward relayer specified in `PayFee` callback (if unspecified, it must refund forward fee to original fee payer(s)) - It must pay the ack fee for a packet to the reverse relayer specified in `PayFee` callback - It must pay the timeout fee for a packet to the timeout relayer specified in `PayTimeoutFee` callback - It must refund any remainder fees in escrow to the original fee payer(s) if applicable @@ -98,7 +100,8 @@ While the details may vary between fee modules, all Fee modules **must** ensure ```typescript // EscrowPacketFee is an open callback that may be called by any module/user that wishes to escrow funds in order to // incentivize the relaying of the given packet. -// NOTE: These fees are escrowed in addition to any previously escrowed amount for the packet. +// NOTE: These fees are escrowed in addition to any previously escrowed amount for the packet. In the case where the previous amount is zero, +// the provided fees are the initial escrow amount. // They may set a separate receiveFee, ackFee, and timeoutFee to be paid // for each step in the packet flow. The caller must send max(receiveFee+ackFee, timeoutFee) to the fee module to be locked // in escrow to provide payout for any potential packet flow. @@ -115,6 +118,7 @@ function PayFee(packet: Packet, forward_relayer: string, reverse_relayer: string // pay the forward fee to the forward relayer address // pay the reverse fee to the reverse relayer address // refund extra tokens to original fee payer(s) + // NOTE: if forward relayer address is empty, then refund the forward fee to original fee payer(s). } // PayFee is a callback implemented by fee module called by the ICS-4 TimeoutPacket handler. @@ -143,14 +147,21 @@ function GetTimeoutFee(portID, channelID, sequence, relayer) Fee Since different chains may have different representations for fungible tokens and this information is not being sent to other chains; this ICS does not specify a particular representation for the `Fee`. Each chain may choose its own representation, it is incumbent on relayers to interpret the Fee correctly. +A default representation will have the following structure: + +```typescript +interface Fee { + denom: string, + amount: uint256, +} +``` + ### IBC Module Wrapper -The fee module will implement its own ICS-26 callbacks that wrap the application-specific module callbacks. This fee module middleware will ensure that the counterparty module supports incentivization and will implement all fee-specific logic. It will then pass on the request to the embedded application module for further callback processing. +The fee middleware will implement its own ICS-26 callbacks that wrap the application-specific module callbacks as well as the ICS-4 handler functions called by the underlying application. This fee middleware will ensure that the counterparty module supports incentivization and will implement all fee-specific logic. It will then pass on the request to the embedded application module for further callback processing. In this way, custom fee-handling logic can be hooked up to the IBC packet flow logic without placing the code in the ICS-4 handlers or the application code. This is valuable since the ICS-4 handlers should only be concerned with correctness of core IBC (transport, authentication, and ordering), and the application handlers should not be handling fee logic that is universal amongst all other incentivized applications. In fact, a given application module should be able to be hooked up to any fee module with no further changes to the application itself. -As mentioned above, the fee module will implement the ICS-26 callbacks, and can embed an application IBC Module. All ICS-26 callbacks in the fee module will call into the embedded application's callback. Thus, only the fee callbacks that do something additional are explicitly specified here. - #### Fee Protocol Negotiation The fee middleware will negotiate its fee protocol version with the counterparty module by prepending its own version to the application version. @@ -161,15 +172,7 @@ Ex: `fee_v1:ics20-1` The fee middleware's handshake callbacks ensure that both modules agree on compatible fee protocol version(s), and then pass the application-specific version string to the embedded application's handshake callbacks. -### Port - -The portID will similarly be nested like the channel version to allow all nested modules to negotiate their respective ports. - -Applications must note however, that the portID in its entirety will be contained in the packet. - -PortID: `fee:transfer` - -Once the fee module has done its own logic based on the full port, it will strip the fee prefix and pass along the nested `portID` to the nested module. +#### Handshake Callbacks ```typescript function onChanOpenInit( @@ -180,22 +183,19 @@ function onChanOpenInit( counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, version: string) { - // if the version and port is prefixed, - // then remove the prefix and pass the app-specific version to app callback. + // remove the prefix and pass the app-specific version to app callback. // otherwise, pass version directly to app callback. - // ensure fee ports are compatible (if they exist) - feePort, appPort = splitFeePort(portID) feeVersion, appVersion = splitFeeVersion(version) - cpFeePort, cpAppPort = splitFeePort(counterpartyPortIdentifier) - if !isCompatible(feePort, cpFeePort) { + // check that feeVersion is supported + if !isSupported(feeVersion) { return error } app.OnChanOpenInit( order, connectionHops, - appPort, + portIdentifier, channelIdentifier, - cpAppPort, + counterpartyPortIdentifier, counterpartyChannelIdentifier, appVersion, ) @@ -210,16 +210,13 @@ function OnChanOpenTry( counterpartyChannelIdentifier: Identifier, version: string, counterpartyVersion: string) { + // select mutually compatible fee version cpFeeVersion, cpAppVersion = splitFeeVersion(counterpartyVersion) feeVersion, appVersion = splitFeeVersion(version) - feePort, appPort = splitFeePort(portID) - cpFeePort, cpAppPort = splitFeePort(counterpartyPortIdentifier) - if !isCompatible(feePort, cpFeePort) { - return error - } if !isCompatible(cpFeeVersion, feeVersion) { return error } + selectFeeVersion(cpFeeVersion, feeVersion) // call the underlying applications OnChanOpenTry callback app.OnChanOpenTry( @@ -238,11 +235,23 @@ function onChanOpenAck( portIdentifier: Identifier, channelIdentifier: Identifier, version: string) { - if !isCompatible(version) { + feeVersion, appVersion = splitFeeVersion(version) + if !isSupported(feeVersion) { return error } + + // call the underlying applications OnChanOpenAck callback + app.OnChanOpenAck(portIdentifier, channelIdentifier, appVersion) } +function onChanOpenConfirm( + portIdentifier: Identifier, + channelIdentifier: Identifier) { + // fee middleware performs no-op on ChanOpenConfirm, + // just call underlying callback + app.onChanOpenConfirm(portIdentifier, channelIdentifier) + } + function splitFeeVersion(version: string): []string { if hasPrefix(version, "fee") { splitVersions = split(version, ":") @@ -255,10 +264,6 @@ function splitFeeVersion(version: string): []string { // otherwise return an empty fee version and full version as app version return []string{"", version} } - -function splitFeePort(portID: string) []string { - // identical logic to splitFeeVersion -} ``` #### Packet Callbacks @@ -272,7 +277,7 @@ function onRecvPacket(packet: Packet, relayer: string): bytes { privateStore.set(forwardRelayerPath(packet), relayer) } - // if channel is incentivized, wrap the acknowledgement with forward relayer and return marshalled bytes + // wrap the acknowledgement with forward relayer and return marshalled bytes // constructIncentivizedAck takes the app-specific acknowledgement and receive-packet relayer (forward relayer) // and constructs the incentivized acknowledgement struct with the forward relayer and app-specific acknowledgement embedded. ack = constructIncentivizedAck(app_acknowledgment, relayer) @@ -280,8 +285,7 @@ function onRecvPacket(packet: Packet, relayer: string): bytes { } function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) { - // If incentivization is enabled, then the acknowledgement - // is a marshalled struct containing the forward relayer address as a string (called forward_relayer), + // the acknowledgement is a marshalled struct containing the forward relayer address as a string (called forward_relayer), // and the raw acknowledgement bytes returned by the counterparty application module (called app_ack). // get the forward relayer from the acknowledgement @@ -330,8 +334,6 @@ function getAppAcknowledgement(ack: Acknowledgement): bytes { #### Embedded applications calling into ICS-4 -Whenever embedded applications call into ICS-4, they must go through their parent application. For example, if ICS-20 wants to bind to the `transfer` port. Rather than calling `BindPort` directly, implementers must take care to ensure that this call passes through the top-level fee module first; which will then prepend `fee:` and cause ICS-4 to bind the top-level fee module to the `fee:transfer` port. - Note that if the embedded application uses asynchronous acks then, the `WriteAcknowledgement` call in the application must call the fee middleware's `WriteAcknowledgement` rather than calling the ICS-4 handler's `WriteAcknowledgement` function directly. ```typescript @@ -339,36 +341,52 @@ Note that if the embedded application uses asynchronous acks then, the `WriteAck function writeAcknowledgement( packet: Packet, acknowledgement: bytes) { - // if the channel is incentivized, // retrieve the forward relayer that was stored in `onRecvPacket` relayer = privateStore.get(forwardRelayerPath(packet)) ack = constructIncentivizedAck(acknowledgment, relayer) ack_bytes marshal(ack) return ics4.writeAcknowledgement(packet, ack_bytes) - // otherwise just write the acknowledgement directly - return ics4.writeAcknowledgement(packet, ack_bytes) +} + +// Fee Middleware sendPacket function just forwards message to ics-4 handler +function sendPacket(packet: Packet) { + return ics4.sendPacket(packet) +} +``` + +### User Interaction with Fee Middleware + +**User sending Packets** + +A user may specify a fee to incentivize the relaying during packet submission, by submitting a fee payment message atomically with the application-specific "send packet" message (e.g. ICS-20 MsgTransfer). The fee middleware will escrow the fee for the packet that is created atomically with the escrow. The fee payment message itself is not specified in this document as it may vary greatly across implementations. In some middleware, there may be no fee payment message at all if the fees are being paid out from an altruistic pool. + +**Relayers sending RecvPacket** + +However, the message the relayer sends to the fee middleware must be standardized so that relayers do not need to implement custom code for each middleware implementation. The forward relayer SHOULD submit a `PayOnSource` Message atomically with the `RecvPacket` message(s). The `PayOnSource` message must be ordered first before any `RecvPacket` messages. The fee middleware will save the `payOnSource` address embedded in the message, and encode it into the acknowledgements for any packets that are received atomically with the `PayOnSource` message. This allows the fee middleware to incorporate the relayer-provided input into the acknowledgement without changing the underlying application. + +If the relayer does not include the `PayOnSource` message before a `RecvPacket`, then the fee middleware will simply encode the empty string into the ack and the source chain will refund the forward fee to original fee payer(s). + +```typescript +interface PayOnSourceMsg { + sourceAddress: string } ``` #### Backwards Compatibility -Maintaining backwards compatibility with an unincentivized chain directly in the fee module, would require the top-level fee module to negotiate versions that do not contain a fee version and bind to nested ports directly without a fee port prefix. This pattern causes unnecessary complexity as the layers of nested applications increase. +Maintaining backwards compatibility with an unincentivized chain directly in the fee module, would require the top-level fee module to negotiate versions that do not contain a fee version and communicate with both incentivized and unincentivized modules. This pattern causes unnecessary complexity as the layers of nested applications increase. Instead, the fee module will only connect to a counterparty fee module. This simplifies the fee module logic, and doesn't require it to mimic the underlying nested application(s). -In order for an incentivized chain to maintain backwards compatibility with an unincentivized chain for a given application (e.g. ICS-20), the incentivized chain should host both a top-level ICS-20 module and a top-level fee module that nests an ICS-20 application. - -Thus, a relayer looking to create an incentivized channel between two incentivized chains can do so by creating channel between their `fee:transfer` modules. A relayer looking to create an unincentivized channel on a backwards-compatible incentivized chain may do so by creating the channel on the `transfer` port. +In order for an incentivized chain to maintain backwards compatibility with an unincentivized chain for a given application (e.g. ICS-20), the incentivized chain should host both a top-level ICS-20 module and a top-level fee module that nests an ICS-20 application each of which should bind to unique ports. #### Reasoning -This proposal satisfies the desired properties. All parts of the packet flow (receive/acknowledge/timeout) can be properly incentivized and rewarded. The protocol does not specify the relayer beforehand, thus the incentivization is permissionless. The escrowing and distribution of funds is completely handled on source chain, thus there is no need for additional IBC packets or the use of ICS-20 in the fee protocol. The fee protocol only assumes existence of fungible tokens on the source chain. Using the connection version, the protocol enables opt-in incentivization and backwards compatibility. The fee module can implement arbitrary custom logic so long as it respects the callback interfaces that IBC expects. +This proposal satisfies the desired properties. All parts of the packet flow (receive/acknowledge/timeout) can be properly incentivized and rewarded. The protocol does not specify the relayer beforehand, thus the incentivization can be permissionless or permissioned. The escrowing and distribution of funds is completely handled on source chain, thus there is no need for additional IBC packets or the use of ICS-20 in the fee protocol. The fee protocol only assumes existence of fungible tokens on the source chain. By creating application stacks for the same base application (one with fee middleware, one without), we can get backwards compatibility. ##### Correctness -The fee module is responsible for correctly escrowing and distributing funds to the provided relayers. It is IBC's responsibility to provide the fee module with the correct relayers. The ack and timeout relayers are trivially retrievable since they are the senders of the acknowledgment and timeout message. - -The receive relayer submits the message to the counterparty chain. Thus the counterparty chain must communicate the knowledge of who relayed the receive packet to the source chain using the acknowledgement. The address that is sent back **must** be the address of the forward relayer on the source chain. +The fee module is responsible for correctly escrowing and distributing funds to the provided relayers. The ack and timeout relayers are trivially retrievable since they are the senders of the acknowledgment and timeout message. The forward relayer is responsible for providing their `payOnSource` address before sending `recvPacket` messages, so that the destination fee middleware can embed this address in the acknowledgement. The fee middleware on source will then use the address in acknowledgement to pay the forward relayer on the source chain. The source chain will use a "best efforts" approach with regard to the forward relayer address. Since it is not verified directly by the counterparty and is instead just treated as a string to be passed back in the acknowledgement, the forward relayer `payOnSender` address may not be a valid source chain address. In this case, the invalid address is discarded, the receive fee is refunded, and the acknowledgement processing continues. It is incumbent on relayers to pass their `payOnSender` addresses to the counterparty chain correctly. In the event that the counterparty chain itself incorrectly sends the forward relayer address, this will cause relayers to not collect fees on source chain for relaying packets. The incentivize-driven relayers will stop relaying for the chain until the acknowledgement logic is fixed, however the channel remains functional. @@ -377,16 +395,14 @@ We cannot return an error on an invalid `payOnSender` address as this would perm Thus, the forward relayer reward is contingent on it providing the correct `payOnSender` address when it sends the `receive_packet` message. The packet flow will continue processing successfully even if the fee payment is unsuccessful. -With the forward relayer correctly embedded in the acknowledgement, and the reverse and timeout relayers available directly in the message; IBC is able to provide the correct relayer addresses to the fee module for each step of the packet flow. +With the forward relayer correctly embedded in the acknowledgement, and the reverse and timeout relayers available directly in the message; the fee middleware will accurately escrow and distribute fee payments to the relevant relayers. #### Optional addenda -## Backwards Compatibility - -This can be added to any existing protocol without breaking it on the other side. - ## Forwards Compatibility +Not applicable. + ## Example Implementation Coming soon. diff --git a/spec/app/ics-030-middleware/README.md b/spec/app/ics-030-middleware/README.md index 457d667b5..e0cf38556 100644 --- a/spec/app/ics-030-middleware/README.md +++ b/spec/app/ics-030-middleware/README.md @@ -16,11 +16,21 @@ This standard documents specifies the interfaces and state machine logic that a ### 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 middleware 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 logic. +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 to 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. +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 seperate modules that can wrap over the end 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. +Middleware allows developers to define the extensions as seperate 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 @@ -37,12 +47,12 @@ In order to function as IBC Middleware, a module must implement the IBC applicat 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 port and version negotiation without interference from the nesting middleware. In order to do this, the middleware will prepend the portID and version with its own portID and version. In the application callbacks, the middleware must do its own version and port negotation and then strip out the prefixes before handing over the data to the nested application's callback. Middleware SHOULD always prepend the portID with its own port. This will allow the original application to also exist as a top-level module connected to the IBC Router. - -PortID: `{middleware_port}:{app_port}` +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 prepend the version with its own middleware version string. In the application callbacks, the middleware must do its own version negotation on the prefix and then strip out the prefix before handing over the data 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. Version: `{middleware_version}:{app_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. + #### Handshake Callbacks ```typescript @@ -54,20 +64,16 @@ function onChanOpenInit( counterpartyPortIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, version: string) { - middlewarePort, appPort = splitMiddlewarePort(portID) middlewareVersion, appVersion = splitMiddlewareVersion(version) - cpMiddlewarePort, cpAppPort = splitMiddlewarePort(counterpartyPortIdentifier) - if !isCompatible(middlewarePort, cpMiddlewarePort) { - return error - } + doCustomLogic() app.OnChanOpenInit( order, connectionHops, - appPort, + portIdentifier, channelIdentifier, - cpAppPort, + counterpartyPortIdentifier, counterpartyChannelIdentifier, - appVersion, + appVersion, // note we only pass app version here ) } @@ -82,14 +88,10 @@ function OnChanOpenTry( counterpartyVersion: string) { cpMiddlewareVersion, cpAppVersion = splitMiddlewareVersion(counterpartyVersion) middlewareVersion, appVersion = splitMiddlewareVersion(version) - middlewarePort, appPort = splitMiddlewarePort(portID) - cpMiddlewarePort, cpAppPort = splitMiddlewarePort(counterpartyPortIdentifier) - if !isCompatible(middlewarePort, cpMiddlewarePort) { - return error - } if !isCompatible(cpMiddlewareVersion, middlewareVersion) { return error } + doCustomLogic() // call the underlying applications OnChanOpenTry callback app.OnChanOpenTry( @@ -99,8 +101,8 @@ function OnChanOpenTry( channelIdentifier, counterpartyPortIdentifier, counterpartyChannelIdentifier, - cpAppVersion, - appVersion, + cpAppVersion, // note we only pass counterparty app version here + appVersion, // only pass app version ) } @@ -108,9 +110,22 @@ function onChanOpenAck( portIdentifier: Identifier, channelIdentifier: Identifier, version: string) { - if !isCompatible(version) { + middlewareVersion, appVersion = splitMiddlewareVersion(version) + if !isCompatible(middlewareVersion) { return error } + doCustomLogic() + + // call the underlying applications OnChanOpenTry callback + app.OnChanOpenAck(portIdentifier, channelIdentifier, appVersion) +} + +function OnChanOpenConfirm( + portIdentifier: Identifier, + channelIdentifier: Identifier) { + doCustomLogic() + + app.OnChanOpenConfirm(portIdentifier, channelIdentifier) } function splitMiddlewareVersion(version: string): []string { @@ -119,12 +134,10 @@ function splitMiddlewareVersion(version: string): []string { appVersion = join(version[1:], ":") return []string{middlewareVersion, appVersion} } - -function splitMiddlewarePort(portID: string) []string { - // identical logic to splitMiddlewareVersion -} ``` +NOTE: Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version splitting and negotiation, and will simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly. + #### Packet Callbacks ```typescript @@ -167,6 +180,8 @@ function onTimeoutPacketClose(packet: Packet, relayer: string) { } ``` +NOTE: Middleware may do pre- and post-processing on underlying application data for all IBC Module callbacks defined in ICS-26. + #### ICS-4 Wrappers ```typescript @@ -187,4 +202,36 @@ function sendPacket(app_packet: Packet) { return ics4.sendPacket(packet) } -``` \ No newline at end of file +``` + +### 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 Implementation + +Coming soon. + +## Other Implementations + +Coming soon. + +## History + +June 22, 2021 - Draft submitted + +## Copyright + +All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). From 40e193034d74c1ec57dc07ad962b0345e156a8f0 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 23 Jun 2021 17:26:36 +0200 Subject: [PATCH 19/22] add fee definitions --- spec/app/ics-029-fee-payment/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index f3daf6cce..38360798e 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -40,6 +40,14 @@ define a clear interface that can be easily adopted by any application, but not - Relayer addresses should not be forgeable - Enable permissionless or permissioned relaying +### Definitions + +`forward relayer`: The relayer that submits the `recvPacket` message for a given packet +`reverse relayer`: The relayer that submits the `acknowledgePacket` message for a given packet +`timeout relayer`: The relayer that submits the `timeoutPacket` or `timeoutOnClose` message for a given packet +`source address`: The address of a relayer on the chain that sent the packet +`destination address`: The address of a relayer on the chain that receives the packet + ## Technical Specification ### General Design From 524697fa3010ce90c22bd925447745412b130545 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Thu, 24 Jun 2021 14:45:57 +0200 Subject: [PATCH 20/22] clarify fee payment messages --- spec/app/ics-029-fee-payment/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 38360798e..e7cbaa0cc 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -368,6 +368,10 @@ function sendPacket(packet: Packet) { A user may specify a fee to incentivize the relaying during packet submission, by submitting a fee payment message atomically with the application-specific "send packet" message (e.g. ICS-20 MsgTransfer). The fee middleware will escrow the fee for the packet that is created atomically with the escrow. The fee payment message itself is not specified in this document as it may vary greatly across implementations. In some middleware, there may be no fee payment message at all if the fees are being paid out from an altruistic pool. +Since the fee middleware does not need to modify the outgoing packet, the fee payment message may be placed before or after the send packet message. However in order to maintain consistency with other middleware messages, it is recommended that fee middleware require their messages to be placed before the send packet message and escrow fees for the **next sequence** on the given channel. This way when the messages are atomically committed, the next sequence on the channel is the send packet message sent by the user, and the user escrows their fee for the created packet. + +In case a user wants to pay fees on a packet after it has already been created, the fee middleware SHOULD provide a message that allows users to pay fees on a packet with the specified sequence, channel and port identifiers. This allows the user to uniquely identify a packet that has already been created, so that the fee middleware can escrow fees for that packet after the fact. + **Relayers sending RecvPacket** However, the message the relayer sends to the fee middleware must be standardized so that relayers do not need to implement custom code for each middleware implementation. The forward relayer SHOULD submit a `PayOnSource` Message atomically with the `RecvPacket` message(s). The `PayOnSource` message must be ordered first before any `RecvPacket` messages. The fee middleware will save the `payOnSource` address embedded in the message, and encode it into the acknowledgements for any packets that are received atomically with the `PayOnSource` message. This allows the fee middleware to incorporate the relayer-provided input into the acknowledgement without changing the underlying application. From 7a2f41637eaa927b80c987072a8eaeeaecdf351d Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 7 Jul 2021 16:22:17 +0200 Subject: [PATCH 21/22] clarify forward relaying and ics4 wrapper logic --- spec/app/ics-029-fee-payment/README.md | 51 ++++++++++++++++++-------- spec/app/ics-030-middleware/README.md | 20 ++++++++++ 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index e7cbaa0cc..20b6f5610 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -68,17 +68,18 @@ The fee payment mechanism will be implemented as IBC Middleware (see ICS-30) in Given this, the flow would be: +1. Relayer registers their destination address to source address mapping on the destination chain's fee middleware. 1. User/module submits a send packet on the `source` chain, along with a message to the fee middleware module with some tokens and fee information on how to distribute them. The fee tokens are all escrowed by the fee module. -2. RelayerA submits `RecvPacket` on the `destination` chain. Along with the `RecvPacket` message, the *forward relayer* will submit a message to the fee middleware with the `payToOnSource` address where payment should be sent. The middleware message must be ordered first and both messages should commit atomically. -3. Destination application includes this `payToOnSource` in the acknowledgement -4. RelayerB submits `AcknowledgePacket` which provides the *return relayer* address on the source chain, along with the `payToOnSource` address -5. Source application can distribute the tokens escrowed in (1) to both the *forward* and the *return* relayers. +1. RelayerA submits `RecvPacket` on the `destination` chain. +1. Destination fee middleware will retrieve the source address for the given relayer's destination address (this mapping is already registered) and include it in the acknowledgement. +1. RelayerB submits `AcknowledgePacket` which provides the *return relayer* address on the source chain in the message sender, along with the source address of the *forward relayer* embedded in the acknowledgement. +1. Source fee middleware can distribute the tokens escrowed in (1) to both the *forward* and the *return* relayers and refund remainder tokens to original fee payer(s). Alternate flow: 1. User/module submits a send packet on the `source` chain, along with some tokens and fee information on how to distribute them -2. Relayer submits `OnTimeout` which provides its address on the source chain -3. Source application can distribute the tokens escrowed in (1) to this relayer, and potentially return remainder tokens to the original packet sender. +1. Relayer submits `OnTimeout` which provides its address on the source chain +1. Source application can distribute the tokens escrowed in (1) to this relayer, and potentially return remainder tokens to the original fee payer(s). ### Fee details @@ -99,6 +100,7 @@ The logic involved in collecting fees from users and then paying it out to the r While the details may vary between fee modules, all Fee modules **must** ensure it does the following: +- It must allow relayers to register their counterparty address. - It must have in escrow the maximum fees that all outstanding packets may pay out (or it must have ability to mint required amount of tokens) - It must pay the receive fee for a packet to the forward relayer specified in `PayFee` callback (if unspecified, it must refund forward fee to original fee payer(s)) - It must pay the ack fee for a packet to the reverse relayer specified in `PayFee` callback @@ -106,6 +108,13 @@ While the details may vary between fee modules, all Fee modules **must** ensure - It must refund any remainder fees in escrow to the original fee payer(s) if applicable ```typescript +// RegisterCounterpartyAddress is called by the relayer on each channelEnd and allows them to specify their counterparty address before relaying +// This ensures they will be properly compensated for forward relaying since destination chain must send back relayer's source address (counterparty address) in acknowledgement +// This function may be called more than once by relayer, in which case, latest counterparty address is always used. +function RegisterCounterpartyAddress(address: string, counterPartyAddress: string) { + // set mapping between address and counterparty address +} + // EscrowPacketFee is an open callback that may be called by any module/user that wishes to escrow funds in order to // incentivize the relaying of the given packet. // NOTE: These fees are escrowed in addition to any previously escrowed amount for the packet. In the case where the previous amount is zero, @@ -280,15 +289,20 @@ function splitFeeVersion(version: string): []string { function onRecvPacket(packet: Packet, relayer: string): bytes { app_acknowledgement = app.onRecvPacket(packet, relayer) + // get source address by retrieving counterparty address of this relayer stored in fee middleware. + // NOTE: source address may be empty or invalid, counterparty + // must refund fee in these cases + sourceAddress = getCounterpartyAddress(relayer) + // in case of asynchronous acknowledgement, we must store the relayer address so that we can retrieve it later to write the acknowledgement. if app_acknowledgement == nil { - privateStore.set(forwardRelayerPath(packet), relayer) + privateStore.set(forwardRelayerPath(packet), sourceAddress) } // wrap the acknowledgement with forward relayer and return marshalled bytes // constructIncentivizedAck takes the app-specific acknowledgement and receive-packet relayer (forward relayer) // and constructs the incentivized acknowledgement struct with the forward relayer and app-specific acknowledgement embedded. - ack = constructIncentivizedAck(app_acknowledgment, relayer) + ack = constructIncentivizedAck(app_acknowledgment, sourceAddress) return marshal(ack) } @@ -353,12 +367,14 @@ function writeAcknowledgement( relayer = privateStore.get(forwardRelayerPath(packet)) ack = constructIncentivizedAck(acknowledgment, relayer) ack_bytes marshal(ack) - return ics4.writeAcknowledgement(packet, ack_bytes) + // ics4Wrapper may be core IBC or higher-level middleware + return ics4Wrapper.writeAcknowledgement(packet, ack_bytes) } // Fee Middleware sendPacket function just forwards message to ics-4 handler function sendPacket(packet: Packet) { - return ics4.sendPacket(packet) + // ics4Wrapper may be core IBC or higher-level middleware + return ics4Wrapper.sendPacket(packet) } ``` @@ -374,16 +390,21 @@ In case a user wants to pay fees on a packet after it has already been created, **Relayers sending RecvPacket** -However, the message the relayer sends to the fee middleware must be standardized so that relayers do not need to implement custom code for each middleware implementation. The forward relayer SHOULD submit a `PayOnSource` Message atomically with the `RecvPacket` message(s). The `PayOnSource` message must be ordered first before any `RecvPacket` messages. The fee middleware will save the `payOnSource` address embedded in the message, and encode it into the acknowledgements for any packets that are received atomically with the `PayOnSource` message. This allows the fee middleware to incorporate the relayer-provided input into the acknowledgement without changing the underlying application. - -If the relayer does not include the `PayOnSource` message before a `RecvPacket`, then the fee middleware will simply encode the empty string into the ack and the source chain will refund the forward fee to original fee payer(s). +Before a relayer starts relaying on a channel, they should register their counterparty message using the standardized message: ```typescript -interface PayOnSourceMsg { - sourceAddress: string +interface RegisterCounterpartyAddressMsg { + channelID: string + portID: string + counterpartyAddress: string + address: string } ``` +It is the responsibility of the receiving chain to authenticate that the message was received from owner of `address`. The receiving chain must store the mapping from: `address -> counterpartyAddress` for the given channel. Then, `onRecvPacket` of the destination fee middleware can query for the counterparty address of the `recvPacket` message sender in order to get the source address of the forward relayer. This source address is what will get embedded in the acknowledgement. + +If the relayer does not register their counterparty address, or registers an invalid address; the acknowledgment will still be received and processed but the forward fee will be refunded to the original fee payer(s). + #### Backwards Compatibility Maintaining backwards compatibility with an unincentivized chain directly in the fee module, would require the top-level fee module to negotiate versions that do not contain a fee version and communicate with both incentivized and unincentivized modules. This pattern causes unnecessary complexity as the layers of nested applications increase. diff --git a/spec/app/ics-030-middleware/README.md b/spec/app/ics-030-middleware/README.md index e0cf38556..aa659e720 100644 --- a/spec/app/ics-030-middleware/README.md +++ b/spec/app/ics-030-middleware/README.md @@ -53,6 +53,26 @@ Version: `{middleware_version}:{app_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 + +```typescript +// Middleware implements the ICS26 Module interface +interface Middleware extends ICS26Module { + app: ICS26Module // middleware has acccess to an underlying application which may be wrapped by more middleware + ics4Wrapper: ICS4Wrapper // middleware has access to ICS4Wrapper which may be core IBC Channel Handler or a higher-level middleware that wraps this middleware. +} +``` + +```typescript +// 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(packet: Packet) + writeAcknowledgement(packet: Packet, ack: Acknowledgement) +} +``` + #### Handshake Callbacks ```typescript From c838afb258ecaf9c2d6085c1fdc6fd10d6a22cc2 Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Wed, 7 Jul 2021 16:24:33 +0200 Subject: [PATCH 22/22] update correctness section --- spec/app/ics-029-fee-payment/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/app/ics-029-fee-payment/README.md b/spec/app/ics-029-fee-payment/README.md index 20b6f5610..35caddfef 100644 --- a/spec/app/ics-029-fee-payment/README.md +++ b/spec/app/ics-029-fee-payment/README.md @@ -419,12 +419,12 @@ This proposal satisfies the desired properties. All parts of the packet flow (re ##### Correctness -The fee module is responsible for correctly escrowing and distributing funds to the provided relayers. The ack and timeout relayers are trivially retrievable since they are the senders of the acknowledgment and timeout message. The forward relayer is responsible for providing their `payOnSource` address before sending `recvPacket` messages, so that the destination fee middleware can embed this address in the acknowledgement. The fee middleware on source will then use the address in acknowledgement to pay the forward relayer on the source chain. +The fee module is responsible for correctly escrowing and distributing funds to the provided relayers. The ack and timeout relayers are trivially retrievable since they are the senders of the acknowledgment and timeout message. The forward relayer is responsible for registering their sourcer address before sending `recvPacket` messages, so that the destination fee middleware can embed this address in the acknowledgement. The fee middleware on source will then use the address in acknowledgement to pay the forward relayer on the source chain. -The source chain will use a "best efforts" approach with regard to the forward relayer address. Since it is not verified directly by the counterparty and is instead just treated as a string to be passed back in the acknowledgement, the forward relayer `payOnSender` address may not be a valid source chain address. In this case, the invalid address is discarded, the receive fee is refunded, and the acknowledgement processing continues. It is incumbent on relayers to pass their `payOnSender` addresses to the counterparty chain correctly. +The source chain will use a "best efforts" approach with regard to the forward relayer address. Since it is not verified directly by the counterparty and is instead just treated as a string to be passed back in the acknowledgement, the registered forward relayer source address may not be a valid source chain address. In this case, the invalid address is discarded, the receive fee is refunded, and the acknowledgement processing continues. It is incumbent on relayers to register their source addresses to the counterparty chain correctly. In the event that the counterparty chain itself incorrectly sends the forward relayer address, this will cause relayers to not collect fees on source chain for relaying packets. The incentivize-driven relayers will stop relaying for the chain until the acknowledgement logic is fixed, however the channel remains functional. -We cannot return an error on an invalid `payOnSender` address as this would permanently prevent the source chain from processing the acknowledgment of a packet that was otherwise correctly received, processed and acknowledged on the counterparty chain. The IBC protocol requires that incorrect or malicious relayers may at best affect the liveness of a user's packets. Preventing successful acknowledgement in this case would leave the packet flow at a permanently incomplete state, which may be very consequential for certain IBC applications like ICS-20. +We cannot return an error on an invalid source address as this would permanently prevent the source chain from processing the acknowledgment of a packet that was otherwise correctly received, processed and acknowledged on the counterparty chain. The IBC protocol requires that incorrect or malicious relayers may at best affect the liveness of a user's packets. Preventing successful acknowledgement in this case would leave the packet flow at a permanently incomplete state, which may be very consequential for certain IBC applications like ICS-20. Thus, the forward relayer reward is contingent on it providing the correct `payOnSender` address when it sends the `receive_packet` message. The packet flow will continue processing successfully even if the fee payment is unsuccessful.