Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
55f0696
chore(mirror): open the funding lane at 0.201.0 (#461, #463)
MichaelTaylor3d Aug 31, 2026
9c16f0d
fix(mirror): skip an unauthenticatable funding candidate instead of r…
MichaelTaylor3d Aug 31, 2026
91b203e
feat(mirror): alert the operator once when funding blocks a mirror cr…
MichaelTaylor3d Aug 31, 2026
0ff6147
style(mirror): rustfmt the funding alert tests
MichaelTaylor3d Aug 31, 2026
e8e0ff4
Merge origin/main into loop/mc-fund
MichaelTaylor3d Aug 31, 2026
fde43d3
feat(mirror): deliver the funding alert from the pass, carrying the g…
MichaelTaylor3d Aug 31, 2026
424206f
fix(mirror): authenticate before bounding, so an attacker cannot choo…
MichaelTaylor3d Sep 1, 2026
8f54219
fix(mirror): a pass that could afford nothing is a shortfall, not a h…
MichaelTaylor3d Sep 1, 2026
f7964fb
docs(spec): state funding-selection authentication order and shortfal…
MichaelTaylor3d Sep 1, 2026
818dfca
Merge origin/main into loop/mc-fund
MichaelTaylor3d Sep 1, 2026
c33826b
test(mirror): prove the funding figures on a chain holding the operat…
MichaelTaylor3d Sep 1, 2026
cc1b374
Merge remote-tracking branch 'origin/main' into loop/mc-fund
MichaelTaylor3d Sep 1, 2026
08befdf
chore: merge main and take 0.222.0
MichaelTaylor3d Sep 1, 2026
b88e522
chore: merge main (#477) and hold 0.222.0
MichaelTaylor3d Sep 1, 2026
1e8a4fe
fix(mirror): no operator money figure is quoted without authenticatio…
MichaelTaylor3d Sep 1, 2026
db3ca04
docs(mirror): point the per-pass authentication budget note at dig-no…
MichaelTaylor3d Sep 1, 2026
3fd6938
style(mirror): rustfmt the new truncated-walk observation arm
MichaelTaylor3d Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ edition = "2021"
# the ROOT manifest (`[workspace.package].version`), so it MUST be set here for a
# release to fire (§3.6). The library crates (dig-node-core/dig-runtime/dig-wallet)
# keep their own independent versions — only the released binary tracks the workspace version.
version = "0.217.0"
version = "0.222.0"

# Release hardening, matching digstore: keep integer-overflow checks ON in release.
# The node parses untrusted serialized input and does offset/length arithmetic over
Expand Down
76 changes: 76 additions & 0 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -8722,3 +8722,79 @@ Changing the value affects only coins created after the change. Bringing an exis
means reclaiming and re-creating it — a round trip and a fee — and the node MUST NOT reclaim in
response to a configuration edit.


### 25.11. Funding a create — authentication precedes every figure the operator is told

The $DIG that funds a create is selected from ONE address: `dig_cat_puzzle_hash(owner)`, the CAT
curry of the operator's own puzzle hash. That address is **publicly derivable** — the owner puzzle
hash is public and the curry is canonical — and anyone may pay any coin to any puzzle hash. So a row
returned by the scan is a CANDIDATE, never a coin, and the number and declared amounts of those rows
are chosen by whoever last paid into the address.

A candidate becomes one of this operator's coins only by AUTHENTICATION: its creating spend is read
from chain and executed, and a CAT child matching the candidate is produced, of the $DIG asset and
at this operator's puzzle hash. The node MUST NOT treat an unauthenticated candidate as money.

**The node MUST authenticate before it computes any figure it reports.** In particular:

* The spendable total in a shortfall MUST be the total of AUTHENTICATED candidates. It MUST NOT be
the address total. An understated total sends an operator to buy $DIG they already hold, and the
§25.12 gate then suppresses the correction as immaterial.
* The input bound MUST be applied to a selection drawn from AUTHENTICATED candidates only. Applied
to the raw scan it is a bound a stranger sets, and the refusal it produces is the operator-facing
claim *the wallet holds enough $DIG, in too many pieces* — so a stranger paying enough small coins
into the address chooses which of two OPPOSITE instructions the operator is given, and an operator
holding nothing is told that adding more will not help.
* A candidate that fails authentication MUST be passed over rather than aborting the selection, MUST
be counted and reported, and MUST NOT occupy an input slot.

**Authentication costs one chain read per candidate, so it MUST be bounded by a constant** that does
not depend on how many candidates exist. Without such a bound the reads one automated pass performs
are chosen by whoever paid coins into the address, on the pass timer, indefinitely.

**A walk truncated by that bound leaves the wallet UNMEASURED.** The node MUST refuse with a
condition of its own that states NO total — it MUST NOT report the total of the candidates that
happened to be walked, and MUST NOT clear a live shortfall. It MUST classify as §25.12's
*unmeasured*, and MUST therefore be reported to the operator without an amount: an operator whose
node has stopped bonding because a stranger filled the scan address is otherwise never told
anything, on any pass, indefinitely.

**A pass that authenticated no candidate at all has no spendable total either.** The commonest such
pass is one that priced a create and could afford none, so the selection was never invoked. It is
still SHORT — authentication only ever removes candidates, so a reported balance below one create's
cost proves the authenticated one is below it too — but the SIZE of the gap is not established, and
the node MUST NOT quote the address total, or any figure derived from it, as the spendable one. It
MUST classify as §25.12's *unmeasured*.

### 25.12. Reporting a funding shortfall to the operator

The node MUST distinguish four funding observations per pass — *healthy*, *short* (with the amount
and the remedy), *unmeasured* (blocked, with no amount), and *unknown* — and MUST raise an
operator-facing message only on a CHANGE: once on entering the short state, again only when the
remedy changes or the deficit grows materially, and once on recovery. An *unknown* observation MUST
raise nothing and MUST NOT clear a live shortfall.

An *unmeasured* observation MUST raise a message once on entering it, MUST state NO spendable total
and NO deficit, and MUST NOT clear a live shortfall. Saying nothing about the AMOUNT and saying
nothing AT ALL are different: the second leaves a node that has silently stopped bonding
unreported, and leaves a shortfall already alerted on latched at a figure that can never be
corrected. The message MUST name the condition and an action the operator can take, and MUST NOT
assert a remedy the observation does not establish — in particular a truncated walk MUST NOT tell
an operator to add $DIG, since adding it need not help.

A *short* observation's spendable total MUST be authenticated (§25.11). A pass that has no
authenticated total is *unmeasured*, never *short with the address total*.

**A pass that planned no create because it could afford none is SHORT, not healthy.** The two
conditions that produce an empty create list are opposites — nothing to bond, and nothing
affordable — and only the first is healthy. Classifying the second as healthy leaves the shortfall
with no producer for the commonest real case (a wallet holding less than one create's collateral,
which never attempts a create and so never produces a funding refusal), and worse, CLEARS a live
shortfall and announces a recovery that has not happened.

The figures a shortfall reports MUST be the money that must actually be ADDED: the authenticated
$DIG remaining after the affordable creates were funded, against the cost of those that were not.
The wallet balance alone overstates what is available towards the unmade creates, and an
unauthenticated balance is in addition a figure a stranger chooses (§25.11) — so where the
remaining $DIG has not been authenticated, the cost of the unmade creates is reported alone, as an
*unmeasured* observation.
Loading
Loading