Parent epic: https://github.com/DIG-Network/dig_ecosystem/issues/3173
Found by: the PR #401 security gate
Sequencing: this MUST be resolved before #377 lands.
The defect
A transient degraded chain read can write a permanently uncorrectable low collateral
requirement.
Three properties compose into it, each individually reasonable:
- The census bypasses NC-12 corroboration that exists in the same struct.
ChainTransport prefers
peer_reads — agreement across concurrently-queried peers — for the wallet's own reads
(chain.rs:343-352). The census instead goes through ChiaQueryProvider, whose
peak_height/coin reads resolve coinset-first (coinset_fallback_enabled: true by default,
sources.rs:116-118), degrading to an unagreed peer-tracked value when coinset is unreachable.
put is immutable and never lets a later record supersede a Censused one. That is deliberate
and correct against a peer — it is what stops a lying peer overwriting a node's own census.
catch_up never re-censuses an epoch it has already recorded. Also deliberate: it is what makes
the walk idempotent and cheap.
Together: a source that is briefly wrong writes a record that is wrong forever, and the two
mechanisms that would normally repair it are the same two mechanisms protecting it from a peer.
Why it is not gating PR #401, and why it becomes serious the moment #377 lands
Today every record is (stores: 0, owners: 0, locked: 0) and that is the correct answer — nothing
has ever minted a mirror coin in production, because the creation path is
#377, still an open draft PR. A degraded read that
produces zero today produces the same number a healthy read produces.
#377 is exactly what makes it exploitable. Once real mirror coins exist, a degraded read produces a
count lower than the truth, the requirement is derived from that count, and the record is sealed. That
is the down direction — the direction the user named explicitly as the threat this model must resist,
and the direction in which a wrong answer under-collateralises the network rather than over-charging it.
An attacker does not need to sustain the degradation. One badly-timed window per epoch is enough, and
epochs are seven days apart.
What a fix has to preserve
Any remedy must keep the two properties it is threading between:
Three shapes worth considering, none decided:
- Route the census through the same NC-12 corroboration the wallet uses, so a degraded source cannot
produce a confident low count in the first place. Most direct; the cost is that census reads become
as expensive as wallet reads.
- Record the read's provenance in the record and permit a self-supersession when a later census
ran against a strictly better-corroborated source. Preserves peer immutability, since only this node's
own census may supersede — but "strictly better" needs a total order that does not currently exist.
- Refuse to record at all when the source is degraded, leaving the epoch
unknown. Fails closed and
matches this family's established preference for an honest unknown over a confident figure — but an
epoch left unrecorded interacts with the walk's no-gaps property and with retention.
Acceptance
A census run against a degraded or single-source chain view cannot seal a requirement that a healthy
run would have computed differently — either because it does not record, or because it can later be
superseded by this node's own better-corroborated census. Peer records still cannot supersede a
Censused record, and the walk stays idempotent.
Verify by probe, not by reading: the PR #401 gate demonstrated that a census result can be moved by
controlling the source, which is the same lever this defect uses. A test that degrades the source mid-walk
and asserts the outcome is the direct proof.
Parent epic: https://github.com/DIG-Network/dig_ecosystem/issues/3173
Found by: the PR #401 security gate
Sequencing: this MUST be resolved before #377 lands.
The defect
A transient degraded chain read can write a permanently uncorrectable low collateral
requirement.
Three properties compose into it, each individually reasonable:
ChainTransportpreferspeer_reads— agreement across concurrently-queried peers — for the wallet's own reads(
chain.rs:343-352). The census instead goes throughChiaQueryProvider, whosepeak_height/coin reads resolve coinset-first (coinset_fallback_enabled: trueby default,sources.rs:116-118), degrading to an unagreed peer-tracked value when coinset is unreachable.putis immutable and never lets a later record supersede aCensusedone. That is deliberateand correct against a peer — it is what stops a lying peer overwriting a node's own census.
catch_upnever re-censuses an epoch it has already recorded. Also deliberate: it is what makesthe walk idempotent and cheap.
Together: a source that is briefly wrong writes a record that is wrong forever, and the two
mechanisms that would normally repair it are the same two mechanisms protecting it from a peer.
Why it is not gating PR #401, and why it becomes serious the moment #377 lands
Today every record is
(stores: 0, owners: 0, locked: 0)and that is the correct answer — nothinghas ever minted a mirror coin in production, because the creation path is
#377, still an open draft PR. A degraded read that
produces zero today produces the same number a healthy read produces.
#377 is exactly what makes it exploitable. Once real mirror coins exist, a degraded read produces a
count lower than the truth, the requirement is derived from that count, and the record is sealed. That
is the down direction — the direction the user named explicitly as the threat this model must resist,
and the direction in which a wrong answer under-collateralises the network rather than over-charging it.
An attacker does not need to sustain the degradation. One badly-timed window per epoch is enough, and
epochs are seven days apart.
What a fix has to preserve
Any remedy must keep the two properties it is threading between:
point of the
AdoptedFromPeers → Censusedasymmetry, and it was itself a gating finding earlier inthis family.
cost (Cold-start census walks every epoch since genesis — 103 epochs, ~11 minutes, and it grows weekly #404) multiplied by every run.
Three shapes worth considering, none decided:
produce a confident low count in the first place. Most direct; the cost is that census reads become
as expensive as wallet reads.
ran against a strictly better-corroborated source. Preserves peer immutability, since only this node's
own census may supersede — but "strictly better" needs a total order that does not currently exist.
unknown. Fails closed andmatches this family's established preference for an honest unknown over a confident figure — but an
epoch left unrecorded interacts with the walk's no-gaps property and with retention.
Acceptance
A census run against a degraded or single-source chain view cannot seal a requirement that a healthy
run would have computed differently — either because it does not record, or because it can later be
superseded by this node's own better-corroborated census. Peer records still cannot supersede a
Censusedrecord, and the walk stays idempotent.Verify by probe, not by reading: the PR #401 gate demonstrated that a census result can be moved by
controlling the source, which is the same lever this defect uses. A test that degrades the source mid-walk
and asserts the outcome is the direct proof.