Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ICS29: General Relayer Fee Protocol #578

Closed
AdityaSripal opened this issue May 26, 2021 · 41 comments
Closed

ICS29: General Relayer Fee Protocol #578

AdityaSripal opened this issue May 26, 2021 · 41 comments
Labels
app Application layer. brainstorming Open-ended brainstorming. standard New standard.

Comments

@AdityaSripal
Copy link
Member

Fees aren't baked into IBC protocol because IBC does not have the concept of fungible tokens in-built to the protocol. This is an important and correct decision to make IBC as general as possible. IBC should still work with ledgers that have no tokens, and in contexts where a relayer fee isn't necessary.

However, we should still have a general approach to incentivizing application packets getting relayed. This issue is a place to collect discussion on possible approaches.

@AdityaSripal
Copy link
Member Author

AdityaSripal commented May 26, 2021

I have a rough sketch for a general idea that I want to pitch here before writing it up into a broader spec.

I propose we still keep the notion of tokens away from core IBC, and implement a separate module called ibc-fee that will handle incentivizing relayers.

This ibc-fee module can implement a separate message:

type IBCFeeMsg struct {
     string channelID
     string portID
     uint64 sequence
     receiveFee sdk.Coin
     ackFee sdk.Coin
     timeoutFee sdk.Coin
}

This message uniquely identifies the packet to be incentivized and specifies the fees to be paid for each potential action (receive/ack/timeout).

Any IBC application can now "opt-in" to support incentivization by simply calling a PayFee callback in OnAckPacket and OnTimeoutPacket.

func OnAcknowledgeent() {
    // ...
    // get receive relayer from acknowledgement. This only works if counterparty app supports incentivization
    // if empty, refund receiveFee
    // get ack relayer from message signer
    ibcfeeKeeper.PayFee(channelID, portID, sequence, receive_relayer, ack_relayer)
}
func OnTimeoutPacket() {
    // ... 
    ibcfeekeeper.PayTimeoutFee(channelID, portID, sequence, timeout_relayer)
}

The ibc-fee Keeper is in charge of paying out the relevant relayers and refunding any unused fees. This will allow any ibc app to be incentivized so long as the sending chain has some notion of fungible tokens, without introducing the concept into core IBC. Blockchains that have no concept of fungible tokens will need some other method to incentivize relayers.

There will need to be some channel registration so fees aren't sent to channels that do not support incentivization.

The above IBCFeeMsg explicitly includes a sequence, which is annoying for ICS-20 since sequence is not known in advance of sending transfer message. Rather than requiring users to send 2 separate messages, we could create a special fee msg that just incentivizes the latest sequence on specified channel. This allows users to send Transfer msg and incentivize it in a single MultiMsg Tx.

I think this is a relatively simple general fee solution that still avoids introducing fees into core IBC. Rather it allows opt-in by apps on chains with fungible tokens to incentivize their packets getting relayed through the ibc-fee module.

@ethanfrey
Copy link
Contributor

Thank you for opening this issue, I think it will lead to a much deeper discussion on the fee issue.

My first question is what is the desired behavior of how fees are paid out? It seems the user must pay all 3 fees when sending the packet. And will be refunded 1 or 2 of them. While configurable, it seems overly complex.

@ethanfrey
Copy link
Contributor

My second comment is that it feels weird to jump into ibc-go implementation details here. Let us first discuss it on the level of spec and desired behavior. It should function for all possible chains, including solo machines.

I would welcome a pr that captures much of this thinking on layer of spec so we can do line by line comments.

@colin-axner
Copy link
Contributor

Some thoughts/questions:

  • how does refunding work?
  • how does receiveFee work if this is paid on a single chain?
  • why does fee need to be a separate message? Why can't we just wrap the existing TransferMsg with the fee logic?
  • as a user, I'm willing to pay a higher fee for a failed ack and a lower fee for a successful ack. Fail/success is not natural in the general case since acks can be anything, but I think it could be worth while just adding another field if we already specify receive, ack, and timeout (ie receive, failAck, successAck, timeout)

Food for thought:
What about using ICS27 to allow a different denom to be paid on the receiving chain? I think this is an interesting problem worth discussion. Probably not the solution for a generalized fee? But it is certainly a problem that will reoccur. I want to initiate an IBC send on chainA and use an account on chainB to pay a fee to someone on chainB. This requires associating an account on chainA with an account on chainB

@ethanfrey
Copy link
Contributor

I would reiterate to have this as a spec-first design (no talk of ibc-go) and focus on desired properties like @colin-axner mentioned (where the fees go).

My thoughts on minimal changes are that:

  1. The receiving chain does not need to be aware of the fees at all. This fee info thus does not need to be stored in the on-the-wire packet, but is an optional extension that can be stored separately from the packet by the sender ibc implementation
  2. The only info needed to be added to the ibc spec to allow this to be calculated on the sending chain is the address that submitted the packet on the remote chain. This could be passed back along with the acknowlegdement (or changing the acknowledgement data structure)

I also agree that this should only focus on packet incentivization. Client/Connection/Channel creation are 1-time events and will be paid by the person who cares about them. Packets may be created by many modules with no access to a relayer and are the key point to focus on incentives for.

Furthermore, I would propose all fee storage/deduction to be handles in the ics4 channel/packet implementation and this information should never be exposed to the application logic. Either the app handles all fees in a custom way (like my proposal #577), or the ibc handlers handle all fees in a standard way (this proposal). But we should not mix responsibility between both apps and core ibc handlers - that can only create more error cases.

@ethanfrey
Copy link
Contributor

Also, I realize there is one fundamental issue with this design.

While it seems to make sense to pay the relayer on the source chain for work on the receiving chain, how can we do this? The relayer has a different address on each chain and there is no guaranteed correlation between them.

Example:

I set up a relayer between a CosmWasm chain and the cosmos hub.

The address cosmos17fzz8v57p4uajhn2peysjgavyvveky2vnpemg6 successfully submitted the receive packet on the Cosmos Hub (thank you).

The address wasm10tsncqntt778xpkt7d5uwhul2rcmlj5c3larlj later submits an ack on the CosmWasm chain, and along with the ack, the info of the remote signer is included cosmos17fzz8v57p4uajhn2peysjgavyvveky2vnpemg6 in some provable manner. How do I pay cosmos17fzz8v57p4uajhn2peysjgavyvveky2vnpemg6?

  • I cannot use bank.MsgSend there as that is an invalid address
  • If I send the tokens over ics20 to the remote chain, I just create an infinite loop, using one packet to pay the fees of the last one, and this next packet must be incentivized itself
  • We could maintain some lookup, so I know that cosmos17fzz8v57p4uajhn2peysjgavyvveky2vnpemg6 on cosmoshub-4 is the same as wasm10tsncqntt778xpkt7d5uwhul2rcmlj5c3larlj on CosmWasm chain, but this is hard to prove (anyone else can claim they are cosmos17fzz8v57p4uajhn2peysjgavyvveky2vnpemg6 to claim fees), requires more complex interchain coordination, and leaks information that maybe not everyone is happy to share.

This is a fundamental issue in the "pay ReceivePacket relayer as part of AcknowledgePacket flow" design. And let's discuss this theoretical point before looking any more into details.

@colin-axner
Copy link
Contributor

This is a fundamental issue in the "pay ReceivePacket relayer as part of AcknowledgePacket flow" design. And let's discuss this theoretical point before looking any more into details.

More generally, the issue is paying for execution/work done on a remote chain correct? I see the same problem occurring in a consumer <> service IBC app model.

To step back a little, it does seem like doing all fee payments on a single chain is the ideal option, but in the general form of this problem, we can either pay for this remote execution on the source chain or receiving chain.

So some questions to be answered:

  1. In what cases (for remote execution fee payment in general) do we want to pay on the remote chain? And in what cases do we want to pay on the source chain?
  2. If we pay on the source, how do we pay the address that did the work on the remote
  3. If we pay on the remote, how do we know what address to pay the fee?

I think it is possible that it is desirable to pay on source or remote depending on context. I also think it is possible to build a module/app that can handle both situations and handle relayer incentivization as a use case

@milosevic
Copy link
Contributor

milosevic commented May 27, 2021

Thank you Aditya for opening an issue and coming up with a strawmen solution. I agree with Frey that having more detailed proposal would make it easier to point potential issues, but that takes time and it seems we can make some progress also at this level. There are several things I really like in this proposal: 1) it is nicely decoupled from core ibc , 2) it makes assumptions on existence of fungible tokens (incetivization layer) only on the sending side and 3) focus only on packets (assuming handshake is created by those that needs it). So my understanding is that paying fee would happen only as part of ack/timeout flow, i.e., a relayer that proves that packet is received or timed out gets fee on the sending side. This is nice and simple as relayer can include it's address as part of ack/timeout packet. The missing piece is incentivising relayer to pass receive packet. One idea is embedding relayer address on the sending chain as part of receive packet, so it can be added to packet acknowledgment, so no matter if different relayer sends ack, the relayer that sends receive packet will get paid.

@ethanfrey
Copy link
Contributor

The missing piece is incentivising relayer to pass receive packet.

Which is actually the key point to incentivize in my mind. Otherwise, you just pay relayers to pass back timeouts

One idea is embedding relayer address on the sending chain as part of receive packet, so it can be added to packet acknowledgment, so no matter if different relayer sends ack, the relayer that sends receive packet will get paid.

This would simplify the issue, but this solution may well get is in legal problems. One of the requirements I took with the ics20-2 proposal is that anyone can relay any packet and get paid. Unless we hear differently in a legal opinion, let's keep this requirement in all designs.

@AdityaSripal
Copy link
Member Author

AdityaSripal commented May 27, 2021

Thank you all for the feedback. Definitely this should be written out as a spec. I've written it up in SDK terms just to demonstrate the general point, it's also the easiest way for me to writeup pseudocode while we still discuss at this level. It's also worth noting that the only things that needs to be consistent across implementations is how to provide the the address that submitted the packet on the remote chain and the callback interfaces used by OnAckPacket and OnTimeoutPacket. This is a benefit of this approach since the ibc-fee message and the ibc-fee handler could vary completely from chain to chain without a problem. Maybe some chains will prioritize UX and just ask for a single fee that gets split up in a default way, others could provide a lot more flexibility. We should come up with a general default implementation, but we do not need to enforce a way to handle this info for all chains

The ibc-fee incentivization is orthogonal to core IBC, and only requires that apps provide the correct information on who relayed what.

The relayer has a different address on each chain and there is no guaranteed correlation between them.

This is a great point, thank you for pointing it out.

One idea is embedding relayer address on the sending chain as part of receive packet, so it can be added to packet acknowledgment, so no matter if different relayer sends ack, the relayer that sends receive packet will get paid.
This would simplify the issue, but this solution may well get is in legal problems. One of the requirements I took with the ics20-2 proposal is that anyone can relay any packet and get paid. Unless we hear differently in a legal opinion, let's keep this requirement in all designs.

Correct me if I'm wrong, I believe you are misunderstanding Zarko's proposal. It's not the case that the relayer's address gets put into the packet by the user. But your point is solvable if we just add a payto field in MsgReceivePacket

MsgReceivePacket {
packet Packet
payToAddress string // this is the address of receivePacket relayer on source chain
signer string // this is the address of receivePacket relayer on dest chain
}

Anyone can still relay the packet. The user is not making any choice here. The relayer themselves is adding their preferred payTo address into the MsgReceivePacket, in the same way that they fill in the signer information.

When the receiving chain writes the ack, they just need to take the payToAddress and put it in the ACK. They don't need to understand anything about it other than that its a string.
The payToAddress will be a valid address on the sender chain that can be paid out.

This incentivizes relaying the original packet, which as you said is the most important part of packet flow to incentivize

@AdityaSripal
Copy link
Member Author

how does refunding work?

This is up to ibc-fee handler and could vary from chain-to-chain. A default implementation would be that the sum total of fees get escrowed on ibc-fee msg handler.

When OnAckPacket gets called:
ReceiveRelayer gets recv fee, ack relayer gets ack fee, timeout fee gets sent back to original incentivizer

When OnTimeoutPacket gets called:
Timeout fee gets sent to timeout relayer
Recv fee and ack fee get sent back to original incentivizer

how does receiveFee work if this is paid on a single chain?

The address of the relayer who sent packet on remote chain gets sent inside the ack from the receiving chain. As noted above, this must be the relayer's address on sender chain.

why does fee need to be a separate message? Why can't we just wrap the existing TransferMsg with the fee logic?

We want it to be a separate message because it should work for all ibc-apps. We don't want to be hardcoding in the fee fields into every single ibc-app message. We also don't want individual ibc-apps to make assumptions about how the fee is structured. In my above example, I put in three separate fees. But another chain might choose one fee, or they may choose four (one for ack_success and one for ack_fail). If we hardcode it into application message, it will only be compatible with one type of ibc-fee. If it's a separate message, it will be compatible with any ibc-fee module that respects the same interface.
By keeping it a separate message you preserve very similar UX through multimsg tx, but you gain modularity/separation of concerns/flexibility. It's a similar reasoning for why fee information are not hardcoded into sdk.Msg but are part of superset sdk.Tx.

as a user, I'm willing to pay a higher fee for a failed ack and a lower fee for a successful ack. Fail/success is not natural in the general case since acks can be anything, but I think it could be worth while just adding another field if we already specify receive, ack, and timeout (ie receive, failAck, successAck, timeout)

This is possible. Just a tradeoff between simplicity/flexibility. The nice thing is that different chains can choose whatever they want. Since fee handling is all on same chain, they don't need to agree on incentivization so long as they agree on how to communicate remote relayer.

What about using ICS27 to allow a different denom to be paid on the receiving chain? I think this is an interesting problem worth discussion. Probably not the solution for a generalized fee? But it is certainly a problem that will reoccur. I want to initiate an IBC send on chainA and use an account on chainB to pay a fee to someone on chainB. This requires associating an account on chainA with an account on chainB

I don't think we should rely on an IBC app for a general fee protocol. The general fee protocol should require as few assumptions about the two chains as possible. This proposal requires an assumption that sending chain has fungible tokens, and receiving chain will send back payTo address of remote relayer in its ack.

@AdityaSripal
Copy link
Member Author

To step back a little, it does seem like doing all fee payments on a single chain is the ideal option, but in the general form of this problem, we can either pay for this remote execution on the source chain or receiving chain.

I think it only makes sense to pay on source since that is the only chain that can escrow. How would you pay everything on receiving chain without escrowing on sending chain in the first place? Once you've done that, you've added fee-handling logic in both places (and all underlying assumptions of fungible tokens) when you only needed it in one place.

If you just handle everything on sender chain, then your ibc-fee protocol doesn't even use IBC in the first place which I think is a benefit.

@ethanfrey
Copy link
Contributor

Aditya, thank you for pointing this out:

MsgReceivePacket {
packet Packet
payToAddress string // this is the address of receivePacket relayer on source chain
signer string // this is the address of receivePacket relayer on dest chain
}

I was understanding it was added to the Packet produced on the source chain. If the relayer itself adds it when processing receive, that would be much cleaner. 💯

@milosevic
Copy link
Contributor

This proposal looks very promising to me. I guess we need feedback from more people (@dtribble, @zmanian, @cwgoes, @charleenfei) and then we can potentially try to align/address potential concerns during next IBC call. And then if all is good, we can go after spec proposal. Does this make sense @ethanfrey?

@ethanfrey
Copy link
Contributor

Trying to summarize again...

  • All fee escrow/payment logic is done in the source chain - this is implementation specific but does not need to be in the ibc spec (discuss in detail in ibc-go)
  • This fee info per packet should be easily available to the relayers (which have chain-aware implementations), so they know what incentive they can expect for their services
  • The relayer should submit their source-chain address along with the IbcReceiveMsg in a chain-aware format
  • The acknowledgement committed on the destination chain must encode this payToOnSource address

So, most of the implementation discussion can go into ibc-go and focus on how the cosmos-sdk will handle it. The only part that will need agreement in the IBC standard is the last point: "The acknowledgement committed on the destination chain must encode this payToOnSource address"

Is this correct?

@ethanfrey
Copy link
Contributor

If so, I see two potential ways to do this:

  1. every ICS application spec will need to pass this field in their app-specific acknowledgement messages. This must be passed both in success and error cases, so we should provide a different standard acknowledgement envelope that also includes this field. Any app using that will be able to pass the fee payer info into lower levels.
  2. We encode this when writing every acknowledgement. This means the data is always there on the ICS core layer, so the actual fee handling can be done one level below the application.

To me (1) seems easier to slowly opt-in to for existing chains. (2) would be a bit harder to do in a backwards-compatible way with the current cosmos hub (but with some clever design it should be doable), but it will really make incentivization a first-class citizen.

(2) is my preference, but it seems you were discussing (1) above. If this is this only thing that needs to change in core ibc, we should spend time to focus on that.

@ethanfrey
Copy link
Contributor

I think part of my reservation of (1) and placing the fee refunding logic into the ibc application comes from writing ibc-enabled CosmWasm contracts. Forcing each of them to properly escrow and release fees, or providing them some sdk-callback to release the fees seems quite difficult/error-prone to me.

I assume Agoric would have a similar situation, where they would like to decouple the ibc-speaking contracts from the fee payment.

That said, if it is the preferred mechanism, I will figure out a way to support it. But if we assume a world where there will be dozens or 100s of ibc applications, we should really consider handling fees at a lower-level (like the cosmos-sdk ante handlers do rather than force every module to check and enforce proper fee payment)

@dtribble
Copy link

There were a few elements that I didn't understand, but this largely matches the approach I was considering. I'll post a few things separately to then try to clarify that.

Example use cases:

  1. chain A puts together a fund to tip deliveries of successful transfer to/from chain A's bank from [chain B]
  2. dapps can reward deliveries of successful transactions (not spam)
  3. a DEX can pay different fees for SELL orders and BUY orders, depending on market conditions
  4. an individual can pay for delivery of their messages

A few key requirements:

  1. Neither the sender nor receiver select the relayer; relayers are still permissionless
  2. Fees can be paid without the receiving dapps participation (e.g., by a 3rd party)
  3. Dapps can participate in paying fees
  4. This does not require additional packets per packet. Some scenarios may require additional transactions (e.g., an occasional roll-up payment for accumulated fees), but nothind of the same cardinality

@dtribble
Copy link

To figure out the parts I don't understand, let me summarize the variant I was thinking about and then confirm that it's covered. Some variants may be infeasible even if the rest is feasible of course.

  1. when a relayer relays a packet, it adds a "delivery tips" address for the destination chain as part of its submission. Presumably that's in the packet, but could be separate metadata in IBC.
  2. the recipient module can query for the tips address and send it a payment (locally, on chain, with no IBC required)
  3. an external module can watch events and pay fees based on packets delivered. This keeps it out of the dapp code. Multiple payors including the dapp can contribute fees
  4. aggregate transaction data could be accumulated for off-chain processes to figure out who is owed what, and then airdrop fees. This is of course lower assurance that payment happens, but allows complex payment arrangement or computation (e.g., relayers get paid at the completion of a long transaction sequence, based on its success and timeliness).
    1. payments of tips can be aggregated for efficiency/convenience; e.g., collecting how many successful deliveries and then paying fees base d

Because the recipient module can determine tips based on any application content, a DEX can for example pay a higher fee for delivering buy orders rather than sell orders when the market is urgently selling.

For background, this was inspired by a proposal for incentivizing wallets: transactions submitted from a wallet include a "UI tips" field. Then any app or external process can send rewards to the wallet that led to valuable transactions. To be clear, my thoughts about the relaying have nothing to do with wallets; it was just an inspiration.

@dtribble
Copy link

So the things I don't understand yet:

  • All fee escrow/payment logic is done in the source chain - this is implementation specific but does not need to be in the ibc spec (discuss in detail in ibc-go)

I would expect that, in a send/ack pair, the destination chain would tip the send and the source chain would tip the ack. In the simplest case, they have nothing to do with each other, and each chain is just tipping all deliveries between chain A and B out of a "pseudo-altruism" fund to e.g., support connectivity between the chains.

  • how does refunding work?

I don't understand the issue around refunds at all. Why is this relevant here?

  • This fee info per packet should be easily available to the relayers (which have chain-aware implementations), so they know what incentive they can expect for their services

This seems like it could be outside the scope of any of this. e.g., If Agoric and/or AiB sponsored traffic between the Agoric AMM and Gravity DEX, we could just say it, and folks would setup relayers and clean up (you know you would @jackzampolin). That's not incredibly robust but it sure covers a lot of scenarios for years.

BTW I completely agree with Ethan's points about figuring our the right structure and flows before any technical details.

@AdityaSripal
Copy link
Member Author

Trying to summarize again...

  • All fee escrow/payment logic is done in the source chain - this is implementation specific but does not need to be in the ibc spec (discuss in detail in ibc-go)
  • This fee info per packet should be easily available to the relayers (which have chain-aware implementations), so they know what incentive they can expect for their services
  • The relayer should submit their source-chain address along with the IbcReceiveMsg in a chain-aware format
  • The acknowledgement committed on the destination chain must encode this payToOnSource address

So, most of the implementation discussion can go into ibc-go and focus on how the cosmos-sdk will handle it. The only part that will need agreement in the IBC standard is the last point: "The acknowledgement committed on the destination chain must encode this payToOnSource address"

Is this correct?

This is all correct.

@AdityaSripal
Copy link
Member Author

AdityaSripal commented May 27, 2021

If so, I see two potential ways to do this:

  1. every ICS application spec will need to pass this field in their app-specific acknowledgement messages. This must be passed both in success and error cases, so we should provide a different standard acknowledgement envelope that also includes this field. Any app using that will be able to pass the fee payer info into lower levels.
  2. We encode this when writing every acknowledgement. This means the data is always there on the ICS core layer, so the actual fee handling can be done one level below the application.

To me (1) seems easier to slowly opt-in to for existing chains. (2) would be a bit harder to do in a backwards-compatible way with the current cosmos hub (but with some clever design it should be doable), but it will really make incentivization a first-class citizen.

(2) is my preference, but it seems you were discussing (1) above. If this is this only thing that needs to change in core ibc, we should spend time to focus on that.

Yes, I initially discussed (1) but I think (2) works just as well and offers some advantages.

All ibc apps are going to be writing in the payTo address in the exact same way and they will be calling the ibc-fee callbacks in the exact same way. So it is better to lift this burden into core IBC.

The one caveat is that we should definitely make it such that IBC apps can register their own custom ibc-fee module to be used. This way one app could use the default ibc-fee whose callback will require user to payout fee from escrowed funds. While another app could register a custom ibc-fee module that pays out fees from a commonly held fee pool.
This doesn't need to happen for MVP, we can stick to a default for all apps. But we should definitely design the feature to support this future extension.

The only reason I see to support (1) long-term, is if we want ibc-apps to be able to explicitly disallow incentivization for their packets. I don't see any reason for this. Users who don't need incentivization can set 0 fees. Any fee they do set is additional incentive for relayer. App doesn't need to preemptively ban this. The other reason is as you said, (1) may be slightly easier to implement in a non-breaking way on a short time horizon. In the worst case, (1) could be a non-breaking MVP for an eventual solution that looks more like (2)

@AdityaSripal
Copy link
Member Author

There were a few elements that I didn't understand, but this largely matches the approach I was considering. I'll post a few things separately to then try to clarify that.

Example use cases:

  1. chain A puts together a fund to tip deliveries of successful transfer to/from chain A's bank from [chain B]
  2. dapps can reward deliveries of successful transactions (not spam)
  3. a DEX can pay different fees for SELL orders and BUY orders, depending on market conditions
  4. an individual can pay for delivery of their messages

A few key requirements:

  1. Neither the sender nor receiver select the relayer; relayers are still permissionless
  2. Fees can be paid without the receiving dapps participation (e.g., by a 3rd party)
  3. Dapps can participate in paying fees
  4. This does not require additional packets per packet. Some scenarios may require additional transactions (e.g., an occasional roll-up payment for accumulated fees), but nothind of the same cardinality

Most of your use cases can be supported just by using a custom fee module. The one thing that isn't considered in the original proposal is the ability to condition fee payout on the type of packet you are sending. If you look at the callbacks I sketched out in original comment, they don't contain the packet.

We could add the packet to the callback arguments. This would allow the ibc-fee module to use the packet as part of its logic when it does fee payout. So it isn't a problem technically.

My two concerns are if there is any legal implications with paying fees based on packet type (especially the DEX example you posted above). The other is the relayer experience. Ideally the relayer knows exactly how much they will get paid before they relay the packet, and this won't hold in your DEX example if the fee is subject to changing market conditions.

It's an interesting point of discussion that we should discuss at next IBC call.

All of your key requirements are met.

@AdityaSripal
Copy link
Member Author

I would expect that, in a send/ack pair, the destination chain would tip the send and the source chain would tip the ack. In the simplest case, they have nothing to do with each other, and each chain is just tipping all deliveries between chain A and B out of a "pseudo-altruism" fund to e.g., support connectivity between the chains.

Hmm I think we're working with different canonical usecases in mind. My canonical usecase is a single user incentivizing their own packet flow, with pseudo-altruistic funds being a special case. These funds could still work. They would just be incentivizing packet flow coming out of their chain. So chainA could incentivize packets going to chainB, and incentivize acks/timeouts coming back. The proposal I have does not include a way to incentivize a relayed packet from the receiving chain.

@ancazamfir
Copy link
Contributor

ancazamfir commented May 28, 2021

A few questions (not sure I fully understand the proposal):

  • for the new IBCFeeMsg message, there is no need for receiveFee, right?

  • don't we also need to add the pay_to to the MsgAcknowledgment and also a proof? So, the flow would be something like:

    • chain A sends IBCFeeMsg(channel, ack_fee, timeout_fee) + IBCSendMsg (Packet)
    • relayer
      • gets send_packet event (like today)
      • gets new ibc_fee event (needs to be easy to correlate with the corresponding send_packet. Relayer should also be able to query this on start. Will this be again from query_tx() or will it be stored?
      • relays MsgReceive(packet, proofs, signer, pay_to) -> extra field, added by relayer, needs ICS04 and proto update
    • chain B recv_packet handler stores the pay_to along with the ack bytes (when write ack happens)
    • relayer
      • gets the write_acknowledment event. This will include pay_to in addition to the old attributes.
      • gets the proof_pay_to in addition to the old proof_ack
      • relays MsgAcknowledgment(packet, ack, proof_ack, pay_to, proof_pay_to) -> extra fields, needs ICS04, ICS24 and proto updates
    • chain A acknowledgment handler verifies proofs, calls fee module to pay the relayer's pay_to the ack_fee
  • to support backwards compatibility a new channel version is required. Relayer and ics04 handler need to support multiple versions. Or we create a new channel type?

  • for timeouts pay_to cannot be proven

  • ackFee - ideally set to cover the 2 Tx fees, the one of the remote chain is not easy to estimate

  • timeoutFee - ideally covers the Tx fee

  • for unordered channels a relayer may selectively relay packets

  • for ordered channels a relayer could wait for others to relay small fee sequences, or just relay hoping for higher fees in the next sequences, or delay relaying until high fee sequences show up.

  • there is no incentive to relay acknowledgment with pay_to not matching the relayer's account.

@ethanfrey
Copy link
Contributor

My two concerns are if there is any legal implications with paying fees based on packet type (especially the DEX example you posted above). The other is the relayer experience. Ideally the relayer knows exactly how much they will get paid before they relay the packet, and this won't hold in your DEX example if the fee is subject to changing market conditions.

I agree. We can pass the relevant info into the dApps to incentivize, but dynamic values are unlikely to be useful. These payments are only useful if relayers can easily and reliably determine how much they will receive for relaying a packet before they relay it. Ideally with <1 query per packet.

I would support passing both the signer (packet submitted) and payToOnSource address to the ibc application, so it could theoretically pay directly from the dApp in a fully dynamic way, so Dean's use cases could be handled without future breaking changes. But I see them as orthogonal to a sender paying fees for their own packet, and app-specific so not requiring any further extensions to the core ibc protocol.

@ethanfrey
Copy link
Contributor

Thank you for your comments, Anka. I agree with most of this, but consider the go structs above a loose prototype, and I would propose different go implementations that also handle this as a decorator architecure like Aditya suggested above. As I mentioned here with Aditya's agreement, there is only one change needed to the IBC specs. I would like to just focus on that now and once we have agreement there start the go details.

chain A sends IBCFeeMsg(channel, ack_fee, timeout_fee) + IBCSendMsg (Packet)

This is go implementation detail. I would just extend the IBCSendMsg, but again, this is after we have a clear spec.

relays MsgReceive(packet, proofs, signer, pay_to) -> extra field, added by relayer, needs ICS04 and proto update

This field doesn't need to be in the packet and just needs to be in the go implementation IBCMsgReceive, thus I do not expect a need for ICS04 update, just ibc-go.

for timeouts pay_to cannot be proven

Which is good. No one submitted anything on the remote chain, so no one should get paid for that.

there is no incentive to relay acknowledgment with pay_to not matching the relayer's account.

There is. pay_to in the acknowledgement will determine which account gets the receiveFee for submitting the packet on the destination chain. Whoever submits the acknowledgement will get the ackFee. These may be different parties (but obviously the pay_to address has more incentive.

@ethanfrey
Copy link
Contributor

chain B recv_packet handler stores the pay_to along with the ack bytes (when write ack happens)

This is the key change and what affects the payloads and proofs between chains. I would like to open a PR (likely monday) explaining how I would extend this. In short, I would replace acknoweldgement []byte with acknowledgement Acknowledgement and make this a struct like Packet. Packet has one field containing the application data along with metadata. The acknowledgement is currently only the application data with no way to add metadata. Something like:

message Acknowledgement {
  data bytes = 1;
  payTo string = 2;
}

Would be a start and would allow for future extensibility without such breaking changes (we can add an optional field to Packet already).

@ancazamfir
Copy link
Contributor

.. relays MsgReceive(packet, proofs, signer, pay_to) -..

This field doesn't need to be in the packet and just needs to be in the go implementation IBCMsgReceive, thus I do not expect a need for ICS04 update, just ibc-go.

pay_to is not in the Packet itself but it is a new field in the MsgReceive which is standardized by ICS04 and protobufs

there is no incentive to relay acknowledgment with pay_to not matching the relayer's account.

There is. pay_to in the acknowledgement will determine which account gets the receiveFee for submitting the packet on the destination chain. Whoever submits the acknowledgement will get the ackFee. These may be different parties (but obviously the pay_to address has more incentive.

aah, so the ackFee goes to the signer of the MsgAcknowledgment and receiveFee goes to the pay_to. Missed this one. Thanks @ethanfrey

@AdityaSripal
Copy link
Member Author

AdityaSripal commented May 28, 2021

chain B recv_packet handler stores the pay_to along with the ack bytes (when write ack happens)

This is the key change and what affects the payloads and proofs between chains. I would like to open a PR (likely monday) explaining how I would extend this. In short, I would replace acknoweldgement []byte with acknowledgement Acknowledgement and make this a struct like Packet. Packet has one field containing the application data along with metadata. The acknowledgement is currently only the application data with no way to add metadata. Something like:

message Acknowledgement {
  data bytes = 1;
  payTo string = 2;
}

Would be a start and would allow for future extensibility without such breaking changes (we can add an optional field to Packet already).

Thank you @ethanfrey for focusing the discussion on what needs to happen on ICS first. Do you intend for the above proposal to be backwards compatible? Can a chain with ack []byte talk to a chain with ack Acknowledgement?

If not, is backwards compatibility a requirement for the short term incentivization protocol?

I'm in support of this proposal if it matches the backwards-compatibility requirements from ecosystem.

I would like to open a PR (likely monday) explaining how I would extend this.

Great! I look forward to reviewing it, feel free to address the above in your PR

@charleenfei
Copy link
Contributor

just read through this conversation, great breakdown. as far as i can see so far, the proposed route still allows the relayer to fill in its own address as payTo (and signer) so that means any relayer can pick up the packet which is good.

regarding the changes to ics-04, i took a look per @ancazamfir 's comment and haven't seen any standardization of the receive message, just of the acknowledgement envelope which SHOULD return bytes but could return another Acknowledgement type potentially. maybe i missed it?

@ancazamfir
Copy link
Contributor

regarding the changes to ics-04, i took a look per @ancazamfir 's comment and haven't seen any standardization of the receive message,

In ICS04 function recvPacket(... will require an extra parameter. These parameters are coming from the MsgReceive fields. The wire format for MsgReceive changes, no? Same for MsgAcknowledgment? What am I missing?

@ethanfrey
Copy link
Contributor

ethanfrey commented May 28, 2021

Yes, we must update the functions in the spec. And the proto defs in ibc-go.

I will start a strawman of this Monday or Tuesday to replace my ics20-2 proposal

@ebuchman
Copy link
Member

Thanks for the discussion all.

Most of this discussion seems to assume that fee payment should happen on source chain because as the initiator of the packet that's the place where the user can actually specify funds to be escrowed to pay fees for the entire packet life cycle.

But on the other hand, if possible, I think it would seem more natural for fees to be paid on whatever chain a tx was successfully committed on (ie. ack/timeout on the source chain and receive on the destination chain). And that way we wouldn't need to carry the destination chain relayer address back to the source chain through an extension to the ack as discussed.

Would it not be possible to leverage ICS20 itself for the fee payment for the receive packet on the destination chain? The funds could still be locked on the sending side, but now they'd be carried to the destination side with the other packet payload. Then we might not actually need to make changes to core IBC at all ?

Haven't thought through exact details yet. I guess the ICS20 transfer would be to the ibc fee module on the destination chain (note we'd have to enable transfer to this module address ...). Alternatively we could have a variant of ICS20 that's just for this use case, where the packet is intended to be relayed along with some other payload and just pays the relayer.

In fact maybe the proposal here could be turned into this, where the new IBCFeeMsg effectively transfers the receiveFee to the destination chain for it to be paid out there ?

@ethanfrey
Copy link
Contributor

@ebuchman Please read #577

That was my first approach and easily allows payments on the remote chain, but only for ICS20. There was some decent discussion on that item and in the IBC call a desire to make a more generic solution. If we make a generic one, the only approach we could reason about was paying on the source chain when the receive/ack pair or timeout was complete.

The problem with every other protocol is it could not just mint ics20 tokens that were escrowed somewhere else. So either every protocol becomes a superset of ics20-2 (allow it to transfer tokens along with other actions), or we pay on source chain.

I was quite a proponent for just doing the simple ics20-2 solution and getting the hub incentivized. But seeing that a generic solution may be possible without a huge amount of coding, I am leaning towards this.

Using one (ics20) packet to pay the fee for another packet brings up the issue of who pays for the ics20 packet?

In the end, with this proposal, I would assume the relayer would just accumulate some amount of payment on the source chain(s) and after some large number, move those tokens onto the destination chains where it needs to top up it's account to pay fees (and can use ics20 once there to collect the fees for say 100 packets). Forcing us to transfer fees with a separate packet for every app packet seems much less scalable and less flexible than allowing the relayer to relay them as a batch when needed (same work to send 0.01 atom or 100 atom in one ics20 packet)

@dtribble
Copy link

Most of this discussion seems to assume that fee payment should happen on source chain because as the initiator of the packet that's the place where the user can actually specify funds to be escrowed to pay fees for the entire packet life cycle.

It still seems to me that "delivery is paid for by receiver or receiver's fee keeper" supports all the scenarios. What are the specific scenarios that can't be supported with that? Sources can send ics-20 payments to top up their reserves on the destination chain., but that's a batch operation, not one-per-message. There could be a mechanism for asking about the prices for relayers. That's a nicely independent mechanism for posting or even dynamically negotiating prices.

I would expect that, in a send/ack pair, the destination chain would tip the send and the source chain would tip the ack. In the simplest case, they have nothing to do with each other, and each chain is just tipping all deliveries between chain A and B out of a "pseudo-altruism" fund to e.g., support connectivity between the chains.

Hmm I think we're working with different canonical usecases in mind. My canonical usecase is a single user incentivizing their own packet flow, with pseudo-altruistic funds being a special case. These funds could still work. They would just be incentivizing packet flow coming out of their chain. So chainA could incentivize packets going to chainB, and incentivize acks/timeouts coming back. The proposal I have does not include a way to incentivize a relayed packet from the receiving chain.

OK I'll think more about this use case.

The "pseudo-altruistic" approach btw is for near term to enable simple things to be rolled out quickly with some amount of funding getting the packets flowing. We need to get more precise control and incentives, but I think that can be longer term. The advantage of the simpler models is that they are easy and incentive-aligned to roll-out and fund.

My two concerns are if there is any legal implications with paying fees based on packet type (especially the DEX example you posted above). The other is the relayer experience. Ideally the relayer knows exactly how much they will get paid before they relay the packet, and this won't hold in your DEX example if the fee is subject to changing market conditions.

I think these are non-issues, but of course I'm interested in legal opinion. Basically as you note:

Anyone can still relay the packet. The user is not making any choice here. The relayer themselves is adding their preferred payTo address into the MsgReceivePacket, in the same way that they fill in the signer information.

So neither the receiver nor the sender are making a choice. The relayer is just an arms-length transmitter of opaque data blobs (and indeed protocols could perfectly well encrypt in the future). Whether -- like stateful packet inspection firewalls -- they inspect the blobs more closely than required in order to better do their jobs seems independent.

@colin-axner
Copy link
Contributor

This is a side note as it is an aspect of chain implementations of the ibc-fee, but the ibc-fee implementation should allow for adding to the existing fees for the packet (as opposed to setting it once). This helps in the cases:

  • user sends packet with insufficient fees
  • transfer fails, ack fee is too low to be relayed, but it is very valuable to have the ack relayed

Apologies if I'm restating something said above

@michaelfig
Copy link
Contributor

Just as an aside (which may already seem obvious to the people involved), for any ibc-go implementation, please ensure that the sender's payTo field is stored and transmitted on the receiving chain as a string and not a chain-specific "AccAddress", since the address is opaque to the receiving chain.

Thanks!

@mpoke mpoke added the brainstorming Open-ended brainstorming. label Mar 17, 2022
@mpoke mpoke changed the title General Relayer Fee Protocol ICS29: General Relayer Fee Protocol Mar 17, 2022
@mpoke mpoke added app Application layer. standard New standard. labels Mar 17, 2022
@crodriguezvega
Copy link
Contributor

Closing this since ics29 spec has already been merged.

@ethanfrey
Copy link
Contributor

Is there an ETA on an implementation being ready to use? Not just coded, but with relayer support and tested on some testnets at least.

Or is this something ready now for any new chain to use, just requiring the channel upgrade for existing channels?

@adizere
Copy link
Contributor

adizere commented Oct 19, 2022

It's ready for use in Hermes, to be released officially in v1.1 (before end of Oct). No testnets that I know of yet, only developer networks.

Some useful testing notes

Or is this something ready now for any new chain to use, just requiring the channel upgrade for existing channels?

Should be ready for new chains to use. Gaia reported to us they will not enable fees until channel upgradeability is ready. I don't know of other networks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
app Application layer. brainstorming Open-ended brainstorming. standard New standard.
Projects
Status: Backlog
Development

No branches or pull requests