Skip to main content

dig_peer_selector/
pool_event.rs

1//! [`PoolEvent`] / [`PoolRemovalReason`] — the topology/churn feed, byte-compatible with
2//! `dig_gossip::PoolEvent` (SPEC.md §5.4, §7.2).
3//!
4//! # Why this shape is mirrored, not imported
5//!
6//! SPEC §5.4/§11 originally required `on_pool_event` to consume `dig_gossip::PoolEvent` **verbatim**.
7//! In practice `dig-gossip` pulls the entire chia-protocol / consensus / TLS stack into what is a
8//! pure decision layer, and its published git tip does not currently compile as a dependency (it
9//! lags the upstream `chia-*` crate versions it references). Taking that dependency would both bloat
10//! and *break* this crate's build — contradicting the SPEC's own dependency-minimalism principle
11//! (§1, §11) and the `dig-dht` / `dig-pex` precedent, which deliberately avoid `dig-gossip` for
12//! exactly this reason.
13//!
14//! This crate therefore mirrors the churn-event shape locally — **byte-identical** field names and
15//! variants, over the SAME re-used [`dig_nat::PeerId`] — exactly as `dig-pex` mirrors the L7 address
16//! shape "rather than pulling those crates in." The host adapter (`dig-node`) converts a
17//! `dig_gossip::PoolEvent` into this type with a trivial 1:1 field map. Because the shapes are
18//! identical, the contract is preserved; the SPEC records this deviation (§5.4, §7.2, §11, SEL-01).
19//!
20//! When `dig-gossip` is published to crates.io with a compiling tip, this module MAY be replaced by a
21//! direct re-export without changing any field name or variant — the shapes are the same.
22
23use std::net::SocketAddr;
24
25use dig_nat::PeerId;
26
27/// A churn event as the connected pool gains or loses a peer — byte-compatible with
28/// `dig_gossip::PoolEvent`.
29///
30/// The host subscribes to `dig-gossip`'s pool events and forwards each one to the selector via
31/// [`crate::PeerSelector::on_pool_event`], which keeps the registry live (SPEC §2.3):
32/// - `PeerAdded` **upserts** a registry entry (provenance [`crate::Provenance::Gossip`]), preserving
33///   any existing learned quality (a reconnecting peer keeps its history — SPEC §2.3);
34/// - `PeerRemoved` marks the peer **disconnected** but **retains** its entry + learned quality so a
35///   later reconnect resumes from history (subject to eviction — SPEC §2.5); a `Banned` reason makes
36///   the peer ineligible for selection until re-added (SPEC §9.4).
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum PoolEvent {
39    /// A peer was added to the connected pool (dialed successfully, or accepted inbound and adopted).
40    PeerAdded {
41        /// The verified peer identity now in the pool.
42        peer_id: PeerId,
43        /// The remote endpoint the connection runs over (peer, or relay for a relayed link).
44        addr: SocketAddr,
45    },
46    /// A peer left the connected pool (disconnected, evicted dead/stale, or banned).
47    PeerRemoved {
48        /// The peer identity that is no longer connected.
49        peer_id: PeerId,
50        /// Why it left.
51        reason: PoolRemovalReason,
52    },
53}
54
55/// Why a peer was removed from the pool — byte-compatible with `dig_gossip::PoolRemovalReason`.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum PoolRemovalReason {
58    /// A normal disconnect (peer closed, or we called `disconnect`).
59    Disconnected,
60    /// Evicted because keepalive found it dead / unresponsive.
61    Dead,
62    /// Removed because the peer was banned for misbehaviour — ineligible until re-added (SPEC §9.4).
63    Banned,
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    fn pid(b: u8) -> PeerId {
71        PeerId::from_bytes([b; 32])
72    }
73
74    #[test]
75    fn pool_event_variants_construct_and_compare() {
76        let addr: SocketAddr = "203.0.113.7:9444".parse().unwrap();
77        let a = PoolEvent::PeerAdded {
78            peer_id: pid(1),
79            addr,
80        };
81        let b = PoolEvent::PeerRemoved {
82            peer_id: pid(1),
83            reason: PoolRemovalReason::Banned,
84        };
85        assert_ne!(a, b);
86        assert_eq!(
87            a.clone(),
88            PoolEvent::PeerAdded {
89                peer_id: pid(1),
90                addr
91            }
92        );
93    }
94
95    #[test]
96    fn removal_reasons_are_distinct() {
97        assert_ne!(PoolRemovalReason::Disconnected, PoolRemovalReason::Banned);
98        assert_ne!(PoolRemovalReason::Dead, PoolRemovalReason::Banned);
99    }
100}