Path Unwinding #824
Replies: 9 comments 5 replies
-
cc: @dtribble @ValarDragon @colin-axner @crodriguezvega @ethanfrey |
Beta Was this translation helpful? Give feedback.
-
Terminology:
Then:
(1) I think that clients + on-initiating chain registries suffice for most use cases For a contract, I'd store the intermediate path somewhere on the initiating chain within the contract or perhaps in a go module or something. Its a nice optimization to allow filling in defaults in the initiating chain, but not at all needed / can be added layer / easily added by a custom module from initiating chain. It would also be nice to later on make registries on various source chains, (that can then be governed by various mechanisms, e.g. protocol upgrades, gov proposals delegated to a group, CNS provided by another chain, etc), but also not a big deal, and can be added later/ (2) this is fair. As always with fees, I feel like this isn't v1 blocking personally =p I see three options:
(3) oh great point, hadn't thought about this. From asking you, the goal is for atomic multi-denom to next chain, which does conflict with path unwinding. So I guess you can't have both path unwinding + multi-denom in the same packet, a packet can only choose one of the two, or one of the two is architected to being in another ICS (4) Should definitely store metadata for bounded time lengtbs so that it can automatically send back along the chain. |
Beta Was this translation helpful? Give feedback.
-
We've modified the packet forward middleware to be atomic and to send back to originating chain in the case of failure. It should satisfy the requirements above. |
Beta Was this translation helpful? Give feedback.
-
I think the ultimate solution should allow a 3rd party, such as a relayer, to perform the unwind and pay on the users' behalf ie users can be entirely passive and have unwinding occur in the background so long as their channels support the feature. |
Beta Was this translation helpful? Give feedback.
-
I'd prefer an approach where:
My proposal is to use a cryptographic identifier for a source chain rather than a global name registry to avoid unwinding of packet To summarize the technical issues:
High level problem is -> How do we uniquely identify the source chain? Use a combination of the consensus state root and the height of a chain as its unique identifier. The likelihood of a hash collision at the same height across chains is infinitesimally low. On send (source chain):
On intermediate transfers (non-source chain):
On recv (source chain):
On transfer back to source (non-source chain):
|
Beta Was this translation helpful? Give feedback.
-
This will not work to uniquely identify a chain, the collision is not infinitesimally low. An attacker chain could forge another chain's identity We could uniquely identify a chain using However, currently ICS20 does not enforce non-fungibility just at the chain layer but even at the channel layer. i.e. two channels coming from the same chain will have different denominations. This is because these channels may be built on different connections and clients which may have different security parameters and thus should not be fungible with each other. If a channel with stricter security requirements is fungible with a weaker one, then effectively all channels have the same security model as the weakest channel between two chains. In practice, we have seen that a canonical transfer channel emerges between two chains.
This is not how the current model works as in the current model when we transfer intermediately in the forward direction (ie, adding a step to the tokens provenance) we escrow the tokens. When we transfer intermediately in the reverse direction (ie, removing a step in the tokens provenance) we burn the tokens. The token denom is changed at each intermediate step (adding a prefix if we are adding to token provenance, removing prefix if we are removing from token provenance). This is critical to ensure that the IBC security model is preserved. Suppose that we only prefixed the original source information to the token denomination. Then once the tokens have left their original source chain, they are completely fungible with each other. This yields the following attack:
With the way ICS20 is currently architected, only the This is extended to the multihop case. If I send stake from All users who had send tokens up to B but not onward to C are completely protected from C's malicious behaviour. All tokens that have chain C in its provenance are vulnerable to an attack by C. This is a critical property of the IBC security model that we must preserve in the path unwinding case. The only way I see of doing that is to maintain the semantics that chains escrow on forward paths to keep track of total balances out; and that when we unwind, these escrow balances on all intermediate chains in the token provenance are updated accordingly |
Beta Was this translation helpful? Give feedback.
-
Per @crodriguezvega's request, I'm posting the IBC unwinding proposal drafted by Skip protocol. https://hackmd.io/@bpiv400/ibc-unwinding Tagging @mpoke @cwgoes @AdityaSripal as their approval is required to modify this repo according to the standards committee document here: https://github.com/cosmos/ibc/blob/main/meta/STANDARDS_COMMITTEE.md |
Beta Was this translation helpful? Give feedback.
-
Thank you everyone for your feedback. This is our current thinking. We want to ensure that this features get widespread adoption across the Interchain. We also want to reduce friction and timelines for getting this feature out. I will write out the particular approaches that we considered from the different feedback we got and the pros and cons of each. I will leave minor points in italics and major points (as I see them) in bold. I. Use middleware to process packet forwarding information in the memo fieldPros:
Cons:
II. Build logic into transfer to process packet forwarding information in the memo fieldPros:
Cons:
III. Use middleware to wrap base ICS20 packet data with custom middleware packet infoPros:
Cons:
IV. Build logic into transfer with a custom field in ICS20Pros:
Cons:
Explanation of why Channel Upgradability would be needed for memo but not for a new fieldSuppose there is a token originating from chain C on chain A that has went through the following path: C -> B -> A The user on chain C wishes to unwind and send to a chain D. So our logic should go through the following path: A -> B -> C -> D Unwinding logic must exist on chains: A, B, C We want this feature to be atomic. So when the user initiates the transfer on A, only one of two things happen.
If all the chains in our path have the unwinding logic, then we can implement this behaviour using async acks. We will just hold off on writing the ack until the last chain writes it, then we will propogate it all the way back to sender. 🎉 Now suppose that all of the chains on our path have the memo field update, but chain C does not have the unwinding update. The tokens will be correctly unwound down to chain C. When chain C receives them, it will treat the packet as a valid ICS20 packet with a memo that it does not know how to parse or act on; so it will just succeed as a regular transfer and the token flow will stop there. The tokens will not be forwarded on to chain D. This leads to the undesirable case where tokens might be left anywhere along the path and the user has to go find them and pick them up. This is further complicated if the chains don't follow cosmos bech32 standard or if the user is a smart contract. So we can fix this by using channel upgradability. Here we use it to communicate in advance whether our counterparty has the unwinding logic. So B would know that chain C does not have the unwinding logic since it doesn't have it negotiated in the B<->C channel version. Then B would simply receive the tokens from A, check the next channel version, and if it doesn't support channel unwinding we fail early and send the tokens back. Thus, we've recovered atomicity property in this case. Now suppose that instead of overloading the memo field, we instead add a new field e.g. When all chains have the unwinding logic, we have the exact same behaviour as before. Let's look again at the scenario where one of the chains (again chain C) does not have the unwinding logic implemented. Here, we continue on the same way until we reach the Receive handler on chain C. Since chain C does not have the new logic, it will not expect the new field. It will fail the unmarshal here: https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/ibc_module.go#L178 and send back an error acknowledgement. This will be propogated back and the tokens will successfully refund on chain A. When paired with the async ack logic we automatically get atomicity when introducing a new field with no need for channel upgradability. Given the desire to get widespread adoption, get unwinding as a first-class in-built feature of ICS20, and not having to rely on a new complicated protocol to get our desired properties; our team is in favor of approach IV. We reached this assessment after looking at the four approaches above and their associated pros and cons. If there is anything critical that we have missed, or you think the pros and cons should be weighted differently than what I have conveyed. Then please comment below with your rationale |
Beta Was this translation helpful? Give feedback.
-
Is there a concrete plan to implement this? |
Beta Was this translation helpful? Give feedback.
-
Currently the IBC fungible token transfer protocol (ICS-20) allows tokens to flow through the IBC network without restriction. A token may take several hops from its source chain before it lands on the final destination chain. We encode this path the token takes into its denomination so that it is not fungible with tokens that take different paths. This is critical because the security properties of two tokens taking two different paths through the IBC network are fundamentally different and thus the tokens cannot be viewed as equivalent at the protocol level.
While this property must hold for security purposes, it is inconvenient for users and applications since they may have tokens that originate on
chainA
living onchainB
that they then wish to send to achainC
. If they send directly tochainC
, the end application or user onchainC
will not view it as equivalent to a token being sent directly fromchainA
. This is increasingly becoming a pain point as the network built around ICS-20 becomes more complex.Currently the solution is to manually initiate a two-step process. Send the tokens back to the original chain, and then from there send to the desired destination.
This works for end users, and could be automated by front-ends to make the experience more seamless. However, it will not work for smart contracts or modules that wish to send non-native tokens to a new destination chain. These contracts could in theory already use ICA and send a series of messages asynchronously to achieve the same effect already, however that is a lot of orchestration and complexity for a simple and common use case.
One solution is to build an optional feature into the ICS-20 transfer message, which when given a non-native denom will "unwind" the path by sending the packet back to the original source while maintaining information about the final destination chain which the original chain can send the tokens to in the end.
This discussion exists to gather feedback on the proposed feature and also to address the concerns and edge cases that would come up from such a feature.
Here are the questions I think are unaddressed:
related work: Thanks to @jackzampolin and the strangelove team for their work on the packet-forward-middleware. My proposal is similar but would atomically send tokens back if the second hop failed, and this would be implemented on all chains for the purposes of path-unwinding, rather than existing on a single chain as a router
Beta Was this translation helpful? Give feedback.
All reactions