Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

XCM Retry Queue #2702

Open
xlc opened this issue Mar 25, 2021 · 2 comments
Open

XCM Retry Queue #2702

xlc opened this issue Mar 25, 2021 · 2 comments

Comments

@xlc
Copy link
Contributor

xlc commented Mar 25, 2021

It will be common for a parachain to receive XCM that cannot be executed successfully. Some of them could be nonsense message that can never success but some of them are worth retrying in future. A common example is receive a XCM transfer of an unknown token. i.e. User may send some token to a parachain that it cannot handle. This means the fund will be lost as the credit XCM message execution failed.

One way to help the situation is to record a proof of unhandled XCM and allow user to replay them later. For example, the parachain could have a runtime upgrade to support a new token. So all the previous unknown token transfer can now be correctly handled and allow funds to be recovered. Another example is some weight limits may prevent XCM to be completed. User could pay extra fee to increase the weight limit in order to successfully execute the XCM.

Simply store all the unhandled XCM on chain could result state exploding as some of the XCM may never be successed or the transfered token amount is too small that is less than transaction fee to replay them. Therefore we need a better solution. Instead of storing all the XCM, we can just store the trie root of all XCM. User can use the preimage and a proof to replay it. To prevent replay attack, an executed XCM trie root is required. By doing this, no matter how many unhandled XCM we have, the onchain state only needs to store two trie root.

Pallet Design

  • xcm-retry-queue
    • Types
      • trait OnXcmError
        • fn on_xcm_error(xcm: VersionedXCM, error: XcmError)
    • Config
      • type Period: Get<BlockNumber>
        • Number blocks for a trie
      • type Count: Get<u32>
        • Number of trie to keep
    • Storage
      • ChildStorages
        • Hash => VersionedXCM
      • HistoricalTrieRoot: Hash
      • ExecutedTrieRoot: Hash
    • Pallet Methods
      • fn trie_index(n: BlockNumber) -> BlockNumber
        • n / Period
      • fn trie_prefix(n: BlockNumber) -> [u8; 32]
        • hash(("xcm-retry-queue", trie_index(n)).encode())
      • fn replay(xcm_hash: Hash, n: BlockNumber) -> XcmResult
        • Take (Get & Remove) XCM from trie
        • Execute the XCM
      • fn insert(xcm: VersionedXCM)
        • Insert xcm into current trie
      • fn replay_historical(xcm: VersionedXCM, exist_proof: Vec<u8>, empty_proof: Vec<u8>)
        • Verify exist proof with HistoricalTrieRoot
        • Verify empty proof with ExecutedTrieRoot
        • Execute XCm
        • Update HistoricalTrieRoot & ExecutedTrieRoot
      • impl OnXcmError
    • Pallet Hooks
      • fn on_initialize
        • if now % Period == 0
          • let index = now / Period - Count
          • HistoricalTrieRoot = Hash(HistoricalTrieRoot ++ trie_root_hash_at(index))
          • remove trie with index
    • Note
      • No calls. Up to parachain to decide how they want to expose the call.
      • Update XcmExecutor & Config to all OnError handler to allow people to hook this pallet into
      • HistoricalTrieRoot can be used to resure pruned XCM, but the proof size is O(n) with n is how old the message is
        • The older the message, the more expensive to recure it, and eventually it will exceed the block weight limit

cc @gavofyork

@xlc
Copy link
Contributor Author

xlc commented Mar 25, 2021

Our intent usage is to address existential deposit issue on orml-unknown-tokens.

By default, deposit into unknown-token will fail. But people can pay a deposit and then replay the XCM in order to have the token credited. Then they can use XCM to transfer this token to other accounts (within the parachain or to other parachain).

@xlc
Copy link
Contributor Author

xlc commented Mar 25, 2021

It seems like we cannot automatically update trie root without the whole trie to be stored on chain. The unhandled messages needs to be stored differently.

We can have one trie per X blocks, say 100. and keep Y tries, say 1008. This will allow the chain to store unhandled messages for X * Y blocks, which will be 7 days with 100 & 1008.

If we have on average 10 unhandled XCM per block, each trie will have 1000 items, which is not too large.

I will expect this will be the worst case scenario because most of the messages should be executable one. Also the standard Xcm limits applies to limit spamming.

This will allow us to store up to 7 days of unhandled messages without unbounded data.

If 7 days is not enough, (e.g. waiting for runtime upgrade to fix something), there can also be another never expire trie root stored on chain. People can pay fee to move their unhandled XCM from expiring trie to the permanent trie. Because people needs to trigger this action externally, we can have the trie to be stored offchain and only the root stored onchain. Can also adjust the fee based on items in the trie.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant