Task
mirror::funding::select_operator_dig_cats places no bound on how many coins it selects, and each
selected coin costs one further chain read. The scanned address —
dig_cat_puzzle_hash(owner_puzzle_hash) — is derivable by anyone who knows this node's operator
puzzle hash, so the number of candidates is not under this node's control.
Context
crates/dig-node-service/src/mirror/funding.rs:194-215 (as of dig-node#423):
:190 reads every unspent row at the scan hash — one chain call, unbounded row count.
:197-201 filters spent and committed rows.
:207 select_largest_first takes as many as the shortfall needs, with no cap.
:214-216 then calls authenticate(source, record, owner_puzzle_hash) per selected coin, and
authenticate resolves lineage from the creating spend — a chain read each.
A party who sends the operator a large number of dust $DIG CATs makes the selection walk many of
them to reach the required amount, and each one becomes a lineage read. The cost is borne by this
node and by whatever ChainSource it is pointed at.
This is NOT the truncation case: dig_mirror_coin::list's incomplete-scan refusal
(mirror/lifecycle.rs, observe_chain) covers the inventory read, not this selection.
Scope
Assess first, then bound. Two questions the assessment must answer, because they change the shape:
- Is there an amount below which a candidate cannot usefully contribute, given the collateral
amounts §25.3 produces? A dust floor removes most of the population without a cap.
- What is the honest failure when the bound is hit — a refusal (fail closed, consistent with every
other refusal in this module) or a partial selection? A partial selection funds a create at the
wrong amount, which §25.3's apply_safety_margin note says locks money and advertises nothing,
so a refusal is the likely answer and should be stated rather than assumed.
Whatever bound lands must be pinned from BOTH sides in tests: one coin over must refuse, at-bound
must select.
Evidence
Raised by the pre-merge security gate on dig-node#423 as a non-gating follow-up. Not absorbed into
that PR, whose re-gate scope was the reservation defect.
Parent: #421
Task
mirror::funding::select_operator_dig_catsplaces no bound on how many coins it selects, and eachselected coin costs one further chain read. The scanned address —
dig_cat_puzzle_hash(owner_puzzle_hash)— is derivable by anyone who knows this node's operatorpuzzle hash, so the number of candidates is not under this node's control.
Context
crates/dig-node-service/src/mirror/funding.rs:194-215(as of dig-node#423)::190reads every unspent row at the scan hash — one chain call, unbounded row count.:197-201filters spent and committed rows.:207select_largest_firsttakes as many as the shortfall needs, with no cap.:214-216then callsauthenticate(source, record, owner_puzzle_hash)per selected coin, andauthenticateresolves lineage from the creating spend — a chain read each.A party who sends the operator a large number of dust $DIG CATs makes the selection walk many of
them to reach the required amount, and each one becomes a lineage read. The cost is borne by this
node and by whatever
ChainSourceit is pointed at.This is NOT the truncation case:
dig_mirror_coin::list's incomplete-scan refusal(
mirror/lifecycle.rs,observe_chain) covers the inventory read, not this selection.Scope
Assess first, then bound. Two questions the assessment must answer, because they change the shape:
amounts §25.3 produces? A dust floor removes most of the population without a cap.
other refusal in this module) or a partial selection? A partial selection funds a create at the
wrong amount, which §25.3's
apply_safety_marginnote says locks money and advertises nothing,so a refusal is the likely answer and should be stated rather than assumed.
Whatever bound lands must be pinned from BOTH sides in tests: one coin over must refuse, at-bound
must select.
Evidence
Raised by the pre-merge security gate on dig-node#423 as a non-gating follow-up. Not absorbed into
that PR, whose re-gate scope was the reservation defect.
Parent: #421