Skip to content

feat(bridge): add destination claim flow#127

Open
erenyegit wants to merge 2 commits into
mainfrom
feat/bridge-destination-claim
Open

feat(bridge): add destination claim flow#127
erenyegit wants to merge 2 commits into
mainfrom
feat/bridge-destination-claim

Conversation

@erenyegit

Copy link
Copy Markdown
Collaborator

Summary

Adds the destination side of the bridge: a chain anchors an attested foreign
state root, then claims a transfer by proving its record against that root
and minting the mapped asset to the recipient. Together with the source lock
(#126), this establishes the core trusted-anchor destination claim path:

source lock → anchor root → claim (proof) → recipient minted

What's added

  • AnchorForeignRoot — the genesis-configured attestor pins a foreign state
    root, keyed by (source_chain_id, view) with a strictly monotonic view per
    source chain (distinct source chains never block each other). This is a
    trusted/authority-attested anchor for the MVP, deterministic and
    replay-safe — not trustless finalization verification.
  • Claim { source_chain_id, source_view, record, proof } — verifies the
    record targets this chain, looks up the anchored root for source_view,
    checks the embedded StateProof with verify_state_update, and marks the
    record consumed exactly once (double-claims rejected). The embedded proof
    decodes with fixed bridge-internal bounds so the operation stays
    Read<Cfg = ()>.
  • Settlement: lock-and-mint, wired in the coins-chain integration layer. A
    genesis-configured AssetId -> CoinId mapping selects the local wrapped coin,
    and a new, narrowly-scoped coins::Ledger::bridge_mint credits the recipient
    by increasing that coin's supply (backed 1:1 by the source escrow). A missing
    mapping fails before anything is consumed or minted.
  • Atomicity, state and events: the claim's consume marker and the mint share
    one inner overlay, and the claim's events are buffered and emitted only after
    settlement commits — a mint failure reverts state and drops the events.
  • common::StateProof gains Clone/Debug/PartialEq + manual Eq so it can be
    embedded in the claim operation (QmdbOperation has no upstream Eq;
    structural equality is reflexive here).

Note on proof generation

A dedicated test proves that a record written by a real BridgeOperation::Lock
verifies with verify_state_update — so the lock → proof → claim chain holds.
However, naive full-range proof generation over a state that also contains coins
writes (escrow balance overwrites advance the QMDB inactivity floor) does not
verify; a relayer must select the correct proof window over a production state
(via historical_proof and the block's state range). That relayer-side work is
deliberately out of scope: the claim path is agnostic to how the proof was
produced.

Testing

  • Bridge: anchor accepted from the attestor and rejected otherwise; stale view
    rejected; monotonicity is per source chain; claim succeeds and marks consumed;
    double claim, missing anchor, wrong destination, and tampered record rejected;
    a real lock's record is provable; codec round-trips (incl. embedded proof).
  • Integration (coins-chain): claim verifies and mints the mapped wrapped coin
    to the recipient; unmapped asset fails with no consumption; a settlement
    failure (max-supply cap) leaves no event and no consumption
    .

Out of scope (follow-ups)

  • Cryptographic finalization verification (trustless anchor).
  • Relayer proof generation from finalized production blocks (see note above).
  • Multisig claims, reverse redemption/refunds, relayer/RPC wiring, genesis JSON.

Validation

All green:

  • cargo fmt --all -- --check
  • cargo test -p nunchi-bridge -p nunchi-coins -p nunchi-common -p nunchi-coins-chain
  • cargo clippy --locked --all --all-targets -- -D warnings

Add the destination side of the one-way bridge: a chain anchors an attested
foreign state root, then claims a transfer by proving its record against that
root and minting the mapped asset to the recipient.

- AnchorForeignRoot: the genesis-configured attestor pins a foreign state root,
  keyed by (source_chain_id, view) with a strictly monotonic view per source
  chain. This is a trusted/authority-attested anchor for the MVP, not trustless
  finalization verification.
- Claim { source_chain_id, source_view, record, proof }: verifies the record
  targets this chain, looks up the anchored root for the view, checks the
  embedded StateProof with verify_state_update, and marks the record consumed
  exactly once. The proof decodes with fixed bridge-internal bounds so the
  operation stays Read<Cfg = ()>.
- Settlement is lock-and-mint, wired in the coins-chain integration layer: a
  genesis-configured AssetId -> CoinId mapping selects the local wrapped coin,
  and a new coins Ledger::bridge_mint credits the recipient by increasing that
  coin's supply. The claim's consume marker and the mint share one inner overlay,
  and the claim's events are buffered and emitted only after settlement commits,
  so a missing mapping or a mint failure reverts both state and events. Source
  and destination are separate states; the claim never touches the source escrow.

A dedicated test proves a real BridgeOperation::Lock writes a record that
verify_state_update accepts, so the lock -> proof -> claim chain holds.

Adds Clone/Debug/PartialEq + manual Eq to common's StateProof so it can be
embedded in the claim operation.

Out of scope (follow-ups): cryptographic finalization verification, relayer
proof-generation from full production blocks, multisig claims, reverse
redemption, relayer/RPC wiring, and genesis JSON configuration.
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.84314% with 31 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
bridge/src/events.rs 80.85% 0 Missing and 9 partials ⚠️
bridge/src/ledger.rs 90.41% 1 Missing and 6 partials ⚠️
bridge/src/record.rs 89.39% 0 Missing and 7 partials ⚠️
bridge/src/transaction.rs 85.41% 0 Missing and 7 partials ⚠️
coins/src/ledger.rs 90.00% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

…cessors

Adds focused tests for logic branches and state accessors introduced by the
destination claim flow:
- anchor rejected when no attestor is configured
- claim rejected on source-chain mismatch
- ForeignRootAnchored event round-trips (topic + fields)
- foreign-root, latest-view, and attestor accessors round-trip through state

@distractedm1nd distractedm1nd left a comment

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.

I think we should take a step back and use the existing consensus certificate flow that bridging was supposed to use. Right now the attestor design is (probably) a bit less code, but operationally very complex.

The original bridge:

  1. Receives a foreign Finalization certificate.
  2. Verifies it against a configured foreign consensus scheme.
  3. Keeps the newest verified certificate.
  4. Includes that certificate in the destination chain’s consensus extension payload.

So originally just:
foreign certificate -> verify -> cache/include in blocks

But this PR:
attestor signature -> store raw state root -> verify claims

Theres not any connection rn.

Can we maybe try to restrict the MVP to one configured foreign chain and use:

Source Lock

Finalized source checkpoint

Destination stores certified root

Claim proves record

Atomic mint

?

I'll write some more details in sync to not pollute the PR

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants