A wormhole route: what enters, what leaves, and what is still inside #18

Closed
opened 2026-09-14 06:28:03 +00:00 by grenade · 0 comments
Owner

Came out of the distribution table shipping a sent on column that read zero for every miner. That zero was not "nothing left" — it was "nothing left by a route the chain records", because a wormhole transfer does not record who sent it. The privacy is the chain's central feature and the observer currently has nothing to say about it.

The mechanism, read off the runtime

Wormhole  calls    verify_private_batch, verify_public_batch
          events   NativeTransferred, AssetTransferred, ProofVerified,
                   MinerVolumeFeePaid, SegmentsDenied, ExitMintFailed
          storage  UsedNullifiers, TransferCount

ZkTree    events   LeafInserted, TreeGrew
          storage  Leaves, Nodes, LeafCount, Depth, Root, UnprocessedLeaves

A transfer inserts a commitment as a leaf; spending one proves a nullifier, which is then recorded so it cannot be spent twice. Every NativeTransferred we decode carries a leaf_index, so the two halves are joinable from events we already index.

Measured on mainnet

ZkTree::LeafCount           82,644    notes created
Wormhole::UsedNullifiers    47,789    notes spent (map enumerated)
                           -------
outstanding                 34,855    unspent — what is still inside

And the flow, split by the extrinsic that produced each transfer and whether a sender was recorded:

(no extrinsic — initialisation)  hidden   23,876 events    72 recipients
Utility::batch_all               visible  15,303        2,145
Wormhole::verify_public_batch    hidden    8,136          538
Balances::transfer_allow_death   visible   6,941          651
Wormhole::verify_private_batch   hidden    1,758          116
Balances::transfer_keep_alive    visible     127           44

Hidden-sender transfers in block initialisation are mining rewards. The 9,894 from verify_*_batch are real payments with no recorded origin.

Worth noting the ratio: 47,789 nullifiers consumed against 9,894 hidden-sender outputs. Notes are spent in batches far larger than the outputs they produce, which is consolidation and is itself worth showing.

What the route should carry

  • The pool: notes created, notes spent, outstanding, tree depth and root. Outstanding is the headline — it is the anonymity set, and it is the one number that says how much of this chain's activity is currently unobservable.
  • Flow by route: the table above, with summed amounts. This is literally "what enters and what leaves".
  • Per-day: notes created per day, derived from the leaf_index on transfers we already index rather than from ZkTree::LeafInserted, which is in SKIPPED_EVENTS because it fires every block and names no account.
  • Failures: SegmentsDenied and ExitMintFailed have never fired. A zero there is a finding, not a gap.
  • Fees: MinerVolumeFeePaid, 713 events across 19 accounts.

The thing to be careful about

Do not describe outstanding notes as "funds locked in the wormhole" without checking what a leaf actually commits to. Balances::Locks is empty on this chain, and account balances are visibly real, so the relationship between a note and a balance needs establishing rather than assuming. Count what can be counted — notes created, notes spent, the difference — and say that, which is true, rather than a token figure that might not be.

Came out of the distribution table shipping a `sent on` column that read zero for every miner. That zero was not "nothing left" — it was "nothing left by a route the chain records", because a wormhole transfer does not record who sent it. The privacy is the chain's central feature and the observer currently has nothing to say about it. ## The mechanism, read off the runtime ``` Wormhole calls verify_private_batch, verify_public_batch events NativeTransferred, AssetTransferred, ProofVerified, MinerVolumeFeePaid, SegmentsDenied, ExitMintFailed storage UsedNullifiers, TransferCount ZkTree events LeafInserted, TreeGrew storage Leaves, Nodes, LeafCount, Depth, Root, UnprocessedLeaves ``` A transfer inserts a commitment as a **leaf**; spending one proves a **nullifier**, which is then recorded so it cannot be spent twice. Every `NativeTransferred` we decode carries a `leaf_index`, so the two halves are joinable from events we already index. ## Measured on mainnet ``` ZkTree::LeafCount 82,644 notes created Wormhole::UsedNullifiers 47,789 notes spent (map enumerated) ------- outstanding 34,855 unspent — what is still inside ``` And the flow, split by the extrinsic that produced each transfer and whether a sender was recorded: ``` (no extrinsic — initialisation) hidden 23,876 events 72 recipients Utility::batch_all visible 15,303 2,145 Wormhole::verify_public_batch hidden 8,136 538 Balances::transfer_allow_death visible 6,941 651 Wormhole::verify_private_batch hidden 1,758 116 Balances::transfer_keep_alive visible 127 44 ``` Hidden-sender transfers in block initialisation are mining rewards. The 9,894 from `verify_*_batch` are real payments with no recorded origin. Worth noting the ratio: **47,789 nullifiers consumed against 9,894 hidden-sender outputs**. Notes are spent in batches far larger than the outputs they produce, which is consolidation and is itself worth showing. ## What the route should carry - **The pool**: notes created, notes spent, outstanding, tree depth and root. Outstanding is the headline — it is the anonymity set, and it is the one number that says how much of this chain's activity is currently unobservable. - **Flow by route**: the table above, with summed amounts. This is literally "what enters and what leaves". - **Per-day**: notes created per day, derived from the `leaf_index` on transfers we already index rather than from `ZkTree::LeafInserted`, which is in `SKIPPED_EVENTS` because it fires every block and names no account. - **Failures**: `SegmentsDenied` and `ExitMintFailed` have never fired. A zero there is a finding, not a gap. - **Fees**: `MinerVolumeFeePaid`, 713 events across 19 accounts. ## The thing to be careful about Do not describe outstanding notes as "funds locked in the wormhole" without checking what a leaf actually commits to. `Balances::Locks` is empty on this chain, and account balances are visibly real, so the relationship between a note and a balance needs establishing rather than assuming. Count what can be counted — notes created, notes spent, the difference — and say that, which is true, rather than a token figure that might not be.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: blackbeard/observer#18