dig_pex/lib.rs
1//! # dig-pex — Peer Exchange (PEX) for the DIG Node peer network
2//!
3//! PEX lets a participant that already holds an **authenticated** link to another participant tell
4//! it, incrementally, which peers it knows **first-hand** — so the network's address books stay warm
5//! without polling and without a central directory. It adapts the proven mechanics of BitTorrent PEX
6//! (`ut_pex`): peers exchange **deltas of their first-hand known-peer set** over already-established
7//! connections, on a bounded periodic cadence, with hard per-message caps and **no third-party
8//! re-flooding**. This crate is the normative implementation of `SPEC.md` (wire version `1`).
9//!
10//! PEX runs in exactly two places (SPEC §1.1):
11//!
12//! 1. **Node ↔ Node** — over the mutual-TLS dig-nat mux stream transport ([`PexMessage::encode`] /
13//! [`PexMessage::decode`], a u32-BE length prefix + JSON body).
14//! 2. **Relay → Node** — by the `dig-relay` introducer, riding the existing `RelayMessage`
15//! WebSocket (RLY-008) as bare JSON text frames ([`PexMessage::to_json`] /
16//! [`PexMessage::from_json`]).
17//!
18//! ## What it is *not* (SPEC §1.3)
19//!
20//! - **Not a trust channel.** Every received entry is a *hint* — a candidate to dial and verify via
21//! the mTLS handshake, never an authenticated fact ([`PexEvent::Candidates`]).
22//! - **Not a gossip flood.** A participant advertises only what it knows **first-hand**; the
23//! [`Provenance`] type has no `"pex"` token, so a PEX-learned entry can never be re-advertised
24//! until independently verified.
25//! - **Not content discovery.** Locating which peers hold content is the DHT's job (`dig-dht`); PEX
26//! populates the pool of dialable peers underneath it.
27//!
28//! ## The engine (SPEC Appendix A)
29//!
30//! The crate ships a transport-agnostic, **sans-IO** [`PexEngine`]: you feed it link events, inbound
31//! messages, local peer-set changes, and clock ticks; it returns the messages to send and the events
32//! to act on. Both a DIG Node and the relay embed the same engine — only the I/O adapter differs.
33//!
34//! ```
35//! use dig_pex::{PexConfig, PexEngine, PexMessage, PeerEntry, Provenance, Address};
36//!
37//! let me = "a".repeat(64);
38//! let peer = "b".repeat(64);
39//! let mut engine = PexEngine::new(PexConfig::new(me, "mainnet").with_jitter(false));
40//!
41//! // A first-hand peer we know enters our advertise set.
42//! engine.upsert_known(
43//! PeerEntry::new("c".repeat(64), "mainnet", 1_000, Provenance::Direct)
44//! .with_address(Address::direct("203.0.113.7", 9444)),
45//! );
46//!
47//! // A link comes up → we emit our handshake + a snapshot of our first-hand set.
48//! let out = engine.link_up(&peer, 1_000_000);
49//! assert!(matches!(out[0], PexMessage::PexHandshake { .. }));
50//! assert!(matches!(out[1], PexMessage::PexSnapshot { .. }));
51//! ```
52//!
53//! ### DIG Node embedding (node↔node, SPEC §10.1)
54//!
55//! On each established peer connection call [`PexEngine::link_up`] and write the returned frames on a
56//! freshly opened mux stream (that stream is your sending direction). Feed each decoded inbound
57//! message to [`PexEngine::on_message`]; send its replies and honor a muting
58//! [`PexEvent::Violation`]. Drive [`PexEngine::tick`] ~1/s and write the returned deltas. Feed
59//! first-hand knowledge back with [`PexEngine::upsert_known`] / [`PexEngine::remove_known`], and on
60//! close call [`PexEngine::link_down`]. Route [`PexEvent::Candidates`] into the dig-gossip
61//! `AddressManager` as new-table candidates to dial + verify (SPEC §9.3).
62//!
63//! ### dig-relay embedding (relay→node, SPEC §10.2)
64//!
65//! Create one engine for the introducer role (flags `["introducer"]`). Only after a registered
66//! connection sends its `pex_handshake` do you [`PexEngine::link_up`] + [`PexEngine::on_message`] and
67//! reply as WebSocket text frames. Mirror the registry into the engine
68//! ([`PexEngine::upsert_known`] on register, [`PexEngine::remove_known`] on unregister); **never**
69//! fold inbound node PEX data into the registry — discard node-sent [`PexEvent::Candidates`] (the
70//! registry is registration-backed only, SPEC §10.2).
71
72#![forbid(unsafe_code)]
73#![warn(missing_docs)]
74
75pub mod caps;
76pub mod engine;
77pub mod entry;
78pub mod error;
79pub mod state;
80pub mod timer;
81pub mod wire;
82
83pub use caps::{
84 PEX_ARRIVAL_GRACE, PEX_DEFAULT_INTERVAL, PEX_MAX_ADDED, PEX_MAX_ADDRESSES, PEX_MAX_DROPPED,
85 PEX_MAX_ENTRY_AGE, PEX_MAX_FLAGS, PEX_MAX_FLAG_LEN, PEX_MAX_FRAME, PEX_MAX_INTERVAL,
86 PEX_MAX_SNAPSHOT, PEX_MIN_INTERVAL, PEX_VERSION, PEX_VIOLATION_LIMIT,
87};
88pub use engine::{PexConfig, PexEngine, PexEvent, PexOutcome};
89pub use entry::{Address, AddressKind, PeerEntry, Provenance, ValidateCtx};
90pub use error::{EntrySkip, PexErrorCode};
91pub use state::{LinkState, RecvPhase};
92pub use wire::PexMessage;