Task
Production never runs the CAT asset_id attribution pass, so every CAT coin keeps asset_id = NULL
forever and the $DIG balance reads 0 on a funded wallet.
Reported by the user against the DIG App
(DIG-Network/dig-app#291) — "the dig-app is not displaying the DIG CAT
balance, it says zero when i know i have sent DIG to it" — but the app reports faithfully what this
node tells it.
Parent epic: https://github.com/DIG-Network/dig_ecosystem/issues/3166
The defect
crates/dig-wallet/src/sage/sync_supervisor.rs:2277:
sync::run_update_loop(db, receiver, events, None, session).await
The fourth argument is attributor: Option<&CatAttributor<'_>>. Production passes None, and
run_update_loop only attributes behind if let Some(a) = attributor (sync.rs:1041).
Chain of consequence:
coin_state_to_row (sync.rs:438) stores peer-supplied coins with asset_id: None — correct; a
CoinState frame does not carry the TAIL.
CatAttributor (sync.rs:726-744) is meant to fill it in by uncurrying the parent spend.
- It is never constructed outside tests.
unspent_coins(Some(asset)) queries WHERE spent_height IS NULL AND asset_id = ? (db.rs:2039)
and matches nothing.
Why CI is green — and what your regression test must therefore do
CatAttributor is correct and is tested: sync.rs:2382 passes Some(&attributor) and
sync.rs:2330 documents it as "Proves (#407): with a CatAttributor, the update loop runs the
attribution pass".
That test proves the capability. Nothing proves the wiring. Note also that all six None call
sites in sync.rs (1183, 1632, 1854, 1891, 2100) sit inside the test module, which starts at line
1102 — so a reader grepping the call sites sees mostly None and reads it as normal.
Your regression test must fail when the production construction path passes None. A test that
hands run_update_loop an attributor directly already exists and did not catch this. Assert on the
path the supervisor actually takes.
Also check, do not assume
unspent_coins(None) queries asset_id IS NULL, which is what unattributed CAT coins currently look
like. So the XCH balance may be correspondingly over-counted by the same defect. Measure it. If
XCH is inflated while $DIG reads zero, that is two money-lies from one cause and both need the
regression test.
Fix
Construct a CatAttributor in the supervisor and pass Some(..). It needs a LineageSource, the
address prefix, and the wallet's plain p2 puzzle hashes. Attribution is read-only ("it never signs or
broadcasts") and idempotent (already-attributed and already-spent coins are skipped by
reconstruct_coins), so running it on every update is safe.
Consider whether the catch-up path needs the same pass as well as the update loop — a wallet that
syncs from genesis and then receives no further pushes must still attribute what it found.
Bar
This is the money-lie class (§2.6's narrow stop): a surface telling a user they have no funds when they
do. Verify on the real funded wallet on this machine, not only in tests — a green suite is exactly
what failed here. dign wallet balance <address> --asset dig and dign wallet coins <address> --asset dig are the checks.
§2.4b: bring dig-*/chia-* deps to latest in this PR; index checks need a User-Agent header or the
registry reads as unpublished. chia-* move as a SET.
Task
Production never runs the CAT
asset_idattribution pass, so every CAT coin keepsasset_id = NULLforever and the $DIG balance reads 0 on a funded wallet.
Reported by the user against the DIG App
(DIG-Network/dig-app#291) — "the dig-app is not displaying the DIG CAT
balance, it says zero when i know i have sent DIG to it" — but the app reports faithfully what this
node tells it.
Parent epic: https://github.com/DIG-Network/dig_ecosystem/issues/3166
The defect
crates/dig-wallet/src/sage/sync_supervisor.rs:2277:The fourth argument is
attributor: Option<&CatAttributor<'_>>. Production passesNone, andrun_update_looponly attributes behindif let Some(a) = attributor(sync.rs:1041).Chain of consequence:
coin_state_to_row(sync.rs:438) stores peer-supplied coins withasset_id: None— correct; aCoinStateframe does not carry the TAIL.CatAttributor(sync.rs:726-744) is meant to fill it in by uncurrying the parent spend.unspent_coins(Some(asset))queriesWHERE spent_height IS NULL AND asset_id = ?(db.rs:2039)and matches nothing.
Why CI is green — and what your regression test must therefore do
CatAttributoris correct and is tested:sync.rs:2382passesSome(&attributor)andsync.rs:2330documents it as "Proves (#407): with a CatAttributor, the update loop runs theattribution pass".
That test proves the capability. Nothing proves the wiring. Note also that all six
Nonecallsites in
sync.rs(1183, 1632, 1854, 1891, 2100) sit inside the test module, which starts at line1102 — so a reader grepping the call sites sees mostly
Noneand reads it as normal.Your regression test must fail when the production construction path passes
None. A test thathands
run_update_loopan attributor directly already exists and did not catch this. Assert on thepath the supervisor actually takes.
Also check, do not assume
unspent_coins(None)queriesasset_id IS NULL, which is what unattributed CAT coins currently looklike. So the XCH balance may be correspondingly over-counted by the same defect. Measure it. If
XCH is inflated while $DIG reads zero, that is two money-lies from one cause and both need the
regression test.
Fix
Construct a
CatAttributorin the supervisor and passSome(..). It needs aLineageSource, theaddress prefix, and the wallet's plain p2 puzzle hashes. Attribution is read-only ("it never signs or
broadcasts") and idempotent (already-attributed and already-spent coins are skipped by
reconstruct_coins), so running it on every update is safe.Consider whether the catch-up path needs the same pass as well as the update loop — a wallet that
syncs from genesis and then receives no further pushes must still attribute what it found.
Bar
This is the money-lie class (§2.6's narrow stop): a surface telling a user they have no funds when they
do. Verify on the real funded wallet on this machine, not only in tests — a green suite is exactly
what failed here.
dign wallet balance <address> --asset diganddign wallet coins <address> --asset digare the checks.§2.4b: bring
dig-*/chia-*deps to latest in this PR; index checks need aUser-Agentheader or theregistry reads as unpublished.
chia-*move as a SET.