Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
156 changes: 154 additions & 2 deletions 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.222.0"
version = "0.223.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
45 changes: 44 additions & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -8551,7 +8551,9 @@ coin. The `intended_coin_id` is recorded at submission so §23.5's reconcile acc
> not.** The stability-across-a-window rule, in both directions, is satisfied by the pure tracker in
> `mirror/presence.rs` and its `SETTLING_WINDOW_MS`, and `mirror::runner::PassRunner` debounces the
> advertisable half of every observation through it. **Nothing feeds THAT**: there is no periodic
> scan, no start-up scan and no watcher, because `MirrorEffects::observe_disk` has no implementation
> scan, no start-up scan, because `MirrorEffects::observe_disk` has no implementation. The WATCHER half is
> no longer pending: `mirror/events.rs` watches the capsule cache and accelerates the pass, bounded
> by the four rules below
> — so the scanning cadence described below, the un-debounced start-up exemption, and the claim that
> the periodic pass is the correctness mechanism are all still pending, tracked as
> <https://github.com/DIG-Network/dig-node/issues/412>.
Expand All @@ -8560,6 +8562,47 @@ Presence changes are detected by SCANNING, with an optional watcher as an accele
reverse. A watcher event is exactly what a crash, an unmounted volume, or an uncovered path loses;
the periodic pass (§25.4) is the correctness mechanism.

The watcher is implemented (`mirror/events.rs`). It watches the capsule cache directory and MUST
obey all four of the following; a node whose watcher cannot be established, or whose events are all
dropped, MUST still converge on the round timer alone.

1. **An event MAY only lower the instant of the next pass, never raise it.** The round deadline is
computed on entry to the wait and every return happens at or before it, so the timer is a
backstop rather than a fallback. The round length MUST NOT be lengthened to compensate for having
events.
2. **Events are COALESCED, never queued.** The pending state is one instant, so N events in a window
— including events arriving while a pass is running or wedged — owe exactly ONE wake.
3. **An observing wake fires after `QUIET_PERIOD_MS` (5_000) of quiet**, and schedules exactly one
**settling** wake `SETTLING_WINDOW_MS` later. That second wake MUST NOT re-arm, so a burst of any
size causes at most two passes.
4. **No two event-driven passes are closer than `SETTLING_WINDOW_MS`.** A pass cannot act on a change
the tracker has not yet seen hold for a window, so anything closer is amplification on a path that
spends money.

**Chain events do NOT trigger a pass, and no mechanism exists by which they could.** Chain is
observed inside the pass, on the round timer. This is a limitation of the interfaces available, not
only a design preference, and the two are worth separating:

- **There is no chain event to subscribe to.** `MirrorEffects::observe_chain` reads through
`ChainSource` (`dig-chainsource-interface`), whose entire surface is request/response —
`coin_record`, `coin_records_by_puzzle_hash`, `coin_records_by_parent`, `coin_spend`. It exposes
no subscription, no stream and no callback, so there is nothing for a waiting pass to select on.
The node's own §14.2 chain-watch is likewise a POLL loop, not a push source.
- **The one push path in the node cannot say a mirror coin changed.** The wallet's direct-peer sync
(§18.6) does hold a real `request_puzzle_state(subscribe = true)` subscription and publishes to
the §18.14 `EventBus` — but `SyncEvent::CoinState` is fieldless. It names no coin, no puzzle hash
and no height, and it reports the WALLET DB rather than the mirror's chain view. Waking a pass on
it would wake a money-spending pass on any wallet coin activity whatsoever, with no evidence the
event was relevant, at up to the `SETTLING_WINDOW_MS` floor rather than the round.

Independently of both, the two things a pass acts on are not chain-shaped: a CREATE is decided from
disk presence, which IS event-driven; and the epoch rollover a RECLAIM waits on is wall-clock.

What a chain event WOULD buy is freshness of the §25.8 observation — a mirror coin spent out from
under this node is reported up to one round late. That is a staleness bound on a read-only surface,
never a money-safety gap, and closing it needs a coin-state push carrying the coin it is about.
Tracked as <https://github.com/DIG-Network/dig-node/issues/482>.

The debounce is **presence-stable-for-a-window**, not a timer after an event: a bond must be
observed in the SAME state across `SETTLING_WINDOW_MS` (default 30_000) before that state is acted
on, in BOTH directions. An event-reset timer never settles under repeated rewrites and cannot see
Expand Down
6 changes: 6 additions & 0 deletions crates/dig-node-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,12 @@ dig-urn-resolver = "0.5.3"
# line (dig_ecosystem#3161) — so the two node crates can never disagree about a canonical value.
dig-constants = "0.13.0"

# Filesystem notification, for the §25 mirror pass's disk-event accelerant (dig-node#465). The
# watcher is strictly an ACCELERANT: the round timer and the start-up reconcile remain the
# correctness path, and `watch_capsule_cache` returns `None` rather than erroring when no watcher
# can be established, so a platform this crate cannot watch simply keeps the timer.
notify = "8"

# Windows Service Control Protocol. service-manager only REGISTERS the service in the
# SCM; the binary the SCM launches must itself speak the service protocol
# (StartServiceCtrlDispatcher → report RUNNING/STOPPED) or the SCM kills it with error
Expand Down
Loading
Loading