Skip to content

game: Clean up Merkle trees - #28

Open
tindzk wants to merge 4 commits into
mainfrom
feat/1238-tn-clean-up-merkle-trees
Open

game: Clean up Merkle trees#28
tindzk wants to merge 4 commits into
mainfrom
feat/1238-tn-clean-up-merkle-trees

Conversation

@tindzk

@tindzk tindzk commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Until now, credit trees were retained indefinitely on both chains. This PR implements removal:

  • A tree is removed once its credits are minted or its claim deadline has passed
  • Each chain (People, Asset Hub) tells the other what it removed
  • Each chain has a TTL sweep of its own that covers for a message that never arrived

Lifecycle of one tree

stateDiagram-v2
    state "Fully claimed" as Claimed
    [*] --> Stored: receive_credit_trees
    [*] --> Rejected: already past its deadline on arrival
    Stored --> Claimed: every leaf claimed
    Stored --> Expired: claim deadline passed, swept by bucket
    Claimed --> Queued: deletion queued
    Expired --> Queued: deletion queued
    Queued --> [*]: sent to the People chain
    Rejected --> [*]
Loading

Delivery and deletion across the two chains

sequenceDiagram
    participant PeopleOcw as People offchain worker
    participant Credits as nft-credits, People
    participant Claims as nft-claims, Asset Hub
    participant AhOcw as Asset Hub offchain worker
    participant User as Claimant

    Credits->>Claims: receive_credit_trees
    User->>Claims: claim, mints the NFT
    Note over Claims: last leaf of a tree claimed,<br/>tree removed and deletion queued
    AhOcw->>Claims: sweep_expired_trees, one bucket
    Note over Claims: trees past the claim deadline removed,<br/>deletions queued
    AhOcw->>Claims: send_tree_deletions
    Claims->>Credits: receive_tree_deletions, XCM
    Note over Credits: named roots removed
    PeopleOcw->>Credits: sweep_expired_roots, one bucket
    Note over Credits: roots past their own deadline removed,<br/>covers for a lost message
Loading

Both sweeps walk one bucket of wall-clock time per call. A tree's bucket is the UTC day its credits
were awarded in. A sweep therefore selects the trees that are due instead of reading every tree.

On Asset Hub, a tree is dropped 90 days after the credits were awarded, which is the claim deadline.
The People chain keeps the root it was built from for a further 30 days. That deadline is derived
from the Asset Hub one, so the two cannot drift apart. A credit inside its deadline always has a root
and its awards to prove against.

Changes

  • nft-claims: removes a tree once it is fully claimed and once its claim deadline has passed. A
    tree that arrives past its deadline is not stored. Removals queue up and go out to the People chain
    as one message, both driven by the offchain worker.
  • nft-credits: removes the roots Asset Hub reports it is finished with. It sweeps the ones no
    message ever named.
  • indiv-support: holds the expiry-bucket scheme and the sweep helpers both chains drive. The
    offchain submission is shared too, so a stalled submitter behaves the same on either chain.
  • Runtimes: the People chain accepts Asset Hub's unpaid deletion message. Each runtime's
    integrity test asserts that its sweeps fit a block and that a full deletion message fits the
    channel.

@tindzk
tindzk requested review from a team, Zebedeusz, agustinustheo and gui1117 August 21, 2026 09:12

fn tree_deletion_xcm(encoded_call: Vec<u8>) -> Xcm<()> {
Xcm(vec![
UnpaidExecution { weight_limit: WeightLimit::Unlimited, check_origin: None },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why WeightLimit:Unlimited and not a benchmarked weight?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Limited(m) must cover the weight the destination (game chain) computes. We cannot benchmark it here. A hardcoded value would silently drop messages if the game chain weights change.

@tindzk tindzk added the D5-nice to have audit optional to have, meant to express "audit it if there is bandwidth" label Aug 26, 2026

@shawntabrizi shawntabrizi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code may need a storage migration depending on when it lands.

Otherwise stale storage will mess up the state transition function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

D5-nice to have audit optional to have, meant to express "audit it if there is bandwidth"

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants