From a45eefb58aa8d177a745281b102acabd054e0551 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Mon, 13 Jul 2026 03:30:52 +0000 Subject: [PATCH] node: home the peering concept; relocate the connection-retry schedule Create src/node/peering/ as the home for the peer desired-state (homeostatic reconciler) concept and move the cross-attempt connection retry schedule into it: RetryState plus schedule_retry / schedule_reconnect / process_pending_retries. Mechanical relocation only. The methods remain inherent on Node; the retry_count must persist across re-dials (a fresh connection is created each attempt), which is why the schedule belongs in the peering home rather than on a per-connection type. The one-level-deeper module path requires pub(super) -> pub(in crate::node) to preserve the prior scope, plus module-path fixups at the reference sites. No behavior change; unit test count unchanged (1596). --- src/node/lifecycle/mod.rs | 2 +- src/node/mod.rs | 6 +++--- src/node/peering/mod.rs | 12 ++++++++++++ src/node/{ => peering}/retry.rs | 8 ++++---- src/node/tests/unit.rs | 8 ++++---- 5 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 src/node/peering/mod.rs rename src/node/{ => peering}/retry.rs (97%) diff --git a/src/node/lifecycle/mod.rs b/src/node/lifecycle/mod.rs index ea8859f..2bd80bb 100644 --- a/src/node/lifecycle/mod.rs +++ b/src/node/lifecycle/mod.rs @@ -2242,7 +2242,7 @@ impl Node { .or_insert_with(|| peer_identity.short_npub()); self.register_identity(node_addr, peer_identity.pubkey_full()); - let mut state = super::retry::RetryState::new(PeerConfig { + let mut state = super::peering::retry::RetryState::new(PeerConfig { npub: npub.clone(), alias: None, addresses, diff --git a/src/node/mod.rs b/src/node/mod.rs index cbbcfc5..f8665bc 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -15,10 +15,10 @@ pub(crate) mod encrypt_worker; mod handlers; mod lifecycle; pub(crate) mod metrics; +mod peering; mod rate_limit; pub(crate) mod reject; mod reloadable; -mod retry; pub(crate) mod session; pub(crate) mod stats; pub(crate) mod stats_history; @@ -450,7 +450,7 @@ pub struct Node { /// Keyed by NodeAddr. Entries are created when a handshake times out /// or fails, and removed on successful promotion or when max retries /// are exhausted. - retry_pending: HashMap, + retry_pending: HashMap, // === Periodic Parent Re-evaluation === /// Timestamp of last periodic parent re-evaluation (for pacing). @@ -2496,7 +2496,7 @@ impl Node { } /// Iterate over retry state for diagnostics. - pub fn retry_state_iter(&self) -> impl Iterator { + pub fn retry_state_iter(&self) -> impl Iterator { self.retry_pending.iter() } diff --git a/src/node/peering/mod.rs b/src/node/peering/mod.rs new file mode 100644 index 0000000..20f6a7e --- /dev/null +++ b/src/node/peering/mod.rs @@ -0,0 +1,12 @@ +//! Peering homeostasis — the desired-state controller for the node's peer set. +//! +//! This module is the home for the peering-reconciler concept: config defines a +//! desired peer set; the reconciler converges the observed set toward it +//! (auto-connect floor, overlay pool, transport-neighbor growth) under the +//! `node.limits` ceiling. Startup and steady-state are the same loop. +//! +//! The cross-attempt retry schedule (`retry.rs`) lives here because a fresh +//! connection is created per re-dial, so the escalating backoff count must +//! persist in the reconciler, not per-connection. + +pub(in crate::node) mod retry; diff --git a/src/node/retry.rs b/src/node/peering/retry.rs similarity index 97% rename from src/node/retry.rs rename to src/node/peering/retry.rs index 0a97449..f6b2515 100644 --- a/src/node/retry.rs +++ b/src/node/peering/retry.rs @@ -4,10 +4,10 @@ //! automatically retry with exponential backoff. Retry state lives on Node //! (not PeerConnection) because each retry creates a fresh connection. -use super::{Node, NodeError}; use crate::PeerIdentity; use crate::config::PeerConfig; use crate::identity::NodeAddr; +use crate::node::{Node, NodeError}; use crate::proto::fmp::backoff_ms; use tracing::{debug, info, warn}; @@ -55,7 +55,7 @@ impl Node { /// have not been exhausted (unless `reconnect` is true, which retries /// indefinitely). Does nothing if the peer is already connected or has /// a connection in progress. - pub(super) fn schedule_retry(&mut self, node_addr: NodeAddr, now_ms: u64) { + pub(in crate::node) fn schedule_retry(&mut self, node_addr: NodeAddr, now_ms: u64) { let retry_cfg = &self.config().node.retry; let max_retries = retry_cfg.max_retries; if max_retries == 0 { @@ -131,7 +131,7 @@ impl Node { /// preserved and incremented rather than reset to zero. This ensures /// exponential backoff accumulates across repeated link-dead events instead /// of resetting to the base interval on every peer removal. - pub(super) fn schedule_reconnect(&mut self, node_addr: NodeAddr, now_ms: u64) { + pub(in crate::node) fn schedule_reconnect(&mut self, node_addr: NodeAddr, now_ms: u64) { // Find peer in auto-connect config let peer_config = self .config() @@ -196,7 +196,7 @@ impl Node { /// entry stays in `retry_pending` until the connection succeeds (cleared /// in `promote_connection`) or max retries are exhausted (cleared in /// `schedule_retry`). - pub(super) async fn process_pending_retries(&mut self, now_ms: u64) { + pub(in crate::node) async fn process_pending_retries(&mut self, now_ms: u64) { if self.retry_pending.is_empty() { return; } diff --git a/src/node/tests/unit.rs b/src/node/tests/unit.rs index 8e783f2..16233da 100644 --- a/src/node/tests/unit.rs +++ b/src/node/tests/unit.rs @@ -831,7 +831,7 @@ async fn test_process_pending_retries_is_budgeted_per_tick() { let node_addr = *peer_identity.node_addr(); node.retry_pending.insert( node_addr, - crate::node::retry::RetryState { + crate::node::peering::retry::RetryState { peer_config: crate::config::PeerConfig::new(npub, "udp", "10.0.0.2:2121"), retry_count: 0, retry_after_ms: 0, @@ -1256,7 +1256,7 @@ async fn test_process_pending_retries_drops_expired_entries() { let peer_npub = peer_identity.npub(); let peer_node_addr = *PeerIdentity::from_npub(&peer_npub).unwrap().node_addr(); - let mut state = super::super::retry::RetryState::new(crate::config::PeerConfig::new( + let mut state = super::super::peering::retry::RetryState::new(crate::config::PeerConfig::new( peer_npub, "udp", "127.0.0.1:9", @@ -1409,7 +1409,7 @@ fn test_promote_clears_retry_pending() { // Simulate a retry entry existing for this peer node.retry_pending.insert( node_addr, - super::super::retry::RetryState::new(crate::config::PeerConfig::default()), + super::super::peering::retry::RetryState::new(crate::config::PeerConfig::default()), ); assert_eq!(node.retry_pending.len(), 1); @@ -1720,7 +1720,7 @@ async fn process_pending_retries_gated_at_capacity() { let peer_identity = Identity::generate(); let peer_npub = peer_identity.npub(); let peer_node_addr = *PeerIdentity::from_npub(&peer_npub).unwrap().node_addr(); - let mut state = super::super::retry::RetryState::new(crate::config::PeerConfig::new( + let mut state = super::super::peering::retry::RetryState::new(crate::config::PeerConfig::new( peer_npub, "udp", "127.0.0.1:9",