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

    kind:businessa person can DO something new, or money moves, or a shipped surface stops lying to themmvpgates the releasable MVP

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions