Skip to content

CAT discovery: stage derived-hash arrivals, promote only lineage-proven coins into coins #390

Description

@MichaelTaylor3d

Task

PR-B of the #383 split. Discover CAT coins by derived puzzle hash, stage them, and write into coins only what a lineage proof establishes.

Decision: #383 (comment) (normative — read it first).
Sibling: PR-A carries the attributor wiring and closes #382. This ticket closes #380.

The shape

Derived-hash arrivals land in a new cat_admission_pending table. Only lineage-proven coins are ever written to coins, fully attributed. coins keeps exactly origin/main's semantics.

Why a staging table and not a flag on coins

git grep "FROM coins" finds 18 production sites in db.rs alone, plus 4 in rpc.rs and arrivals.rs22 readers. A lineage_proven column makes safety depend on every one of them remembering to filter.

This PR already proved that enumerations here are incomplete. SPEC §18.11a named the puzzle-hash sets; the implementing lane found seven; the gate found an eighth at sync.rs:957 — and that eighth is exactly where a false "you were paid" notification came from.

A staging table makes a fabricated coin unreachable by absence from the table those readers read, not by a guard they must remember to apply.

The obvious alternative does NOT work — checked, do not retry it

Admitting to coins and letting the existing out-of-band pass promote it cannot work: unexamined_attribution_candidates (crates/dig-wallet/src/sage/db.rs:2652) filters on AND asset_id IS NULL, so a derived-hash row admitted with asset_id = DIG is never a candidate and attribution_examined can never be set on it.

Making it reachable requires asset_id = NULL — which means XCH (db.rs:1021) and feeds the spend-input selector (rpc.rs:2933, :3319). That is the round-5 rejection reason, and it survives intact.

What this must defend against — all five rounds' defects

  1. Fixes dig-node drops every CAT coin at ingestion, so $DIG balance reads a confident zero on a funded wallet #380 — the ingestion drop; CAT coins reach the wallet.
  2. No unbounded or amplified chain read. Zero on the frame path; roughly one out-of-band read per coin, terminal (a coin is resolved once), and capped.
  3. No denial. A promotion error must never reach a.attribute(db).await? — that is what turned a read failure into a session kill in rounds 3 and 4.
  4. Failure mode is incompleteness, never a wrong figure. A coin not yet promoted is absent, not zero, and not XCH.
  5. A fabricated coin never enters coins — so the balance, the spend selector and the arrivals notification are structurally clean rather than guarded.

The attack this exists to stop: anyone may CREATE_COIN at any puzzle hash, so a coin at the derived CAT hash costs 1 mojo per displayed base unit and needs only the victim's public address — no malicious peer. Round 5 admitted those directly, producing a fabricated balance, a permanent $DIG send kill-switch (selection is largest-first, the coin is unspendable, it never leaves the set), and a false payment notification.

Notes

  • admit_hinted stays deleted. Its job — distinguishing a real CAT from mojos paid to its address — moves to promotion, off the frame path. That is the whole point: it was the only such defence, and round 4 deleted it for a DoS that promotion does not have.
  • The eight puzzle-hash sets are the hazard map; re-derive them rather than trusting the list, since it grew twice already.
  • §2.6 governs: the thinnest path to a real wallet showing a real $DIG balance from discovered coins.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions