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).
This commit is contained in:
Johnathan Corgan
2026-07-13 03:30:52 +00:00
parent d61d189572
commit a45eefb58a
5 changed files with 24 additions and 12 deletions
+1 -1
View File
@@ -2242,7 +2242,7 @@ impl Node {
.or_insert_with(|| peer_identity.short_npub()); .or_insert_with(|| peer_identity.short_npub());
self.register_identity(node_addr, peer_identity.pubkey_full()); 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(), npub: npub.clone(),
alias: None, alias: None,
addresses, addresses,
+3 -3
View File
@@ -15,10 +15,10 @@ pub(crate) mod encrypt_worker;
mod handlers; mod handlers;
mod lifecycle; mod lifecycle;
pub(crate) mod metrics; pub(crate) mod metrics;
mod peering;
mod rate_limit; mod rate_limit;
pub(crate) mod reject; pub(crate) mod reject;
mod reloadable; mod reloadable;
mod retry;
pub(crate) mod session; pub(crate) mod session;
pub(crate) mod stats; pub(crate) mod stats;
pub(crate) mod stats_history; pub(crate) mod stats_history;
@@ -450,7 +450,7 @@ pub struct Node {
/// Keyed by NodeAddr. Entries are created when a handshake times out /// Keyed by NodeAddr. Entries are created when a handshake times out
/// or fails, and removed on successful promotion or when max retries /// or fails, and removed on successful promotion or when max retries
/// are exhausted. /// are exhausted.
retry_pending: HashMap<NodeAddr, retry::RetryState>, retry_pending: HashMap<NodeAddr, peering::retry::RetryState>,
// === Periodic Parent Re-evaluation === // === Periodic Parent Re-evaluation ===
/// Timestamp of last periodic parent re-evaluation (for pacing). /// Timestamp of last periodic parent re-evaluation (for pacing).
@@ -2496,7 +2496,7 @@ impl Node {
} }
/// Iterate over retry state for diagnostics. /// Iterate over retry state for diagnostics.
pub fn retry_state_iter(&self) -> impl Iterator<Item = (&NodeAddr, &retry::RetryState)> { pub fn retry_state_iter(&self) -> impl Iterator<Item = (&NodeAddr, &peering::retry::RetryState)> {
self.retry_pending.iter() self.retry_pending.iter()
} }
+12
View File
@@ -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;
@@ -4,10 +4,10 @@
//! automatically retry with exponential backoff. Retry state lives on Node //! automatically retry with exponential backoff. Retry state lives on Node
//! (not PeerConnection) because each retry creates a fresh connection. //! (not PeerConnection) because each retry creates a fresh connection.
use super::{Node, NodeError};
use crate::PeerIdentity; use crate::PeerIdentity;
use crate::config::PeerConfig; use crate::config::PeerConfig;
use crate::identity::NodeAddr; use crate::identity::NodeAddr;
use crate::node::{Node, NodeError};
use crate::proto::fmp::backoff_ms; use crate::proto::fmp::backoff_ms;
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
@@ -55,7 +55,7 @@ impl Node {
/// have not been exhausted (unless `reconnect` is true, which retries /// have not been exhausted (unless `reconnect` is true, which retries
/// indefinitely). Does nothing if the peer is already connected or has /// indefinitely). Does nothing if the peer is already connected or has
/// a connection in progress. /// 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 retry_cfg = &self.config().node.retry;
let max_retries = retry_cfg.max_retries; let max_retries = retry_cfg.max_retries;
if max_retries == 0 { if max_retries == 0 {
@@ -131,7 +131,7 @@ impl Node {
/// preserved and incremented rather than reset to zero. This ensures /// preserved and incremented rather than reset to zero. This ensures
/// exponential backoff accumulates across repeated link-dead events instead /// exponential backoff accumulates across repeated link-dead events instead
/// of resetting to the base interval on every peer removal. /// 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 // Find peer in auto-connect config
let peer_config = self let peer_config = self
.config() .config()
@@ -196,7 +196,7 @@ impl Node {
/// entry stays in `retry_pending` until the connection succeeds (cleared /// entry stays in `retry_pending` until the connection succeeds (cleared
/// in `promote_connection`) or max retries are exhausted (cleared in /// in `promote_connection`) or max retries are exhausted (cleared in
/// `schedule_retry`). /// `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() { if self.retry_pending.is_empty() {
return; return;
} }
+4 -4
View File
@@ -831,7 +831,7 @@ async fn test_process_pending_retries_is_budgeted_per_tick() {
let node_addr = *peer_identity.node_addr(); let node_addr = *peer_identity.node_addr();
node.retry_pending.insert( node.retry_pending.insert(
node_addr, node_addr,
crate::node::retry::RetryState { crate::node::peering::retry::RetryState {
peer_config: crate::config::PeerConfig::new(npub, "udp", "10.0.0.2:2121"), peer_config: crate::config::PeerConfig::new(npub, "udp", "10.0.0.2:2121"),
retry_count: 0, retry_count: 0,
retry_after_ms: 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_npub = peer_identity.npub();
let peer_node_addr = *PeerIdentity::from_npub(&peer_npub).unwrap().node_addr(); 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, peer_npub,
"udp", "udp",
"127.0.0.1:9", "127.0.0.1:9",
@@ -1409,7 +1409,7 @@ fn test_promote_clears_retry_pending() {
// Simulate a retry entry existing for this peer // Simulate a retry entry existing for this peer
node.retry_pending.insert( node.retry_pending.insert(
node_addr, 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); 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_identity = Identity::generate();
let peer_npub = peer_identity.npub(); let peer_npub = peer_identity.npub();
let peer_node_addr = *PeerIdentity::from_npub(&peer_npub).unwrap().node_addr(); 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, peer_npub,
"udp", "udp",
"127.0.0.1:9", "127.0.0.1:9",