Skip to content

feat(dig-node): peer provider-snapshot mTLS RPC + concrete NeighbourhoodProbe - #157

Merged
MichaelTaylor3d merged 1 commit into
mainfrom
feat/peer-provider-snapshot-rpc
Aug 3, 2026
Merged

feat(dig-node): peer provider-snapshot mTLS RPC + concrete NeighbourhoodProbe#157
MichaelTaylor3d merged 1 commit into
mainfrom
feat/peer-provider-snapshot-rpc

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

Part of DIG-Network/dig_ecosystem#1989 — child 4/7 of epic #1934, PR 4a of 2 (4b = the governed prefetch loop, a separate later PR). SECURITY-CRITICAL: the anti-Sybil identity-binding layer.

What changed

Provides the concrete NeighbourhoodProbe (child 2 left it a mocked trait seam) + the node↔node RPC it needs to obtain other peers' provider snapshots.

  • Server dig.getProviderSnapshot peer RPC — answers with the node's LOCAL DhtService::provider_snapshot(max_keys), reusing the RLY-009 DhtRecordsAnswer wire shape verbatim (counts-only, no provider identities). max_keys clamped to MAX_PROVIDER_SNAPSHOT_KEYS = 512 (bounded by the 64 KiB read_framed frame ceiling). Answered at the NodeResponder level (it needs self.dht, which the FFI-safe handle_rpc lacks), async-awaited (no block_in_place — that's only needed for RLY-009's sync callback and would panic under the current-thread test runtime).
  • Client + concrete DhtNeighbourhoodProbe::observe_near(point)find_node(point) → for each Contact, mTLS-dial + call the RPC → one PeerObservation per responding peer.
  • Allowlist:dig-rpc-protocol is a crates.io 0.6 pin (can't extend), so this is a dig-node-local method — an explicit, documented exception in is_peer_reachable_method with a regression test asserting it's the ONE deliberate local addition (the shared peer_reachable_names() set is unchanged).

Security properties (the point)

  • Anti-Sybil identity (load-bearing):PeerObservation.peer_id is set ONLY from the verified session SPKI (PeerConnection.peer_id = SHA-256(SPKI DER), checked by dig-tls) — NEVER Contact.peer_id, NEVER any payload field (the answer is deliberately identity-free). Proven by a unit test (Contact claims 0xAA…, session verifies 0xBB… → observation attributed to 0xBB…) + a real-wire two-node test asserting peer_id == the responding server's SPKI. This is what makes child 2's quorum "one peer = one vote" hold.
  • Caps before reconcile():MAX_HOLDINGS_PER_PEER = 512, MAX_OBS_PER_ROUND = 4096 — truncated so a lying peer can't OOM the reconciler.
  • Silent/unreachable/mis-identified peer → nothing, never an error (matches the trait contract).

How verified

  • cargo fmt --all -- --check → 0; cargo clippy -p dig-node-core --all-targets --all-features -- -D warnings → 0; cargo build -p dig-node-core → 0.
  • New neighbourhood_probe module: 11/11 pass (server clamp + empty-DHT, parse good/malformed, identity binding, silent peer, per-peer cap at/over bound, round cap, malformed-key drop, the two-node real-wire test); peer allowlist tests 4/4. The 7 broader peer::tests failures are the known sandbox socket-bind limit (pass on real runners), none touch this code. Coverage deferred to CI's ≥80% gate (local llvm-cov >2min timeout).

Version

dig-node-core 0.31.0 → 0.32.0; root [workspace.package].version 0.80.0 → 0.81.0; Cargo.lock updated. SPEC.md (+ dig-node-core SPEC) document the method + bounds + the verified-identity contract.

Follow-ups (orchestrator-owned / cross-repo)

  • SYSTEM.md gets dig.getProviderSnapshot as a node↔node peer RPC reusing RLY-009's DhtRecordsAnswer shape (I'll add it).
  • A release-first ticket to promote the method into the shared dig-rpc-protocol allowlist so the local exception can be removed.

Generated by Claude Code

…oodProbe (#1989)
Add the anti-Sybil identity-binding layer of the DHT-sampling flywheel
(epic #1934 child 4a): the dig.getProviderSnapshot peer RPC and the concrete
DhtNeighbourhoodProbe that consumes it.
Server: dig.getProviderSnapshot answers this node's LOCAL DHT provider store,
reusing the RLY-009 DhtRecordsAnswer wire shape verbatim (counts only, never
provider identities). max_keys is clamped to a frame-safe 512-key cap. Added to
the peer-reachable allowlist as a dig-node-local method (the shared
dig-rpc-protocol allowlist is a pinned dep we cannot extend; promoting it is a
cross-repo follow-up), answered at the NodeResponder like dig.getPeers.
Client + probe: DhtNeighbourhoodProbe routes toward a keyspace point with
find_node, fetches each peer's snapshot over mTLS, and builds one PeerObservation
per responding peer. THE load-bearing rule: PeerObservation.peer_id is set from
SHA-256(verified mTLS server-cert SPKI DER) of the session, NEVER Contact.peer_id
or any payload field. Per-peer holdings and per-round observation volume are
capped before reconcile so a lying peer cannot OOM the reconciler. An
unreachable/silent/erroring peer yields nothing, never an error.
Tests cover the anti-Sybil identity binding (a peer claiming a different id in
its Contact is attributed to its real SPKI), the volume caps, the bounded
counts-only server, the silent-peer contract, the wire-shape parse, and a
real-wire two-node round-trip proving peer_id == the responding server's SPKI.
Scope: RPC (server+client) + concrete probe + caps only. The prefetch loop
(selection, fetch, cache writes) is child 4b.
Version: dig-node-core 0.31.0 -> 0.32.0 (MINOR, new capability); workspace
0.80.0 -> 0.81.0.
Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d
MichaelTaylor3d merged commit 4d85826 into mainAug 3, 2026
15 checks passed
@MichaelTaylor3d
MichaelTaylor3d deleted the feat/peer-provider-snapshot-rpc branch August 3, 2026 01:50
MichaelTaylor3d added a commit that referenced this pull request Aug 3, 2026
Rebased onto #157; main took 0.81.0/0.32.0 while this was in review.
Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@MichaelTaylor3d@claude