Skip to content

CAT asset_id attribution never runs in production, so $DIG balance reads zero on a funded wallet #382

Description

@MichaelTaylor3d

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:

  1. coin_state_to_row (sync.rs:438) stores peer-supplied coins with asset_id: None — correct; a
    CoinState frame does not carry the TAIL.
  2. CatAttributor (sync.rs:726-744) is meant to fill it in by uncurrying the parent spend.
  3. It is never constructed outside tests.
  4. 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.

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