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
30 changes: 15 additions & 15 deletions Cargo.lock

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

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,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.65.1"
version = "0.66.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 All @@ -54,8 +54,8 @@ overflow-checks = true
# standalone. `native-tls` is NOT patched: dig-gossip is pulled with
# `default-features = false, features = ["rustls", "relay"]`, so the OpenSSL/native-tls path is off.
[patch.crates-io]
chia-protocol = { git = "https://github.com/DIG-Network/dig-gossip", rev = "dbb03430f2a9c5cebd5b6f97ac86b03012be5a41" }
chia-sdk-client = { git = "https://github.com/DIG-Network/dig-gossip", rev = "dbb03430f2a9c5cebd5b6f97ac86b03012be5a41" }
chia-protocol = { git = "https://github.com/DIG-Network/dig-gossip", rev = "3e352d91fbda830e517c914cf4f6afa6a9a7e121" }
chia-sdk-client = { git = "https://github.com/DIG-Network/dig-gossip", rev = "3e352d91fbda830e517c914cf4f6afa6a9a7e121" }

# The dig-nat unification patch is RETIRED (#1280 crates.io cascade): dig-nat 0.7 is on crates.io and
# the ENTIRE peer stack (dig-gossip 0.7.1, dig-dht 0.2.2, dig-download 0.2.1, dig-peer-selector 0.2.1,
Expand Down
34 changes: 34 additions & 0 deletions DEVELOPMENT_LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,40 @@ High-signal realizations from debugging/development: non-obvious cross-system co
sharp edges, and gotchas. Concise durable facts with context — NOT a change diary. See
`CLAUDE.md` §4.5 for the maintenance contract (a curator periodically re-verifies + prunes).

## A git-pinned peer-stack dep hides shipped fixes indefinitely — the network looked dead for 11 minors (#1771)

dig-gossip is a **git dependency pinned by rev** (it cannot go to crates.io until dig-peer-protocol
#681 lands), so it does not participate in any automated dependency cascade: nothing bumps it, and
`cargo update` will not move it. It sat at a v0.16.0-era rev while eleven minors accumulated upstream,
including all THREE duplicate-connection fixes (#1691 inbound, #1703 `connect_to`, #1762
`adopt_nat_connection`). A live EC2 run reproduced all three symptoms *simultaneously* — the fixes were
real, released, tested, and simply not present in the binary. **A rev-pinned dep needs a deliberate,
scheduled bump; treat "the fix is released" as unproven until the pin says so.**

Two couplings make that bump non-trivial, and both are structural rather than incidental:

- **The peer stack's dig-nat major moves as ONE atomic step.** dig-node-core constructs its own
`NodeCert` / `NatConfig` / `NatRuntime` / `RelayStatus` / `TraversalKind` values and passes them INTO
dig-download, dig-gossip and dig-peer-selector. Two dig-nat instances in the tree therefore do not
merely bloat it — those calls stop typechecking (`E0308 expected dig_nat::relay::RelayStatus, found
dig_nat::RelayStatus`). So bumping dig-gossip across a dig-nat major forces dig-nat + dig-dht +
dig-download + dig-peer + dig-peer-selector in the same commit, and it is blocked outright until every
member of the line is published. `tests/dependency_tree.rs` asserts the single-instance invariant
against the resolved lock, which is what makes a partial cascade fail loudly instead of oddly.
- **The vendored-fork patch revs move in lockstep with the dep rev (the #1529 3-rev rule).** A git
dep's own `[patch.crates-io]` does NOT apply transitively, so the workspace re-declares dig-gossip's
vendored additive `chia-protocol`/`chia-sdk-client` at the SAME rev. Leaving the patch rev behind
resolves two copies of a patched crate — a tree that builds green and behaves oddly.

**The behavioural half of a bump matters more than the compile half.** The v0.17 pool republishes
`PoolEvent::PeerAdded` when a fresh session supersedes a stale slot for the same `peer_id`, so any
consumer that counts `PeerAdded` events over-counts under reconnect churn — all of dig-node's consumers
are keyed by `peer_id` and idempotent, so they were safe, but the download-side connected pool APPENDED
the new address and left the superseded (typically dead) one leading the dial order, costing a failed
dial per fetch. Likewise `GossipStats::total_connections` is a LIFETIME counter a supersede increments;
only `connected_peers` / `pool_stats().connected` (live keyed-map sizes) are unique-peer counts. When
bumping a dep whose fix removes a REFUSAL, audit what the refusal was silently keeping rare.

## A mock-transport regression test proves NOTHING about the wire — the read leg died in JSON serde (#1586)

`dig.fetchRange` frames are JSON, so the ciphertext window travels as **base64** — the canonical
Expand Down
8 changes: 7 additions & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -2953,7 +2953,13 @@ relayed / isolated network a holder is discovered in the DHT but its advertised
addresses the reader cannot dial, so a DHT-only locate yields no REACHABLE source and the read
dead-ends at the §21 upstream backfill (404) even though the reader is ALREADY CONNECTED to that holder.
Offering every connected pool peer as a fetch candidate lets the download reach the holder over the
established connection. A connected-pool holder is NOT gated behind a separate `dig.getAvailability`
established connection. A pool peer's candidate MUST list its addresses newest-session-first: dig-gossip
republishes `PoolEvent::PeerAdded` when a freshly authenticated session SUPERSEDES a stale slot for the
same `peer_id` (newest-wins re-adoption), and that is typically a MOVE — a dead relay circuit replaced by
a direct dial. The superseded addresses are RETAINED as trailing fallbacks (a still-working older path is
not discarded) but MUST NOT be dialed first. Re-adoption MUST NOT grow the candidate set: the pool is
keyed by `peer_id`, so a republished `PeerAdded` upserts one entry — a re-adopted peer is one candidate,
never two. A connected-pool holder is NOT gated behind a separate `dig.getAvailability`
confirm probe: the download transport short-circuits the confirm to *available* for any provider whose
`peer_id` is currently in the connected pool (`PoolConfirmTransport`, #836), so the fetch proceeds
straight to `dig.fetchRange`. This is required because dig-download's `locate_and_confirm` DROPS any
Expand Down
64 changes: 38 additions & 26 deletions crates/dig-node-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -136,30 +136,42 @@ getrandom = "0.2"
# RECEIVER (`ChunkLensAssembler`) plus the `split_chunk_lens_pages` helper, so 0.13 carries the whole
# serve-side fix for #1640/#1668.
#
# 0.13 rather than 0.14 is REQUIRED, not merely current. This crate builds its own `dig_nat` `NodeCert`
# / `NatConfig` / `NatRuntime` / `RelayStatus` / `TraversalKind` values and passes them INTO
# dig-download, dig-gossip and dig-peer-selector, so a second dig-nat instance does not merely bloat
# the tree -- those calls stop typechecking (verified: E0308 `expected dig_nat::relay::RelayStatus,
# found dig_nat::RelayStatus`). dig-download 0.12 is now on dig-nat ^0.14, but dig-gossip 0.16 and
# dig-peer-selector 0.6 are still on ^0.13, so requesting 0.14 resolves TWO dig-nat and TWO dig-dht.
# Moving to the 0.14 line needs dig-gossip and dig-peer-selector published on it FIRST -- tracked as
# DIG-Network/dig_ecosystem#1686 (release-first,
# §4.1); `tests/dependency_tree.rs` asserts the single-instance invariant against the resolved LOCK so
# a manifest edit cannot quietly reintroduce the duplicate.
dig-nat = "0.13"
# dig-gossip is the ONE peer-stack exception: at v0.15.2 (on dig-nat 0.11 + dig-tls 0.3, crates.io
# deps) but NOT yet published to crates.io — its own publish is guarded pending dig-peer-protocol (#681).
# So it stays a git dependency, PINNED to the v0.15.2 tag commit (rev dbb03430f2a9, #1584 — both
# outbound pool-add sites (`connect_to` direct-dial + `adopt_nat_connection` NAT-ladder) now reject
# `peer_id == config.peer_id` before any pool insert / `PeerAdded` publish, closing the self-dial
# read-leg 404: a relay introducer advertising a reader to itself no longer gets adopted as its own
# provider), on top of #1581's `PoolEvent::PeerAdded` (feeding DHT routing/selector/PEX), #1541's
# persistent `nat_identity` NodeCert injection, and #1517's relay-circuit auto-dial. Pulled rustls-only (no
# native-tls). Its dig-nat/dig-tls/dig-constants come from crates.io (same versions as here), so the
# The dig-nat MAJOR must match the whole peer stack's, and it moves as ONE step (#1771). This crate
# builds its own `dig_nat` `NodeCert` / `NatConfig` / `NatRuntime` / `RelayStatus` / `TraversalKind`
# values and passes them INTO dig-download, dig-gossip and dig-peer-selector, so a second dig-nat
# instance does not merely bloat the tree -- those calls stop typechecking (verified: E0308 `expected
# dig_nat::relay::RelayStatus, found dig_nat::RelayStatus`). So dig-nat cannot be bumped alone, and
# dig-gossip/dig-dht/dig-download/dig-peer/dig-peer-selector cannot be bumped past it: the 0.14 line
# (dig-gossip 0.17, dig-dht 0.8, dig-download 0.12, dig-peer 0.7, dig-peer-selector 0.7) is a single
# atomic cascade, which #1686 completed by publishing every member. `tests/dependency_tree.rs` asserts
# the single-instance invariant against the resolved LOCK so a partial cascade cannot quietly
# reintroduce the duplicate.
dig-nat = "0.14"
# dig-gossip is the ONE peer-stack exception: not yet published to crates.io (its publish is guarded
# pending dig-peer-protocol, #681), so it stays a git dependency PINNED to a release commit — here
# v0.17.12 (rev 3e352d91).
#
# v0.17.12 is REQUIRED, not merely current (#1771): it carries all THREE duplicate-connection fixes,
# and until it landed here the node's pool refused connections that would have worked. Every pool-add
# path now supersedes a stale slot for an already-authenticated `peer_id` instead of refusing the newer
# session — inbound (#1691), `connect_to` (#1703), and `adopt_nat_connection` (#1762), the last being
# the path EVERY dig-nat connection is adopted through, where a dead relay circuit's leftover slot
# refused the direct dial while the peer reported zero connections. `DuplicateConnection` still exists
# as an error variant but is no longer raised on those paths. Consequence for this crate: `PeerAdded` is
# REPUBLISHED on a supersede, so every consumer of it must be keyed by `peer_id` and idempotent (the DHT
# routing feed, the selector registry, and the download-side connected pool all are — see
# `download.rs::on_pool_event`), and `GossipStats::total_connections` is a LIFETIME counter that a
# supersede increments, so it must never be read as a count of unique peers (this crate reads
# `connected_peers` / `pool_stats().connected`, which are the live keyed-map sizes).
#
# Also on 0.17: the outbound /16+AS diversity caps (#1710/#1716) apply to the adoption path, so a
# test fixture adopting two peers must place them in DIFFERENT /16 groups.
# Pulled rustls-only (no native-tls) -- note `--all-features` is NOT a runnable configuration for
# dig-gossip: with both `ring` and `aws-lc-rs` reachable rustls cannot pick a provider. Its dig-nat/dig-tls/dig-constants come from crates.io (same versions as here), so the
# graph still unifies to ONE of each. The vendored chia-protocol/chia-sdk-client forks it path-patches
# are re-declared at this SAME rev in the workspace `[patch.crates-io]` (a git dep's own patches do not
# apply transitively — the #1529 3-rev lockstep: this dep rev + the two patch revs move together).
dig-gossip = { git = "https://github.com/DIG-Network/dig-gossip", rev = "b065e70c5bc4ad8f343d363e71c605870a4923d2", default-features = false, features = ["rustls", "relay"] }
dig-gossip = { git = "https://github.com/DIG-Network/dig-gossip", rev = "3e352d91fbda830e517c914cf4f6afa6a9a7e121", default-features = false, features = ["rustls", "relay"] }
# The canonical DIG network id (genesis challenge) for the gossip handshake / introducer scope. From
# crates.io: dig-nat 0.7 requires `>=0.4, <0.6` and dig-gossip 0.7.1 requires `>=0.2, <0.5`, so `0.4`
# sits in both windows and the whole graph resolves to ONE crates.io dig-constants 0.4.x.
Expand All @@ -175,7 +187,7 @@ dig-constants = "0.4"
# so cargo unifies to ONE dig-dht instance — dig-dht's `PeerId` / `Contact` / `DhtTransport` share the
# exact types `dig_nat::connect` returns. dig-dht 0.5 deps dig-nat "^0.10" (crates.io), the SAME
# dig-nat "0.10" this crate pins, so the whole graph shares ONE dig-nat 0.10 instance.
dig-dht = "0.7"
dig-dht = "0.8"
# -- L7 Peer Exchange (PEX, #166) --------------------------------------------------------------------
# The node<->node peer-sharing protocol: over each established mTLS peer connection, a dedicated PEX
# logical stream exchanges delta-based first-hand known-peer sets (handshake -> snapshot -> deltas),
Expand Down Expand Up @@ -210,7 +222,7 @@ dig-pex = "0.1"
# `seams/dig_peer/module_anchor.rs` (`ChainAnchoredModuleVerifier`). NOTE the `testkit` feature is
# deliberately NOT enabled: it is what makes the fail-OPEN `AcceptAnyModuleAnchor` nameable, and this
# crate's anchor gate is the reshare path's ONLY root of trust.
dig-download = "0.11"
dig-download = "0.12"
# -- The shared peer client (#1283/#1576) -------------------------------------------------------------
# `DigPeer` — the ONE DIG Network peer client: peer_id-pinned mTLS over the full NAT ladder plus typed
# RPC. Depended on DIRECTLY (not only transitively through dig-download) because dig-node supplies the
Expand All @@ -222,7 +234,7 @@ dig-download = "0.11"
# module pull's trust boundary — on the fields that drive the whole pull plan. dig-download 0.8.1 is on
# dig-peer 0.5 too, so exactly ONE dig-rpc-protocol + ONE dig-peer resolve here (asserted by
# `crates/dig-node-core/tests/dependency_tree.rs`).
dig-peer = "0.6"
dig-peer = "0.7"
# -- Self-optimizing peer selection (#178) ------------------------------------------------------------
# The decision + learning layer between dig-dht discovery and dig-download execution: it ranks the
# providers `find_providers` returns (learning throughput/rtt/reliability + a per-class saturation
Expand All @@ -239,7 +251,7 @@ dig-peer = "0.6"
# dig-nat/dig-dht types). The selector mirrors `dig_gossip::PoolEvent` LOCALLY (it does not depend on
# dig-gossip, which lags as a git dep), so the node maps `dig_gossip::PoolEvent` → the selector's local
# `PoolEvent` 1:1 (see `download.rs::pool_event_to_selector`).
dig-peer-selector = "0.6"
dig-peer-selector = "0.7"
# The canonical DIG mTLS certificate crate (L00, crates.io). The node's PERSISTENT machine identity
# is a CA-signed `dig_tls::NodeCert` minted from the node's own BLS identity key and persisted 0600 in
# the data dir (#908 identity boundary: this is the MACHINE key, never a user key). Replaces the
Expand Down Expand Up @@ -289,7 +301,7 @@ rcgen = "0.13"
#
# Pinned by the `the_fail_open_anchor_verifier_is_not_reachable_from_a_production_build` test, which
# fails if `testkit` ever appears on the production entry.
dig-download = { version = "0.11", features = ["testkit"] }
dig-download = { version = "0.12", features = ["testkit"] }
# Captures the peer-facing serve's real emitted tracing records into an in-memory buffer, so the
# serve-observability tests (#1595) assert what an operator would actually see in the node log —
# and that no payload byte or proof ever reaches it.
Expand Down
Loading
Loading