Skip to content

mirror bond-states answers chain_unreadable on a synced node with 8 peers, while balances read fine #433

Description

@MichaelTaylor3d

Parent epic: https://github.com/DIG-Network/dig_ecosystem/issues/3166
Sibling: #412 (step 7 shipped the observation this reports on)
Found by: the dig-app#289 lane, on the installed node, 2026-08-30

Task

dign mirror bond-states --json returns {"state":"unknown","reason":"chain_unreadable"} on a node
that is synced, with 8 chia peers, and reading the chain fine for balances. Establish why the bond
observation cannot read a chain that the balance path reads successfully, and make it able to.

The measurement

On the installed dign 0.172.0, at the same moment, on the same machine:

call result
control.wallet.balance {"balance":1010000, "synced":true}1 010 $DIG, independently corroborated against api.coinset.org
dign mirror bond-states --json {"state":"unknown","reason":"chain_unreadable"}

chia_peer_peak_height equals the replica peak, so this is not the silent-staleness shape NC-12
records. The node can read the chain. The bond observation says it cannot.

Why this matters rather than being cosmetic

#412 step 7 shipped the observation (merged 5df3e34) specifically so control.mirror.bondStates
would stop answering chain_unreadable on every call. It now constructs a real observation — and on a
real machine still answers chain_unreadable. So the capability exists and the surface does not
receive it, which is the gap between a code path existing and a person seeing it work (§2.6).

Downstream, this is a money surface going blank: dig-app's collateral heading derives its locked
figure from this method, so on this configuration a real user sees "How much is locked up is not
known — your node cannot read the chain right now."
That sentence is honest, which is why this is a
capability gap and not a lie — but it is the wrong answer, and the honest-unknown is masking it.

Scope

Find which chain read the observation performs that the balance path does not, and why it fails.
Candidates worth checking before anything else, in order:

  1. dig_mirror_coin::list needs a ChainSource plus the operator wallet's own puzzle hash. The
    balance path answers per-address through a different route; if the observation's ChainSource is
    absent or differently constructed, chain_unreadable is the honest report of a missing dependency
    rather than a failing read.
  2. Whether the reason is accurate at all. chain_unreadable may be the catch-all arm for several
    distinct failures. If so, the reason itself is the first defect — an operator told the chain is
    unreadable will go looking at peers, which are fine.
  3. Whether it differs by configuration (enable_live_broadcast defaults false; the operator wallet may
    hold nothing).

The trap to avoid

Do not "fix" this by making the observation return an empty page. A page of no rows is a definite
claim that this node holds no bonds, and control.rs's own comment says so: "A caller MUST NOT
substitute an empty observation for an Err — a page of no rows says 'this node holds no bonds',
which is a definite claim about money."
If the read genuinely cannot be performed, the honest unknown
stays; the deliverable is then a more accurate reason, not a fabricated zero.

Acceptance

On a real machine with a synced node, dign mirror bond-states --json returns either a real
observation, or an unknown whose reason names the actual obstacle. Verified by running it — not by a
passing test, and not from the node's own logs alone.

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