From 3b401a0cbd0264b381d106b18dcdd99950c40252 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Thu, 9 Jul 2026 15:43:31 +0000 Subject: [PATCH] Disambiguate the mesh-lookup "discovery" name across the source tree The identifier "discovery" named three unrelated subsystems; the FMP overlay coordinate-lookup subsystem is now consistently "lookup". This finishes the concept-#1 rename across the shell, config, and metric layers left after the earlier proto-layer rename: - Handler module node::handlers::discovery -> node::handlers::lookup, and reset_discovery_backoff -> reset_lookup_backoff. - The Node lookup-engine field Node.discovery -> Node.lookup, renamed by resolved binding so the metrics().discovery and node.discovery config paths are left untouched. - The lookup metric types DiscoveryMetrics -> LookupMetrics, DiscoveryStatsSnapshot -> LookupStatsSnapshot, and Metrics.discovery -> Metrics.lookup. Two surfaces cross a stability boundary and ship behind a compatibility window, both marked in-code for removal at the v2 cutover: - The control-socket metric family is dual-emitted under both "discovery" (deprecated alias) and "lookup" so existing dashboards keep working. - The node.discovery.* config table is split into node.lookup.* (mesh lookup scalars) and node.rendezvous.* (nostr/lan peer rendezvous). NodeConfig does not deny unknown fields, so a naive rename would make a deployed node.discovery: block deserialize into nothing and silently revert every setting to default. A deprecated all-Option DiscoveryConfigCompat field captures a legacy block and a new post-parse Config::normalize_deprecated_keys pass folds it into the new tables with a one-time deprecation warning. Flip the packaged fips.yaml templates to the new keys, add legacy/new/ scalar compat parse tests, and record the split and deprecations in the CHANGELOG. Behavior-neutral; fmt/clippy clean, lib suite green. --- CHANGELOG.md | 22 +++ packaging/common/fips.yaml | 25 ++- .../openwrt-ipk/files/etc/fips/fips.yaml | 17 +- src/config/mod.rs | 161 ++++++++++++++---- src/config/node.rs | 111 ++++++++---- src/control/queries.rs | 14 +- src/control/snapshots/show_routing.json | 24 +++ src/discovery/lan/mod.rs | 2 +- src/node/handlers/handshake.rs | 10 +- src/node/handlers/{discovery.rs => lookup.rs} | 82 ++++----- src/node/handlers/mmp.rs | 4 +- src/node/handlers/mod.rs | 2 +- src/node/lifecycle.rs | 49 +++--- src/node/metrics.rs | 22 +-- src/node/mod.rs | 22 +-- src/node/stats.rs | 2 +- src/node/tests/discovery.rs | 72 ++++---- src/node/tests/unit.rs | 12 +- src/node/tree.rs | 12 +- src/proto/lookup/core.rs | 2 +- src/proto/lookup/limits.rs | 2 +- src/proto/lookup/mod.rs | 2 +- 22 files changed, 442 insertions(+), 229 deletions(-) rename src/node/handlers/{discovery.rs => lookup.rs} (92%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b70236..4d607c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- The mesh-lookup control-metrics family is now emitted under the key + `lookup` in `fipsctl stats metrics` and `show routing`. The former key + `discovery` is still emitted as a deprecated alias carrying identical + counters; update dashboards and alerts to read `lookup`. +- The overloaded `node.discovery.*` config table was split into + `node.lookup.*` (mesh-lookup scalars: `ttl`, `attempt_timeouts_secs`, + `recent_expiry_secs`, `backoff_base_secs`, `backoff_max_secs`, + `forward_min_interval_secs`) and `node.rendezvous.*` (peer rendezvous: + `nostr.*`, `lan.*`). A deployed `node.discovery:` block still loads and is + folded into the new tables with a one-time deprecation warning; migrate your + `fips.yaml` to the new keys. + +### Deprecated + +- The `discovery` metric-family key (control-socket JSON). It is dual-emitted + alongside the new `lookup` key during a migration window and will be removed. + Migrate dashboards/alerts from `discovery.*` to `lookup.*`. +- The `node.discovery.*` config table. Its keys were split into `node.lookup.*` + (mesh-lookup) and `node.rendezvous.*` (peer rendezvous). A legacy + `node.discovery:` block still applies for now with a deprecation warning and + will be removed; migrate to `node.lookup.*` / `node.rendezvous.*`. + ### Fixed ## [0.4.0] - 2026-06-27 diff --git a/packaging/common/fips.yaml b/packaging/common/fips.yaml index a9b2dc8..d4743e9 100644 --- a/packaging/common/fips.yaml +++ b/packaging/common/fips.yaml @@ -10,12 +10,21 @@ node: # # Or set an explicit key (overrides persistent): # nsec: "nsec1..." - discovery: - # Optional Nostr-mediated overlay endpoint discovery. + # Mesh-lookup protocol (node.lookup.*): the overlay coordinate-lookup engine + # (mesh address -> coordinates). Defaults shown; uncomment to override. + # lookup: + # ttl: 64 + # attempt_timeouts_secs: [1, 2, 4, 8] + # recent_expiry_secs: 10 + # backoff_base_secs: 0 + # backoff_max_secs: 0 + # forward_min_interval_secs: 2 + rendezvous: + # Optional Nostr-mediated overlay endpoint rendezvous. # nostr: # enabled: true # policy: configured_only # disabled | configured_only | open - # open_discovery_max_pending: 64 # caps queued open-discovery retries + # open_discovery_max_pending: 64 # caps queued open-rendezvous retries # app: "fips-overlay-v1" # advertise: true # advert_relays: @@ -34,17 +43,17 @@ node: # - "stun:stun.cloudflare.com:3478" # - "stun:global.stun.twilio.com:3478" # - # Optional mDNS-based LAN discovery for sub-second same-LAN pairing. + # Optional mDNS-based LAN rendezvous for sub-second same-LAN pairing. # Opt-in (default false): default-off avoids a per-LAN identity - # broadcast on nodes that have deliberately disabled other discovery + # broadcast on nodes that have deliberately disabled other rendezvous # channels, and avoids any multicast surprise on upgrade. Requires an # operational UDP transport (the advertised port is the one peers dial). # lan: # enabled: false # # Optional application/network scope carried in the LAN-only TXT # # record. Browsers that set a scope ignore adverts for other scopes. - # # Kept separate from the Nostr discovery `app` tag so relay-visible - # # adverts can stay generic while LAN discovery stays per-private-network. + # # Kept separate from the Nostr rendezvous `app` tag so relay-visible + # # adverts can stay generic while LAN rendezvous stays per-private-network. # # scope: "lab-floor-3" # # Advanced: overrides the mDNS service type. Leave unset in normal # # use — only needed to run multiple isolated services on one @@ -147,5 +156,5 @@ peers: [] # - transport: udp # addr: "test-us01.fips.network:2121" # IP or hostname (e.g., "peer.example.com:2121") # - transport: udp - # addr: "nat" # Use node.discovery.nostr for Nostr/STUN hole punching + # addr: "nat" # Use node.rendezvous.nostr for Nostr/STUN hole punching # connect_policy: auto_connect diff --git a/packaging/openwrt-ipk/files/etc/fips/fips.yaml b/packaging/openwrt-ipk/files/etc/fips/fips.yaml index 4a6cb6e..363f241 100644 --- a/packaging/openwrt-ipk/files/etc/fips/fips.yaml +++ b/packaging/openwrt-ipk/files/etc/fips/fips.yaml @@ -10,12 +10,21 @@ node: # # Or set an explicit key (overrides persistent): # nsec: "nsec1..." - discovery: - # Optional Nostr-mediated overlay endpoint discovery. + # Mesh-lookup protocol (node.lookup.*): the overlay coordinate-lookup engine + # (mesh address -> coordinates). Defaults shown; uncomment to override. + # lookup: + # ttl: 64 + # attempt_timeouts_secs: [1, 2, 4, 8] + # recent_expiry_secs: 10 + # backoff_base_secs: 0 + # backoff_max_secs: 0 + # forward_min_interval_secs: 2 + rendezvous: + # Optional Nostr-mediated overlay endpoint rendezvous. # nostr: # enabled: true # policy: configured_only # disabled | configured_only | open - # open_discovery_max_pending: 64 # caps queued open-discovery retries + # open_discovery_max_pending: 64 # caps queued open-rendezvous retries # app: "fips-overlay-v1" # advertise: true # advert_relays: @@ -121,5 +130,5 @@ peers: [] # - transport: udp # addr: "test-us01.fips.network:2121" # IP or hostname (e.g., "peer.example.com:2121") # - transport: udp - # addr: "nat" # Use node.discovery.nostr for Nostr/STUN hole punching + # addr: "nat" # Use node.rendezvous.nostr for Nostr/STUN hole punching # connect_policy: auto_connect diff --git a/src/config/mod.rs b/src/config/mod.rs index 211254f..e4343f5 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -33,9 +33,9 @@ use thiserror::Error; #[cfg(target_os = "linux")] pub use gateway::{ConntrackConfig, GatewayConfig, GatewayDnsConfig, PortForward, Proto}; pub use node::{ - BloomConfig, BuffersConfig, CacheConfig, ControlConfig, DiscoveryConfig, LimitsConfig, - MmpConfig, NodeConfig, NostrDiscoveryConfig, NostrDiscoveryPolicy, RateLimitConfig, - RekeyConfig, RetryConfig, SessionConfig, SessionMmpConfig, TreeConfig, + BloomConfig, BuffersConfig, CacheConfig, ControlConfig, LimitsConfig, LookupConfig, MmpConfig, + NodeConfig, NostrDiscoveryConfig, NostrDiscoveryPolicy, RateLimitConfig, RekeyConfig, + RendezvousConfig, RetryConfig, SessionConfig, SessionMmpConfig, TreeConfig, }; pub use peer::{ConnectPolicy, PeerAddress, PeerConfig}; pub use transport::{ @@ -489,10 +489,59 @@ impl Config { source: e, })?; - serde_yaml::from_str(&contents).map_err(|e| ConfigError::ParseYaml { - path: path.to_path_buf(), - source: e, - }) + let mut config: Config = + serde_yaml::from_str(&contents).map_err(|e| ConfigError::ParseYaml { + path: path.to_path_buf(), + source: e, + })?; + config.normalize_deprecated_keys(); + Ok(config) + } + + /// COMPAT (drop at the v2 cutover): fold a deprecated `node.discovery:` + /// block into the `node.lookup.*` (mesh-lookup scalars) and + /// `node.rendezvous.*` (nostr/LAN peer rendezvous) tables that replaced it. + /// + /// Runs at every deserialize boundary (see `load_file`). A present legacy + /// field fills the corresponding new-table field, so a config that predates + /// the split keeps behaving identically. When a legacy block is seen, a + /// one-time deprecation warning names the old→new key moves. Exposed to the + /// crate so config tests that deserialize directly can invoke it. + pub(crate) fn normalize_deprecated_keys(&mut self) { + let Some(compat) = self.node.discovery.take() else { + return; + }; + tracing::warn!( + target: "fips::config", + "`node.discovery.*` is deprecated and will be removed: mesh-lookup \ + scalars moved to `node.lookup.*`, and peer-rendezvous keys moved to \ + `node.rendezvous.nostr.*` / `node.rendezvous.lan.*`. Please migrate; \ + a legacy `node.discovery` block still applies for now." + ); + if let Some(v) = compat.ttl { + self.node.lookup.ttl = v; + } + if let Some(v) = compat.attempt_timeouts_secs { + self.node.lookup.attempt_timeouts_secs = v; + } + if let Some(v) = compat.recent_expiry_secs { + self.node.lookup.recent_expiry_secs = v; + } + if let Some(v) = compat.backoff_base_secs { + self.node.lookup.backoff_base_secs = v; + } + if let Some(v) = compat.backoff_max_secs { + self.node.lookup.backoff_max_secs = v; + } + if let Some(v) = compat.forward_min_interval_secs { + self.node.lookup.forward_min_interval_secs = v; + } + if let Some(v) = compat.nostr { + self.node.rendezvous.nostr = v; + } + if let Some(v) = compat.lan { + self.node.rendezvous.lan = v; + } } /// Get the standard search paths in priority order (lowest to highest). @@ -600,7 +649,7 @@ impl Config { /// Validate cross-field configuration invariants. pub fn validate(&self) -> Result<(), ConfigError> { - let nostr = &self.node.discovery.nostr; + let nostr = &self.node.rendezvous.nostr; let any_transport_advertises_on_nostr = self .transports @@ -620,13 +669,13 @@ impl Config { if any_transport_advertises_on_nostr && !nostr.enabled { return Err(ConfigError::Validation( - "at least one transport has `advertise_on_nostr = true`, but `node.discovery.nostr.enabled` is false".to_string(), + "at least one transport has `advertise_on_nostr = true`, but `node.rendezvous.nostr.enabled` is false".to_string(), )); } if self.peers.iter().any(|peer| peer.via_nostr) && !nostr.enabled { return Err(ConfigError::Validation( - "at least one peer has `via_nostr = true`, but `node.discovery.nostr.enabled` is false".to_string(), + "at least one peer has `via_nostr = true`, but `node.rendezvous.nostr.enabled` is false".to_string(), )); } @@ -648,12 +697,12 @@ impl Config { if nostr.enabled && has_nat_udp_advert { if nostr.dm_relays.is_empty() { return Err(ConfigError::Validation( - "NAT UDP advert publishing requires `node.discovery.nostr.dm_relays` to be non-empty".to_string(), + "NAT UDP advert publishing requires `node.rendezvous.nostr.dm_relays` to be non-empty".to_string(), )); } if nostr.stun_servers.is_empty() { return Err(ConfigError::Validation( - "NAT UDP advert publishing requires `node.discovery.nostr.stun_servers` to be non-empty".to_string(), + "NAT UDP advert publishing requires `node.rendezvous.nostr.stun_servers` to be non-empty".to_string(), )); } } @@ -1261,7 +1310,9 @@ peers: } #[test] - fn test_parse_nostr_discovery_config() { + fn test_parse_legacy_discovery_nostr_config_compat() { + // COMPAT (drop at the v2 cutover): a deprecated `node.discovery.nostr` + // block must fold into `node.rendezvous.nostr` via normalize. let yaml = r#" node: discovery: @@ -1285,26 +1336,27 @@ peers: - transport: udp addr: "nat" "#; - let config: Config = serde_yaml::from_str(yaml).unwrap(); - assert!(config.node.discovery.nostr.enabled); - assert!(!config.node.discovery.nostr.advertise); - assert_eq!(config.node.discovery.nostr.app, "fips.nat.test.v1"); - assert_eq!(config.node.discovery.nostr.signal_ttl_secs, 45); + let mut config: Config = serde_yaml::from_str(yaml).unwrap(); + config.normalize_deprecated_keys(); + assert!(config.node.rendezvous.nostr.enabled); + assert!(!config.node.rendezvous.nostr.advertise); + assert_eq!(config.node.rendezvous.nostr.app, "fips.nat.test.v1"); + assert_eq!(config.node.rendezvous.nostr.signal_ttl_secs, 45); assert_eq!( - config.node.discovery.nostr.policy, + config.node.rendezvous.nostr.policy, NostrDiscoveryPolicy::ConfiguredOnly ); - assert_eq!(config.node.discovery.nostr.open_discovery_max_pending, 12); + assert_eq!(config.node.rendezvous.nostr.open_discovery_max_pending, 12); assert_eq!( - config.node.discovery.nostr.advert_relays, + config.node.rendezvous.nostr.advert_relays, vec!["wss://relay-a.example".to_string()] ); assert_eq!( - config.node.discovery.nostr.dm_relays, + config.node.rendezvous.nostr.dm_relays, vec!["wss://relay-b.example".to_string()] ); assert_eq!( - config.node.discovery.nostr.stun_servers, + config.node.rendezvous.nostr.stun_servers, vec!["stun:stun.example.org:3478".to_string()] ); assert_eq!( @@ -1314,6 +1366,55 @@ peers: assert!(config.peers[0].via_nostr); } + #[test] + fn test_parse_lookup_and_rendezvous_new_keys() { + // The post-split keys parse directly, with no deprecated block and no + // normalize warning. + let yaml = r#" +node: + lookup: + ttl: 7 + attempt_timeouts_secs: [3, 6] + forward_min_interval_secs: 9 + rendezvous: + nostr: + enabled: true + app: "fips.new.keys.v1" +"#; + let mut config: Config = serde_yaml::from_str(yaml).unwrap(); + config.normalize_deprecated_keys(); + assert_eq!(config.node.lookup.ttl, 7); + assert_eq!(config.node.lookup.attempt_timeouts_secs, vec![3, 6]); + assert_eq!(config.node.lookup.forward_min_interval_secs, 9); + // Unset scalar keeps its default. + assert_eq!(config.node.lookup.recent_expiry_secs, 10); + assert!(config.node.rendezvous.nostr.enabled); + assert_eq!(config.node.rendezvous.nostr.app, "fips.new.keys.v1"); + assert!(config.node.discovery.is_none()); + } + + #[test] + fn test_legacy_discovery_lookup_scalars_compat() { + // COMPAT (drop at the v2 cutover): legacy `node.discovery` mesh-lookup + // scalars must fold into `node.lookup`; unset keys keep their defaults. + let yaml = r#" +node: + discovery: + ttl: 5 + backoff_base_secs: 4 + backoff_max_secs: 30 +"#; + let mut config: Config = serde_yaml::from_str(yaml).unwrap(); + config.normalize_deprecated_keys(); + assert_eq!(config.node.lookup.ttl, 5); + assert_eq!(config.node.lookup.backoff_base_secs, 4); + assert_eq!(config.node.lookup.backoff_max_secs, 30); + // Unset legacy scalar leaves the new-table default intact. + assert_eq!(config.node.lookup.attempt_timeouts_secs, vec![1, 2, 4, 8]); + // The compat block is consumed by normalize. + assert!(config.node.discovery.is_none()); + } + #[test] fn test_validate_transport_advert_requires_nostr_enabled() { let mut config = Config::default(); @@ -1321,7 +1422,7 @@ peers: advertise_on_nostr: Some(true), ..Default::default() }); - config.node.discovery.nostr.enabled = false; + config.node.rendezvous.nostr.enabled = false; let err = config.validate().expect_err("validation should fail"); assert!(err.to_string().contains("advertise_on_nostr")); @@ -1337,7 +1438,7 @@ peers: }], ..Default::default() }; - config.node.discovery.nostr.enabled = false; + config.node.rendezvous.nostr.enabled = false; let err = config.validate().expect_err("validation should fail"); assert!(err.to_string().contains("via_nostr")); @@ -1358,7 +1459,7 @@ peers: // Empty addresses + via_nostr=true + nostr.enabled=true → ok. config.peers[0].via_nostr = true; - config.node.discovery.nostr.enabled = true; + config.node.rendezvous.nostr.enabled = true; config .validate() .expect("via_nostr should allow empty addresses"); @@ -1367,8 +1468,8 @@ peers: #[test] fn test_validate_nat_udp_advert_requires_relays_and_stun() { let mut config = Config::default(); - config.node.discovery.nostr.enabled = true; - config.node.discovery.nostr.dm_relays.clear(); + config.node.rendezvous.nostr.enabled = true; + config.node.rendezvous.nostr.dm_relays.clear(); config.transports.udp = TransportInstances::Single(UdpConfig { advertise_on_nostr: Some(true), public: Some(false), @@ -1378,8 +1479,8 @@ peers: let err = config.validate().expect_err("validation should fail"); assert!(err.to_string().contains("dm_relays")); - config.node.discovery.nostr.dm_relays = vec!["wss://relay.example".to_string()]; - config.node.discovery.nostr.stun_servers.clear(); + config.node.rendezvous.nostr.dm_relays = vec!["wss://relay.example".to_string()]; + config.node.rendezvous.nostr.stun_servers.clear(); let err = config.validate().expect_err("validation should fail"); assert!(err.to_string().contains("stun_servers")); } diff --git a/src/config/node.rs b/src/config/node.rs index 613ff7d..9e54bfe 100644 --- a/src/config/node.rs +++ b/src/config/node.rs @@ -186,48 +186,42 @@ impl CacheConfig { } } -/// Discovery protocol (`node.discovery.*`). +/// Mesh-lookup protocol (`node.lookup.*`): the overlay coordinate-lookup +/// engine (address → coordinates). The peer-rendezvous keys that used to +/// share this table (`nostr`/`lan`) now live under [`RendezvousConfig`] +/// (`node.rendezvous.*`). #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DiscoveryConfig { - /// Hop limit for LookupRequest flood (`node.discovery.ttl`). - #[serde(default = "DiscoveryConfig::default_ttl")] +pub struct LookupConfig { + /// Hop limit for LookupRequest flood (`node.lookup.ttl`). + #[serde(default = "LookupConfig::default_ttl")] pub ttl: u8, - /// Per-attempt timeouts in seconds (`node.discovery.attempt_timeouts_secs`). + /// Per-attempt timeouts in seconds (`node.lookup.attempt_timeouts_secs`). /// Each entry is the time to wait for a response before sending the next /// LookupRequest (with a fresh request_id). Sequence length determines the /// total number of attempts before declaring the destination unreachable. /// Default `[1, 2, 4, 8]` gives 4 attempts and a 15s total budget. - #[serde(default = "DiscoveryConfig::default_attempt_timeouts_secs")] + #[serde(default = "LookupConfig::default_attempt_timeouts_secs")] pub attempt_timeouts_secs: Vec, - /// Dedup cache expiry in seconds (`node.discovery.recent_expiry_secs`). - #[serde(default = "DiscoveryConfig::default_recent_expiry_secs")] + /// Dedup cache expiry in seconds (`node.lookup.recent_expiry_secs`). + #[serde(default = "LookupConfig::default_recent_expiry_secs")] pub recent_expiry_secs: u64, - /// Base backoff after lookup failure in seconds (`node.discovery.backoff_base_secs`). + /// Base backoff after lookup failure in seconds (`node.lookup.backoff_base_secs`). /// Doubles per consecutive failure up to `backoff_max_secs`. Defaults to 0 /// (no post-failure suppression); the per-attempt sequence in /// `attempt_timeouts_secs` provides the only retry pacing. - #[serde(default = "DiscoveryConfig::default_backoff_base_secs")] + #[serde(default = "LookupConfig::default_backoff_base_secs")] pub backoff_base_secs: u64, - /// Maximum backoff cap in seconds (`node.discovery.backoff_max_secs`). - #[serde(default = "DiscoveryConfig::default_backoff_max_secs")] + /// Maximum backoff cap in seconds (`node.lookup.backoff_max_secs`). + #[serde(default = "LookupConfig::default_backoff_max_secs")] pub backoff_max_secs: u64, /// Minimum interval between forwarded lookups for the same target in seconds - /// (`node.discovery.forward_min_interval_secs`). + /// (`node.lookup.forward_min_interval_secs`). /// Defense-in-depth against misbehaving nodes. - #[serde(default = "DiscoveryConfig::default_forward_min_interval_secs")] + #[serde(default = "LookupConfig::default_forward_min_interval_secs")] pub forward_min_interval_secs: u64, - /// Nostr-mediated overlay endpoint discovery. - #[serde(default = "DiscoveryConfig::default_nostr")] - pub nostr: NostrDiscoveryConfig, - /// mDNS / DNS-SD peer discovery on the local link. Identity surface - /// is a strict subset of what `nostr.advertise` already publishes - /// publicly, so there's no marginal privacy cost; the latency win - /// for same-LAN peers is large (sub-second pairing, no relay). - #[serde(default = "DiscoveryConfig::default_lan")] - pub lan: crate::discovery::lan::LanDiscoveryConfig, } -impl Default for DiscoveryConfig { +impl Default for LookupConfig { fn default() -> Self { Self { ttl: 64, @@ -236,13 +230,11 @@ impl Default for DiscoveryConfig { backoff_base_secs: 0, backoff_max_secs: 0, forward_min_interval_secs: 2, - nostr: NostrDiscoveryConfig::default(), - lan: crate::discovery::lan::LanDiscoveryConfig::default(), } } } -impl DiscoveryConfig { +impl LookupConfig { fn default_ttl() -> u8 { 64 } @@ -261,12 +253,45 @@ impl DiscoveryConfig { fn default_forward_min_interval_secs() -> u64 { 2 } - fn default_nostr() -> NostrDiscoveryConfig { - NostrDiscoveryConfig::default() - } - fn default_lan() -> crate::discovery::lan::LanDiscoveryConfig { - crate::discovery::lan::LanDiscoveryConfig::default() - } +} + +/// Peer rendezvous (`node.rendezvous.*`): how the node finds peers to connect +/// to at all — Nostr-mediated overlay endpoints and mDNS/DNS-SD on the local +/// link. Distinct from mesh lookup ([`LookupConfig`]), which finds coordinates +/// for an already-known mesh address. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct RendezvousConfig { + /// Nostr-mediated overlay endpoint rendezvous (`node.rendezvous.nostr.*`). + #[serde(default)] + pub nostr: NostrDiscoveryConfig, + /// mDNS / DNS-SD peer rendezvous on the local link (`node.rendezvous.lan.*`). + /// Identity surface is a strict subset of what `nostr.advertise` already + /// publishes publicly, so there's no marginal privacy cost; the latency + /// win for same-LAN peers is large (sub-second pairing, no relay). + #[serde(default)] + pub lan: crate::discovery::lan::LanDiscoveryConfig, +} + +/// COMPAT (drop at the v2 cutover): a deprecated legacy `node.discovery:` block. +/// +/// The `node.discovery.*` table was split into `node.lookup.*` (mesh-lookup +/// scalars) and `node.rendezvous.*` (nostr/LAN peer rendezvous). Because +/// `NodeConfig` does not deny unknown fields, a still-deployed `node.discovery:` +/// block would otherwise deserialize into nothing and silently revert every +/// lookup/rendezvous setting to its default. This all-`Option` mirror captures +/// it so [`Config::normalize_deprecated_keys`] can fold it into the new tables +/// with a one-time deprecation warning; unset legacy keys stay `None` and leave +/// the new-table defaults intact. +#[derive(Debug, Clone, Deserialize)] +pub(crate) struct DiscoveryConfigCompat { + pub ttl: Option, + pub attempt_timeouts_secs: Option>, + pub recent_expiry_secs: Option, + pub backoff_base_secs: Option, + pub backoff_max_secs: Option, + pub forward_min_interval_secs: Option, + pub nostr: Option, + pub lan: Option, } /// Nostr advert discovery policy. @@ -285,7 +310,7 @@ pub enum NostrDiscoveryPolicy { Open, } -/// Nostr-mediated overlay endpoint discovery (`node.discovery.nostr.*`). +/// Nostr-mediated overlay endpoint discovery (`node.rendezvous.nostr.*`). #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct NostrDiscoveryConfig { @@ -1021,9 +1046,19 @@ pub struct NodeConfig { #[serde(default)] pub cache: CacheConfig, - /// Discovery protocol (`node.discovery.*`). + /// Mesh-lookup protocol (`node.lookup.*`). #[serde(default)] - pub discovery: DiscoveryConfig, + pub lookup: LookupConfig, + + /// Peer rendezvous (`node.rendezvous.*`). + #[serde(default)] + pub rendezvous: RendezvousConfig, + + /// COMPAT (drop at the v2 cutover): a deprecated legacy `node.discovery:` + /// block, folded into `lookup`/`rendezvous` by + /// [`Config::normalize_deprecated_keys`]. Never re-serialized. + #[serde(default, skip_serializing)] + pub(crate) discovery: Option, /// Spanning tree (`node.tree.*`). #[serde(default)] @@ -1080,7 +1115,9 @@ impl Default for NodeConfig { rate_limit: RateLimitConfig::default(), retry: RetryConfig::default(), cache: CacheConfig::default(), - discovery: DiscoveryConfig::default(), + lookup: LookupConfig::default(), + rendezvous: RendezvousConfig::default(), + discovery: None, tree: TreeConfig::default(), bloom: BloomConfig::default(), session: SessionConfig::default(), diff --git a/src/control/queries.rs b/src/control/queries.rs index 0f9d0fb..435b752 100644 --- a/src/control/queries.rs +++ b/src/control/queries.rs @@ -1487,7 +1487,9 @@ pub fn show_routing(node: &Node) -> Value { "recent_requests": node.recent_request_count(), "retries": retries, "forwarding": serde_json::to_value(metrics.forwarding.snapshot()).unwrap_or_default(), - "discovery": serde_json::to_value(metrics.discovery.snapshot()).unwrap_or_default(), + // COMPAT: `discovery` is the deprecated alias for `lookup`; drop at the v2 cutover. + "discovery": serde_json::to_value(metrics.lookup.snapshot()).unwrap_or_default(), + "lookup": serde_json::to_value(metrics.lookup.snapshot()).unwrap_or_default(), "error_signals": serde_json::to_value(metrics.errors.snapshot()).unwrap_or_default(), "congestion": serde_json::to_value(metrics.congestion.snapshot()).unwrap_or_default(), }) @@ -1543,7 +1545,9 @@ pub(crate) fn show_routing_from_handle(handle: &super::read_handle::ControlReadH "recent_requests": view.recent_requests, "retries": retries, "forwarding": serde_json::to_value(metrics.forwarding.snapshot()).unwrap_or_default(), - "discovery": serde_json::to_value(metrics.discovery.snapshot()).unwrap_or_default(), + // COMPAT: `discovery` is the deprecated alias for `lookup`; drop at the v2 cutover. + "discovery": serde_json::to_value(metrics.lookup.snapshot()).unwrap_or_default(), + "lookup": serde_json::to_value(metrics.lookup.snapshot()).unwrap_or_default(), "error_signals": serde_json::to_value(metrics.errors.snapshot()).unwrap_or_default(), "congestion": serde_json::to_value(metrics.congestion.snapshot()).unwrap_or_default(), }) @@ -2328,7 +2332,9 @@ pub(crate) fn show_metrics_from_handle(handle: &super::read_handle::ControlReadH let m = handle.metrics(); json!({ "forwarding": m.forwarding.snapshot(), - "discovery": m.discovery.snapshot(), + // COMPAT: `discovery` is the deprecated alias for `lookup`; drop at the v2 cutover. + "discovery": m.lookup.snapshot(), + "lookup": m.lookup.snapshot(), "tree": m.tree.snapshot(), "bloom": m.bloom.snapshot(), "congestion": m.congestion.snapshot(), @@ -2794,7 +2800,9 @@ mod tests { let expected_families = [ ("forwarding", "received_packets"), + // `discovery` is the deprecated dual-emit alias for `lookup`; drop at the v2 cutover. ("discovery", "req_received"), + ("lookup", "req_received"), ("tree", "accepted"), ("bloom", "accepted"), ("congestion", "ce_forwarded"), diff --git a/src/control/snapshots/show_routing.json b/src/control/snapshots/show_routing.json index 52c3f5a..6f3a888 100644 --- a/src/control/snapshots/show_routing.json +++ b/src/control/snapshots/show_routing.json @@ -63,6 +63,30 @@ "ttl_exhausted_packets": 0 }, "identity_cache_entries": 0, + "lookup": { + "req_backoff_suppressed": 0, + "req_bloom_miss": 0, + "req_decode_error": 0, + "req_dedup_cache_full": 0, + "req_deduplicated": 0, + "req_duplicate": 0, + "req_fallback_forwarded": 0, + "req_forward_rate_limited": 0, + "req_forwarded": 0, + "req_initiated": 0, + "req_no_tree_peer": 0, + "req_received": 0, + "req_target_is_us": 0, + "req_ttl_exhausted": 0, + "resp_accepted": 0, + "resp_decode_error": 0, + "resp_forwarded": 0, + "resp_identity_miss": 0, + "resp_no_route": 0, + "resp_proof_failed": 0, + "resp_received": 0, + "resp_timed_out": 0 + }, "pending_lookups": [], "pending_tun_destinations": 0, "pending_tun_packets": 0, diff --git a/src/discovery/lan/mod.rs b/src/discovery/lan/mod.rs index f5e3b2d..e82b2a7 100644 --- a/src/discovery/lan/mod.rs +++ b/src/discovery/lan/mod.rs @@ -90,7 +90,7 @@ pub enum LanEvent { pub struct LanDiscoveryConfig { /// Master switch. Default: `false` — LAN discovery is opt-in. Operators /// who want sub-second same-LAN pairing enable it via - /// `node.discovery.lan.enabled: true`. Default-off avoids reintroducing + /// `node.rendezvous.lan.enabled: true`. Default-off avoids reintroducing /// a per-LAN identity broadcast on nodes that have deliberately disabled /// other discovery channels, and avoids any multicast surprise on upgrade. #[serde(default = "LanDiscoveryConfig::default_enabled")] diff --git a/src/node/handlers/handshake.rs b/src/node/handlers/handshake.rs index 7efc80d..8f231b5 100644 --- a/src/node/handlers/handshake.rs +++ b/src/node/handlers/handshake.rs @@ -526,7 +526,7 @@ impl Node { } // Schedule filter announce (sent on next tick via debounce) self.bloom_state.mark_update_needed(node_addr); - self.reset_discovery_backoff(); + self.reset_lookup_backoff(); } PromotionResult::CrossConnectionWon { loser_link_id, @@ -557,7 +557,7 @@ impl Node { } // Schedule filter announce (sent on next tick via debounce) self.bloom_state.mark_update_needed(node_addr); - self.reset_discovery_backoff(); + self.reset_lookup_backoff(); } PromotionResult::CrossConnectionLost { winner_link_id } => { // Close the losing TCP connection (no-op for connectionless) @@ -928,7 +928,7 @@ impl Node { } // Schedule filter announce (sent on next tick via debounce) self.bloom_state.mark_update_needed(peer_node_addr); - self.reset_discovery_backoff(); + self.reset_lookup_backoff(); return; } @@ -951,7 +951,7 @@ impl Node { } // Schedule filter announce (sent on next tick via debounce) self.bloom_state.mark_update_needed(node_addr); - self.reset_discovery_backoff(); + self.reset_lookup_backoff(); } PromotionResult::CrossConnectionWon { loser_link_id, @@ -981,7 +981,7 @@ impl Node { } // Schedule filter announce (sent on next tick via debounce) self.bloom_state.mark_update_needed(node_addr); - self.reset_discovery_backoff(); + self.reset_lookup_backoff(); } PromotionResult::CrossConnectionLost { winner_link_id } => { // Close the losing TCP connection (no-op for connectionless) diff --git a/src/node/handlers/discovery.rs b/src/node/handlers/lookup.rs similarity index 92% rename from src/node/handlers/discovery.rs rename to src/node/handlers/lookup.rs index 8d3c152..64f1dad 100644 --- a/src/node/handlers/discovery.rs +++ b/src/node/handlers/lookup.rs @@ -1,6 +1,6 @@ -//! LookupRequest/LookupResponse discovery protocol handlers. +//! LookupRequest/LookupResponse mesh lookup protocol handlers. //! -//! Handles coordinate discovery via bloom-filter-guided tree routing. +//! Handles coordinate lookup via bloom-filter-guided tree routing. //! Requests are forwarded only to tree peers (parent + children) whose //! bloom filter contains the target. TTL and request_id dedup provide //! safety bounds. @@ -19,7 +19,7 @@ use tracing::{debug, info, trace, warn}; /// private `peers` map and call the crate-private tree/bloom predicates. /// /// Holding `&Node` whole is fine for the forward path because it does not -/// also need `&mut self.discovery` concurrently. A later commit whose core +/// also need `&mut self.lookup` concurrently. A later commit whose core /// step needs `&mut discovery` while reading routing state should narrow this /// to borrow only `peers` + `tree_state` instead of the whole node. struct NodeRoutingView<'a> { @@ -51,13 +51,13 @@ impl Node { /// 5. If we're the target, generate and send response /// 6. If TTL > 0, forward to tree peers whose bloom filter matches pub(in crate::node) async fn handle_lookup_request(&mut self, from: &NodeAddr, payload: &[u8]) { - self.metrics().discovery.req_received.inc(); + self.metrics().lookup.req_received.inc(); let request = match LookupRequest::decode(payload) { Ok(req) => req, Err(e) => { self.metrics() - .discovery + .lookup .record_reject(DiscoveryReject::ReqDecodeError); debug!(from = %self.peer_display_name(from), error = %e, "Malformed LookupRequest"); return; @@ -65,11 +65,11 @@ impl Node { }; let now_ms = Self::now_ms(); - let recent_expiry_ms = self.config().node.discovery.recent_expiry_secs * 1000; + let recent_expiry_ms = self.config().node.lookup.recent_expiry_secs * 1000; let my_addr = *self.node_addr(); use crate::proto::lookup::RequestOutcome; match crate::proto::lookup::classify_request( - &mut self.discovery, + &mut self.lookup, &request, from, &my_addr, @@ -79,7 +79,7 @@ impl Node { ) { RequestOutcome::Duplicate => { self.metrics() - .discovery + .lookup .record_reject(DiscoveryReject::ReqDuplicate); debug!( request_id = request.request_id, @@ -89,7 +89,7 @@ impl Node { } RequestOutcome::DedupCacheFull { len } => { self.metrics() - .discovery + .lookup .record_reject(DiscoveryReject::ReqDedupCacheFull); debug!( request_id = request.request_id, @@ -100,7 +100,7 @@ impl Node { ); } RequestOutcome::RespondAsTarget => { - self.metrics().discovery.req_target_is_us.inc(); + self.metrics().lookup.req_target_is_us.inc(); debug!( request_id = request.request_id, origin = %self.peer_display_name(&request.origin), @@ -109,11 +109,11 @@ impl Node { self.send_lookup_response(&request).await; } RequestOutcome::Forward => { - self.metrics().discovery.req_forwarded.inc(); + self.metrics().lookup.req_forwarded.inc(); self.forward_lookup_request(request).await; } RequestOutcome::ForwardRateLimited => { - self.metrics().discovery.req_forward_rate_limited.inc(); + self.metrics().lookup.req_forward_rate_limited.inc(); debug!( request_id = request.request_id, target = %self.peer_display_name(&request.target), @@ -122,7 +122,7 @@ impl Node { } RequestOutcome::TtlExhausted => { self.metrics() - .discovery + .lookup .record_reject(DiscoveryReject::ReqTtlExhausted); debug!( request_id = request.request_id, @@ -145,13 +145,13 @@ impl Node { from: &NodeAddr, payload: &[u8], ) { - self.metrics().discovery.resp_received.inc(); + self.metrics().lookup.resp_received.inc(); let mut response = match LookupResponse::decode(payload) { Ok(resp) => resp, Err(e) => { self.metrics() - .discovery + .lookup .record_reject(DiscoveryReject::RespDecodeError); debug!(from = %self.peer_display_name(from), error = %e, "Malformed LookupResponse"); return; @@ -161,7 +161,7 @@ impl Node { let now_ms = Self::now_ms(); // Check if we forwarded this request (transit node) or originated it - match crate::proto::lookup::classify_response(&mut self.discovery, response.request_id) { + match crate::proto::lookup::classify_response(&mut self.lookup, response.request_id) { crate::proto::lookup::ResponseRoute::AlreadyForwarded => { // Already forwarded a response for this request — drop to // prevent response routing loops. @@ -173,7 +173,7 @@ impl Node { } crate::proto::lookup::ResponseRoute::Transit { from_peer } => { // Transit node: reverse-path forward - self.metrics().discovery.resp_forwarded.inc(); + self.metrics().lookup.resp_forwarded.inc(); // Apply path_mtu min() from the outgoing link's transport MTU self.apply_outgoing_link_mtu_to_response(&mut response, &from_peer); @@ -207,7 +207,7 @@ impl Node { Some((_addr, pubkey)) => pubkey, None => { self.metrics() - .discovery + .lookup .record_reject(DiscoveryReject::RespIdentityMiss); warn!( request_id = response.request_id, @@ -228,7 +228,7 @@ impl Node { ); if !peer_id.verify(&proof_data, &response.proof) { self.metrics() - .discovery + .lookup .record_reject(DiscoveryReject::RespProofFailed); warn!( request_id = response.request_id, @@ -238,7 +238,7 @@ impl Node { return; } - self.metrics().discovery.resp_accepted.inc(); + self.metrics().lookup.resp_accepted.inc(); info!( request_id = response.request_id, @@ -252,7 +252,7 @@ impl Node { // state (backoff + pending lookup) and returns the // cross-subsystem effects for us to drive. let actions = crate::proto::lookup::on_response_accepted( - &mut self.discovery, + &mut self.lookup, &target, response.target_coords, now_ms, @@ -362,7 +362,7 @@ impl Node { // greedy tree-route fallback is a &mut coord-cache op kept in the shell. use crate::proto::lookup::ResponseRouteDecision; let next_hop_addr = match crate::proto::lookup::plan_response_route( - &self.discovery, + &self.lookup, request.request_id, ) { ResponseRouteDecision::ReversePath(peer) => peer, @@ -374,7 +374,7 @@ impl Node { "Cannot route LookupResponse: no reverse path or tree route to origin" ); self.metrics() - .discovery + .lookup .record_reject(DiscoveryReject::RespNoRoute); return; } @@ -428,7 +428,7 @@ impl Node { match outcome { crate::proto::lookup::ForwardOutcome::TtlExhausted => {} crate::proto::lookup::ForwardOutcome::NoPeers => { - self.metrics().discovery.req_no_tree_peer.inc(); + self.metrics().lookup.req_no_tree_peer.inc(); trace!( request_id = request.request_id, "No eligible peers to forward LookupRequest" @@ -440,7 +440,7 @@ impl Node { } => { let peer_count = actions.len(); if used_fallback { - self.metrics().discovery.req_fallback_forwarded.inc(); + self.metrics().lookup.req_fallback_forwarded.inc(); debug!( request_id = request.request_id, target = %self.peer_display_name(&request.target), @@ -479,7 +479,7 @@ impl Node { /// The originator does NOT record the request_id in recent_requests, /// so when the response arrives, it's recognized as "our request". pub(in crate::node) async fn initiate_lookup(&mut self, target: &NodeAddr, ttl: u8) -> usize { - self.metrics().discovery.req_initiated.inc(); + self.metrics().lookup.req_initiated.inc(); let origin = *self.node_addr(); let origin_coords = self.tree_state().my_coords().clone(); @@ -529,27 +529,27 @@ impl Node { /// filter pre-check. If all pass, sends the first attempt's LookupRequest. /// Subsequent attempts (with fresh request_ids) are scheduled by /// [`Self::check_pending_lookups`] when each attempt's per-attempt timeout - /// expires, using the sequence in `node.discovery.attempt_timeouts_secs`. + /// expires, using the sequence in `node.lookup.attempt_timeouts_secs`. pub(in crate::node) async fn maybe_initiate_lookup(&mut self, dest: &NodeAddr) { let now_ms = Self::now_ms(); // Bloom filter pre-check (view read) BEFORE the core call: if no peer's // filter contains the target, it's not in the mesh. Reading `self.peers` - // here keeps the `&mut self.discovery` borrow in `initiate_gate` from + // here keeps the `&mut self.lookup` borrow in `initiate_gate` from // overlapping the immutable peer-table read. let reachable = self.peers.values().any(|peer| peer.may_reach(dest)); use crate::proto::lookup::InitiateDecision; - match crate::proto::lookup::initiate_gate(&mut self.discovery, dest, now_ms, reachable) { + match crate::proto::lookup::initiate_gate(&mut self.lookup, dest, now_ms, reachable) { InitiateDecision::Deduplicated => { - self.metrics().discovery.req_deduplicated.inc(); + self.metrics().lookup.req_deduplicated.inc(); debug!( target_node = %self.peer_display_name(dest), "Discovery lookup deduplicated, already pending" ); } InitiateDecision::Suppressed { failures } => { - self.metrics().discovery.req_backoff_suppressed.inc(); + self.metrics().lookup.req_backoff_suppressed.inc(); debug!( target_node = %self.peer_display_name(dest), failures = failures, @@ -557,19 +557,19 @@ impl Node { ); } InitiateDecision::BloomMiss => { - self.metrics().discovery.req_bloom_miss.inc(); + self.metrics().lookup.req_bloom_miss.inc(); debug!( target_node = %self.peer_display_name(dest), "Discovery skipped, target not in any peer bloom filter" ); } InitiateDecision::Proceed => { - let ttl = self.config().node.discovery.ttl; + let ttl = self.config().node.lookup.ttl; let sent = self.initiate_lookup(dest, ttl).await; // If no tree peers had the target, fail immediately if sent == 0 { - crate::proto::lookup::initiate_failed(&mut self.discovery, dest, now_ms); + crate::proto::lookup::initiate_failed(&mut self.lookup, dest, now_ms); debug!( target_node = %self.peer_display_name(dest), "Discovery failed, no tree peers with bloom match" @@ -582,7 +582,7 @@ impl Node { /// Check pending lookups for next-attempt or final timeout. /// /// Called periodically from the tick handler. The lookup state machine - /// runs through `node.discovery.attempt_timeouts_secs` (default + /// runs through `node.lookup.attempt_timeouts_secs` (default /// `[1, 2, 4, 8]`): each entry is the deadline for one attempt. When the /// current attempt's deadline elapses: /// - If more entries remain: send the next attempt with a fresh @@ -590,12 +590,12 @@ impl Node { /// - Otherwise: declare the destination unreachable, drop queued packets, /// and emit ICMPv6 destination-unreachable for each. pub(in crate::node) async fn check_pending_lookups(&mut self, now_ms: u64) { - let attempt_timeouts = self.config().node.discovery.attempt_timeouts_secs.clone(); + let attempt_timeouts = self.config().node.lookup.attempt_timeouts_secs.clone(); let outcome = - crate::proto::lookup::poll_pending(&mut self.discovery, now_ms, &attempt_timeouts); + crate::proto::lookup::poll_pending(&mut self.lookup, now_ms, &attempt_timeouts); for (target, attempt) in outcome.retries { - let ttl = self.config().node.discovery.ttl; + let ttl = self.config().node.lookup.ttl; let sent = self.initiate_lookup(&target, ttl).await; if sent > 0 { debug!( @@ -607,7 +607,7 @@ impl Node { } for (addr, failures) in outcome.timeouts { - self.metrics().discovery.resp_timed_out.inc(); + self.metrics().lookup.resp_timed_out.inc(); let queued = self.pending_tun_packets.remove(&addr); let pkt_count = queued.as_ref().map_or(0, |p| p.len()); info!( @@ -625,8 +625,8 @@ impl Node { } /// Reset discovery backoff on topology changes. - pub(in crate::node) fn reset_discovery_backoff(&mut self) { - let cleared = self.discovery.reset_backoff(); + pub(in crate::node) fn reset_lookup_backoff(&mut self) { + let cleared = self.lookup.reset_backoff(); if cleared > 0 { debug!( entries = cleared, diff --git a/src/node/handlers/mmp.rs b/src/node/handlers/mmp.rs index e0866af..393603f 100644 --- a/src/node/handlers/mmp.rs +++ b/src/node/handlers/mmp.rs @@ -232,7 +232,7 @@ impl Node { // Surgical invalidation — see CoordCache::invalidate_via_node doc. self.coord_cache .invalidate_via_node(our_identity.node_addr()); - self.reset_discovery_backoff(); + self.reset_lookup_backoff(); self.metrics().tree.parent_switched.inc(); self.metrics().tree.parent_switches.inc(); info!( @@ -266,7 +266,7 @@ impl Node { // Surgical invalidation — see CoordCache::invalidate_other_roots doc. self.coord_cache .invalidate_other_roots(our_identity.node_addr()); - self.reset_discovery_backoff(); + self.reset_lookup_backoff(); self.metrics().tree.parent_switched.inc(); self.metrics().tree.parent_switches.inc(); info!( diff --git a/src/node/handlers/mod.rs b/src/node/handlers/mod.rs index b5d2e82..8bcc757 100644 --- a/src/node/handlers/mod.rs +++ b/src/node/handlers/mod.rs @@ -2,11 +2,11 @@ #[cfg(unix)] pub(crate) mod connected_udp; -pub(crate) mod discovery; mod dispatch; mod encrypted; mod forwarding; mod handshake; +pub(crate) mod lookup; mod mmp; mod rekey; mod rx_loop; diff --git a/src/node/lifecycle.rs b/src/node/lifecycle.rs index dd7f89e..0b2a0bd 100644 --- a/src/node/lifecycle.rs +++ b/src/node/lifecycle.rs @@ -873,19 +873,19 @@ impl Node { } /// Resolve the LAN-only discovery scope. Applications with explicit - /// connectivity config can set `node.discovery.lan.scope` without + /// connectivity config can set `node.rendezvous.lan.scope` without /// changing the public Nostr discovery `app` tag. The older fallback /// extracts a scope from the Nostr app tag used by default scoped /// discovery. pub(super) fn lan_discovery_scope(&self) -> Option { - if let Some(scope) = self.config().node.discovery.lan.scope.as_deref() { + if let Some(scope) = self.config().node.rendezvous.lan.scope.as_deref() { let scope = scope.trim(); if !scope.is_empty() { return Some(scope.to_string()); } } - let app = self.config().node.discovery.nostr.app.trim(); + let app = self.config().node.rendezvous.nostr.app.trim(); if app.is_empty() { return None; } @@ -1138,9 +1138,12 @@ impl Node { } } - if self.config().node.discovery.nostr.enabled { - match NostrDiscovery::start(self.identity(), self.config().node.discovery.nostr.clone()) - .await + if self.config().node.rendezvous.nostr.enabled { + match NostrDiscovery::start( + self.identity(), + self.config().node.rendezvous.nostr.clone(), + ) + .await { Ok(runtime) => { if let Err(err) = self.refresh_overlay_advert(&runtime).await { @@ -1159,7 +1162,7 @@ impl Node { // mDNS / DNS-SD LAN discovery. Independent of Nostr — runs even // when Nostr is disabled, since it gives us sub-second pairing // on the same link without any relay or NAT-traversal roundtrip. - if self.config().node.discovery.lan.enabled { + if self.config().node.rendezvous.lan.enabled { // Advertise the port of a non-bootstrap operational UDP transport. // Bootstrap transports must be excluded (they are not the node's // listening data-plane socket), and a stable selector (lowest @@ -1183,7 +1186,7 @@ impl Node { self.identity(), scope, advertised_udp_port, - self.config().node.discovery.lan.clone(), + self.config().node.rendezvous.lan.clone(), ) .await { @@ -1623,9 +1626,9 @@ impl Node { peer_config: &PeerConfig, existing: &[PeerAddress], ) -> Vec { - if !self.config().node.discovery.nostr.enabled + if !self.config().node.rendezvous.nostr.enabled || !peer_config.via_nostr - || self.config().node.discovery.nostr.policy + || self.config().node.rendezvous.nostr.policy == crate::config::NostrDiscoveryPolicy::Disabled { return Vec::new(); @@ -1849,8 +1852,8 @@ impl Node { max_age_secs: Option, caller: &'static str, ) { - if !self.config().node.discovery.nostr.enabled - || self.config().node.discovery.nostr.policy + if !self.config().node.rendezvous.nostr.enabled + || self.config().node.rendezvous.nostr.policy != crate::config::NostrDiscoveryPolicy::Open { return; @@ -2040,7 +2043,7 @@ impl Node { /// configured statically or established a link to. /// /// Gated identically to [`run_open_discovery_sweep`]: requires - /// `node.discovery.nostr.enabled` and `policy == open`. + /// `node.rendezvous.nostr.enabled` and `policy == open`. async fn maybe_run_startup_open_discovery_sweep( &mut self, bootstrap: &std::sync::Arc, @@ -2048,8 +2051,8 @@ impl Node { if self.startup_open_discovery_sweep_done { return; } - if !self.config().node.discovery.nostr.enabled - || self.config().node.discovery.nostr.policy + if !self.config().node.rendezvous.nostr.enabled + || self.config().node.rendezvous.nostr.policy != crate::config::NostrDiscoveryPolicy::Open { // Mark done so we don't keep re-checking on every tick. @@ -2063,7 +2066,7 @@ impl Node { let delay_ms = self .config() .node - .discovery + .rendezvous .nostr .startup_sweep_delay_secs .saturating_mul(1000); @@ -2074,7 +2077,7 @@ impl Node { let max_age_secs = self .config() .node - .discovery + .rendezvous .nostr .startup_sweep_max_age_secs; self.run_open_discovery_sweep(bootstrap, Some(max_age_secs), "startup") @@ -2167,7 +2170,7 @@ impl Node { let cap_remaining = self .config() .node - .discovery + .rendezvous .nostr .open_discovery_max_pending .saturating_sub(current_open_discovery_pending); @@ -2179,7 +2182,7 @@ impl Node { now_ms.saturating_add( self.config() .node - .discovery + .rendezvous .nostr .advert_ttl_secs .saturating_mul(1000) @@ -2191,7 +2194,7 @@ impl Node { &self, bootstrap: &std::sync::Arc, ) -> Option { - if !self.config().node.discovery.nostr.enabled { + if !self.config().node.rendezvous.nostr.enabled { return None; } @@ -2249,7 +2252,7 @@ impl Node { STUN observation failed; advertising no UDP \ endpoint. Either set transports.udp.external_addr, \ bind to a specific public IP, or ensure \ - node.discovery.nostr.stun_servers is reachable" + node.rendezvous.nostr.stun_servers is reachable" ); } } @@ -2332,9 +2335,9 @@ impl Node { version: ADVERT_VERSION, endpoints, signal_relays: has_udp_nat - .then(|| self.config().node.discovery.nostr.dm_relays.clone()), + .then(|| self.config().node.rendezvous.nostr.dm_relays.clone()), stun_servers: has_udp_nat - .then(|| self.config().node.discovery.nostr.stun_servers.clone()), + .then(|| self.config().node.rendezvous.nostr.stun_servers.clone()), }) } diff --git a/src/node/metrics.rs b/src/node/metrics.rs index e94d70a..d5b9f61 100644 --- a/src/node/metrics.rs +++ b/src/node/metrics.rs @@ -15,8 +15,8 @@ use std::sync::atomic::{AtomicU64, Ordering}; use crate::node::reject::{BloomReject, DiscoveryReject, ForwardingReject, TreeReject}; use crate::node::stats::{ - BloomStatsSnapshot, CongestionStatsSnapshot, DiscoveryStatsSnapshot, ErrorSignalStatsSnapshot, - ForwardingStatsSnapshot, TreeStatsSnapshot, + BloomStatsSnapshot, CongestionStatsSnapshot, ErrorSignalStatsSnapshot, ForwardingStatsSnapshot, + LookupStatsSnapshot, TreeStatsSnapshot, }; /// An atomic counter. @@ -202,7 +202,7 @@ impl ForwardingMetrics { /// Discovery metric counters. #[derive(Default)] -pub struct DiscoveryMetrics { +pub struct LookupMetrics { pub req_received: Padded, pub req_decode_error: Counter, pub req_duplicate: Counter, @@ -227,7 +227,7 @@ pub struct DiscoveryMetrics { pub resp_timed_out: Counter, } -impl DiscoveryMetrics { +impl LookupMetrics { /// Mirror of `DiscoveryStats::record_reject`: route a typed discovery /// rejection to its counter. #[inline] @@ -245,8 +245,8 @@ impl DiscoveryMetrics { } /// Sample every counter into a serializable snapshot. - pub fn snapshot(&self) -> DiscoveryStatsSnapshot { - DiscoveryStatsSnapshot { + pub fn snapshot(&self) -> LookupStatsSnapshot { + LookupStatsSnapshot { req_received: self.req_received.get(), req_decode_error: self.req_decode_error.get(), req_duplicate: self.req_duplicate.get(), @@ -429,7 +429,7 @@ impl ErrorMetrics { #[derive(Default)] pub struct MetricsRegistry { pub forwarding: ForwardingMetrics, - pub discovery: DiscoveryMetrics, + pub lookup: LookupMetrics, pub tree: TreeMetrics, pub bloom: BloomMetrics, pub congestion: CongestionMetrics, @@ -457,7 +457,7 @@ mod tests { #[test] fn discovery_record_reject_routes_to_field() { - let m = DiscoveryMetrics::default(); + let m = LookupMetrics::default(); m.record_reject(DiscoveryReject::ReqDuplicate); m.record_reject(DiscoveryReject::ReqDuplicate); m.record_reject(DiscoveryReject::RespNoRoute); @@ -468,7 +468,7 @@ mod tests { #[test] fn discovery_direct_counters_increment() { - let m = DiscoveryMetrics::default(); + let m = LookupMetrics::default(); m.req_received.inc(); m.req_forwarded.inc(); m.req_forwarded.inc(); @@ -501,9 +501,9 @@ mod tests { fn registry_subcounters_are_independent() { let r = MetricsRegistry::new(); r.forwarding.record_received(10); - r.discovery.req_received.inc(); + r.lookup.req_received.inc(); assert_eq!(r.forwarding.received_packets.get(), 1); assert_eq!(r.forwarding.received_bytes.get(), 10); - assert_eq!(r.discovery.req_received.get(), 1); + assert_eq!(r.lookup.req_received.get(), 1); } } diff --git a/src/node/mod.rs b/src/node/mod.rs index dfced01..cdaa1d3 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -261,7 +261,7 @@ struct PendingConnect { /// /// The `addr_to_link` map enables dispatching incoming packets to the right /// connection before authentication completes. -// Discovery lookup constants moved to config: node.discovery.attempt_timeouts_secs, node.discovery.ttl +// Discovery lookup constants moved to config: node.lookup.attempt_timeouts_secs, node.lookup.ttl pub struct Node { // === Immutable Context === /// Shared immutable context bundle: the single source of truth for the @@ -337,7 +337,7 @@ pub struct Node { // === Discovery === /// Discovery-subsystem state: recent-request dedup cache, in-flight /// lookups, originator-side backoff, and transit-side forward limiter. - discovery: Lookup, + lookup: Lookup, // === Counters === /// Next link ID to allocate. @@ -594,9 +594,9 @@ impl Node { let max_peers = config.node.limits.max_peers; let max_links = config.node.limits.max_links; let coords_response_interval_ms = config.node.session.coords_response_interval_ms; - let backoff_base_secs = config.node.discovery.backoff_base_secs; - let backoff_max_secs = config.node.discovery.backoff_max_secs; - let forward_min_interval_secs = config.node.discovery.forward_min_interval_secs; + let backoff_base_secs = config.node.lookup.backoff_base_secs; + let backoff_max_secs = config.node.lookup.backoff_max_secs; + let forward_min_interval_secs = config.node.lookup.forward_min_interval_secs; let base_host_map = HostMap::from_peer_configs(config.peers()); let hosts_path = std::path::PathBuf::from(crate::upper::hosts::DEFAULT_HOSTS_PATH); @@ -678,7 +678,7 @@ impl Node { coords_response_rate_limiter: RoutingErrorRateLimiter::with_interval_ms( coords_response_interval_ms, ), - discovery: Lookup::new( + lookup: Lookup::new( LookupBackoff::with_params(backoff_base_secs, backoff_max_secs), LookupForwardRateLimiter::with_interval_ms(forward_min_interval_secs * 1000), ), @@ -843,7 +843,7 @@ impl Node { coords_response_rate_limiter: RoutingErrorRateLimiter::with_interval_ms( coords_response_interval_ms, ), - discovery: Lookup::new(LookupBackoff::new(), LookupForwardRateLimiter::new()), + lookup: Lookup::new(LookupBackoff::new(), LookupForwardRateLimiter::new()), pending_connects: Vec::new(), retry_pending: HashMap::new(), nostr_discovery: None, @@ -2423,7 +2423,7 @@ impl Node { /// Disable the discovery forward rate limiter (for tests). #[cfg(test)] pub(crate) fn disable_discovery_forward_rate_limit(&mut self) { - self.discovery.forward_limiter.set_interval_ms(0); + self.lookup.forward_limiter.set_interval_ms(0); } #[cfg(test)] @@ -2535,19 +2535,19 @@ impl Node { /// Number of pending discovery lookups. pub fn pending_lookup_count(&self) -> usize { - self.discovery.pending_lookups.len() + self.lookup.pending_lookups.len() } /// Iterate over pending discovery lookups for diagnostics. pub fn pending_lookups_iter( &self, ) -> impl Iterator { - self.discovery.pending_lookups.iter() + self.lookup.pending_lookups.iter() } /// Number of recent discovery requests tracked. pub fn recent_request_count(&self) -> usize { - self.discovery.recent_requests.len() + self.lookup.recent_requests.len() } /// Count of destinations with queued TUN packets awaiting session setup. diff --git a/src/node/stats.rs b/src/node/stats.rs index 2315ad9..93e92f8 100644 --- a/src/node/stats.rs +++ b/src/node/stats.rs @@ -238,7 +238,7 @@ pub struct ForwardingStatsSnapshot { } #[derive(Clone, Debug, Default, Serialize)] -pub struct DiscoveryStatsSnapshot { +pub struct LookupStatsSnapshot { pub req_received: u64, pub req_decode_error: u64, pub req_duplicate: u64, diff --git a/src/node/tests/discovery.rs b/src/node/tests/discovery.rs index f1e0de2..0bb55e6 100644 --- a/src/node/tests/discovery.rs +++ b/src/node/tests/discovery.rs @@ -22,7 +22,7 @@ async fn test_request_decode_error() { let from = make_node_addr(0xAA); // Too-short payload: should log error and return without panic node.handle_lookup_request(&from, &[0x00; 5]).await; - assert!(node.discovery.recent_requests.is_empty()); + assert!(node.lookup.recent_requests.is_empty()); } #[tokio::test] @@ -38,11 +38,11 @@ async fn test_request_dedup() { // First request: accepted node.handle_lookup_request(&from, payload).await; - assert_eq!(node.discovery.recent_requests.len(), 1); + assert_eq!(node.lookup.recent_requests.len(), 1); // Duplicate request: dropped node.handle_lookup_request(&from, payload).await; - assert_eq!(node.discovery.recent_requests.len(), 1); + assert_eq!(node.lookup.recent_requests.len(), 1); } #[tokio::test] @@ -60,7 +60,7 @@ async fn test_request_target_is_self() { // Should succeed without panic (response send will fail silently // since we have no peers to route toward origin) node.handle_lookup_request(&from, payload).await; - assert!(node.discovery.recent_requests.contains_key(&777)); + assert!(node.lookup.recent_requests.contains_key(&777)); } #[tokio::test] @@ -76,7 +76,7 @@ async fn test_request_ttl_zero_not_forwarded() { node.handle_lookup_request(&from, payload).await; // Request recorded, but not forwarded (TTL=0, and no peers anyway) - assert!(node.discovery.recent_requests.contains_key(&666)); + assert!(node.lookup.recent_requests.contains_key(&666)); } // ============================================================================ @@ -114,7 +114,7 @@ async fn test_response_originator_caches_route() { let payload = &response.encode()[1..]; // skip msg_type // No entry in recent_requests for 555 → we're the originator - assert!(!node.discovery.recent_requests.contains_key(&555)); + assert!(!node.lookup.recent_requests.contains_key(&555)); node.handle_lookup_response(&from, payload).await; @@ -148,7 +148,7 @@ async fn test_response_transit_needs_recent_request() { .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_millis() as u64; - node.discovery + node.lookup .recent_requests .insert(444, RecentRequest::new(make_node_addr(0xDD), now_ms)); @@ -319,16 +319,16 @@ async fn test_recent_request_expiry() { .as_millis() as u64; // Insert an old request (11 seconds ago) - node.discovery + node.lookup .recent_requests .insert(123, RecentRequest::new(make_node_addr(1), now_ms - 11_000)); // Insert a recent request - node.discovery + node.lookup .recent_requests .insert(456, RecentRequest::new(make_node_addr(2), now_ms)); - assert_eq!(node.discovery.recent_requests.len(), 2); + assert_eq!(node.lookup.recent_requests.len(), 2); // Trigger purge via a new lookup request let target = make_node_addr(0xBB); @@ -340,9 +340,9 @@ async fn test_recent_request_expiry() { .await; // Old entry (123) should be purged, recent entry (456) and new entry (789) kept - assert!(!node.discovery.recent_requests.contains_key(&123)); - assert!(node.discovery.recent_requests.contains_key(&456)); - assert!(node.discovery.recent_requests.contains_key(&789)); + assert!(!node.lookup.recent_requests.contains_key(&123)); + assert!(node.lookup.recent_requests.contains_key(&456)); + assert!(node.lookup.recent_requests.contains_key(&789)); } // ============================================================================ @@ -381,7 +381,7 @@ async fn test_request_forwarding_two_node() { // Node1 should have recorded the request assert!( - nodes[1].node.discovery.recent_requests.contains_key(&42), + nodes[1].node.lookup.recent_requests.contains_key(&42), "Node 1 should have recorded the forwarded request" ); @@ -449,13 +449,13 @@ async fn test_request_three_node_chain() { // Node1 should have been a transit node (has the request_id in recent_requests) assert!( - !nodes[1].node.discovery.recent_requests.is_empty(), + !nodes[1].node.lookup.recent_requests.is_empty(), "Node 1 should have recorded the forwarded request" ); // Node2 should have received the request (it's the target) assert!( - !nodes[2].node.discovery.recent_requests.is_empty(), + !nodes[2].node.lookup.recent_requests.is_empty(), "Node 2 should have received the request" ); @@ -504,7 +504,7 @@ async fn test_request_dedup_convergent_paths() { // Node2 (the target) must have received the request assert!( - nodes[2].node.discovery.recent_requests.contains_key(&300), + nodes[2].node.lookup.recent_requests.contains_key(&300), "Node 2 (target) should have received the request" ); @@ -941,8 +941,8 @@ async fn test_open_discovery_sweep_queues_eligible_skips_filtered() { // Build node with open-discovery enabled. let mut config = crate::Config::new(); - config.node.discovery.nostr.enabled = true; - config.node.discovery.nostr.policy = NostrDiscoveryPolicy::Open; + config.node.rendezvous.nostr.enabled = true; + config.node.rendezvous.nostr.policy = NostrDiscoveryPolicy::Open; let mut node = crate::Node::new(config).unwrap(); // Identity of an already-connected peer; insert into node.peers @@ -1016,7 +1016,7 @@ async fn test_open_discovery_sweep_queues_eligible_skips_filtered() { /// Pin the per-attempt timeout sequence in `check_pending_lookups`. /// /// Drives the state machine deterministically through the default -/// `node.discovery.attempt_timeouts_secs = [1, 2, 4, 8]` sequence. +/// `node.lookup.attempt_timeouts_secs = [1, 2, 4, 8]` sequence. /// Asserts: /// 1. **Sequence timing** — retries fire at the cumulative deadlines /// (t=1100ms, 3100ms, 7100ms) and unreachable at t=15100ms. @@ -1049,7 +1049,7 @@ async fn test_check_pending_lookups_default_sequence_unreachable() { // Default attempt_timeouts_secs is [1, 2, 4, 8]. Confirm so the test // cannot silently drift if the default changes. assert_eq!( - node.config().node.discovery.attempt_timeouts_secs, + node.config().node.lookup.attempt_timeouts_secs, vec![1, 2, 4, 8], "test pins the [1,2,4,8] default; update the test if the default changes" ); @@ -1108,18 +1108,18 @@ async fn test_check_pending_lookups_default_sequence_unreachable() { // Inject a PendingLookup directly: attempt=1, last_sent_ms=0. This // mirrors the post-condition of a successful `maybe_initiate_lookup` // at t=0 without depending on wall-clock-derived `Self::now_ms()`. - node.discovery + node.lookup .pending_lookups .insert(target_addr, PendingLookup::new(0)); - let baseline_initiated = node.metrics().discovery.req_initiated.get(); - let baseline_timed_out = node.metrics().discovery.resp_timed_out.get(); + let baseline_initiated = node.metrics().lookup.req_initiated.get(); + let baseline_timed_out = node.metrics().lookup.resp_timed_out.get(); // --- t = 1100ms: first retry deadline (1*1000) --- node.check_pending_lookups(1100).await; { let entry = node - .discovery + .lookup .pending_lookups .get(&target_addr) .expect("still pending"); @@ -1127,7 +1127,7 @@ async fn test_check_pending_lookups_default_sequence_unreachable() { assert_eq!(entry.last_sent_ms, 1100); } assert_eq!( - node.metrics().discovery.req_initiated.get(), + node.metrics().lookup.req_initiated.get(), baseline_initiated + 1, "retry #1 must invoke initiate_lookup exactly once" ); @@ -1136,7 +1136,7 @@ async fn test_check_pending_lookups_default_sequence_unreachable() { node.check_pending_lookups(3100).await; { let entry = node - .discovery + .lookup .pending_lookups .get(&target_addr) .expect("still pending"); @@ -1144,7 +1144,7 @@ async fn test_check_pending_lookups_default_sequence_unreachable() { assert_eq!(entry.last_sent_ms, 3100); } assert_eq!( - node.metrics().discovery.req_initiated.get(), + node.metrics().lookup.req_initiated.get(), baseline_initiated + 2, "retry #2 must invoke initiate_lookup exactly once more" ); @@ -1153,7 +1153,7 @@ async fn test_check_pending_lookups_default_sequence_unreachable() { node.check_pending_lookups(7100).await; { let entry = node - .discovery + .lookup .pending_lookups .get(&target_addr) .expect("still pending"); @@ -1161,7 +1161,7 @@ async fn test_check_pending_lookups_default_sequence_unreachable() { assert_eq!(entry.last_sent_ms, 7100); } assert_eq!( - node.metrics().discovery.req_initiated.get(), + node.metrics().lookup.req_initiated.get(), baseline_initiated + 3, "retry #3 must invoke initiate_lookup exactly once more" ); @@ -1169,16 +1169,16 @@ async fn test_check_pending_lookups_default_sequence_unreachable() { // --- Just-before-final: at t=15099ms the 8s window is not yet reached --- node.check_pending_lookups(15_099).await; assert!( - node.discovery.pending_lookups.contains_key(&target_addr), + node.lookup.pending_lookups.contains_key(&target_addr), "8s window not yet expired: pending_lookup must persist" ); assert_eq!( - node.metrics().discovery.req_initiated.get(), + node.metrics().lookup.req_initiated.get(), baseline_initiated + 3, "no new attempt before final deadline" ); assert_eq!( - node.metrics().discovery.resp_timed_out.get(), + node.metrics().lookup.resp_timed_out.get(), baseline_timed_out, "no timeout before final deadline" ); @@ -1192,18 +1192,18 @@ async fn test_check_pending_lookups_default_sequence_unreachable() { // Pending lookup is dropped. assert!( - !node.discovery.pending_lookups.contains_key(&target_addr), + !node.lookup.pending_lookups.contains_key(&target_addr), "final timeout must remove the pending_lookups entry" ); // resp_timed_out counter ticked. assert_eq!( - node.metrics().discovery.resp_timed_out.get(), + node.metrics().lookup.resp_timed_out.get(), baseline_timed_out + 1, "final timeout must increment discovery.resp_timed_out" ); // No additional initiate_lookup on the timeout step. assert_eq!( - node.metrics().discovery.req_initiated.get(), + node.metrics().lookup.req_initiated.get(), baseline_initiated + 3, "the final-timeout step must NOT call initiate_lookup" ); diff --git a/src/node/tests/unit.rs b/src/node/tests/unit.rs index b8af260..d447f33 100644 --- a/src/node/tests/unit.rs +++ b/src/node/tests/unit.rs @@ -31,7 +31,7 @@ fn test_node_with_identity() { fn test_node_with_identity_validates_config() { let identity = Identity::generate(); let mut config = Config::new(); - config.node.discovery.nostr.enabled = false; + config.node.rendezvous.nostr.enabled = false; config.peers = vec![crate::config::PeerConfig { npub: "npub1peer".to_string(), via_nostr: true, @@ -169,11 +169,11 @@ async fn test_node_state_transitions() { async fn test_node_start_does_not_wait_for_nostr_relay_startup() { let mut config = Config::new(); config.node.control.enabled = false; - config.node.discovery.nostr.enabled = true; - config.node.discovery.nostr.advertise = true; - config.node.discovery.nostr.policy = crate::config::NostrDiscoveryPolicy::Open; - config.node.discovery.nostr.advert_relays = vec!["wss://127.0.0.1:9".to_string()]; - config.node.discovery.nostr.dm_relays = vec!["wss://127.0.0.1:9".to_string()]; + config.node.rendezvous.nostr.enabled = true; + config.node.rendezvous.nostr.advertise = true; + config.node.rendezvous.nostr.policy = crate::config::NostrDiscoveryPolicy::Open; + config.node.rendezvous.nostr.advert_relays = vec!["wss://127.0.0.1:9".to_string()]; + config.node.rendezvous.nostr.dm_relays = vec!["wss://127.0.0.1:9".to_string()]; config.transports.udp = crate::config::TransportInstances::Single(crate::config::UdpConfig { bind_addr: Some("127.0.0.1:0".to_string()), advertise_on_nostr: Some(true), diff --git a/src/node/tree.rs b/src/node/tree.rs index f14c4b3..6d04dad 100644 --- a/src/node/tree.rs +++ b/src/node/tree.rs @@ -371,7 +371,7 @@ impl Node { // Surgical invalidation — see CoordCache::invalidate_via_node doc. self.coord_cache .invalidate_via_node(our_identity.node_addr()); - self.reset_discovery_backoff(); + self.reset_lookup_backoff(); self.metrics().tree.parent_switched.inc(); self.metrics().tree.parent_switches.inc(); @@ -417,7 +417,7 @@ impl Node { // Surgical invalidation — see CoordCache::invalidate_other_roots doc. self.coord_cache .invalidate_other_roots(our_identity.node_addr()); - self.reset_discovery_backoff(); + self.reset_lookup_backoff(); self.metrics().tree.parent_switched.inc(); self.metrics().tree.parent_switches.inc(); info!( @@ -460,7 +460,7 @@ impl Node { .invalidate_via_node(our_identity.node_addr()); self.coord_cache .invalidate_other_roots(self.tree_state.root()); - self.reset_discovery_backoff(); + self.reset_lookup_backoff(); self.send_tree_announce_to_all().await; } } @@ -504,7 +504,7 @@ impl Node { // Surgical invalidation — see CoordCache::invalidate_via_node doc. self.coord_cache .invalidate_via_node(our_identity.node_addr()); - self.reset_discovery_backoff(); + self.reset_lookup_backoff(); let new_addrs: Vec = self.tree_state.my_coords().node_addrs().copied().collect(); @@ -625,7 +625,7 @@ impl Node { // Surgical invalidation — see CoordCache::invalidate_via_node doc. self.coord_cache .invalidate_via_node(our_identity.node_addr()); - self.reset_discovery_backoff(); + self.reset_lookup_backoff(); self.metrics().tree.parent_switched.inc(); self.metrics().tree.parent_switches.inc(); @@ -669,7 +669,7 @@ impl Node { // Surgical invalidation — see CoordCache::invalidate_other_roots doc. self.coord_cache .invalidate_other_roots(our_identity.node_addr()); - self.reset_discovery_backoff(); + self.reset_lookup_backoff(); self.metrics().tree.parent_switched.inc(); self.metrics().tree.parent_switches.inc(); info!( diff --git a/src/proto/lookup/core.rs b/src/proto/lookup/core.rs index 9af173a..291ba8e 100644 --- a/src/proto/lookup/core.rs +++ b/src/proto/lookup/core.rs @@ -1,7 +1,7 @@ //! Sans-IO mesh lookup decision core. //! //! Pure, runtime-agnostic decision logic for the mesh lookup protocol. The -//! async I/O adapter in `node::handlers::discovery` decodes wire bytes, +//! async I/O adapter in `node::handlers::lookup` decodes wire bytes, //! calls into this core, and drives the returned actions (the actual //! encrypted sends). No I/O, no clock, no metrics, no logging here. diff --git a/src/proto/lookup/limits.rs b/src/proto/lookup/limits.rs index b4cb26f..249df86 100644 --- a/src/proto/lookup/limits.rs +++ b/src/proto/lookup/limits.rs @@ -4,7 +4,7 @@ //! //! - **`LookupBackoff`** (originator-side, optional): Exponential //! suppression of fresh lookups after the per-attempt sequence in -//! `node.discovery.attempt_timeouts_secs` has been exhausted. +//! `node.lookup.attempt_timeouts_secs` has been exhausted. //! **Disabled by default** (base/cap = 0); the per-attempt sequence //! is the only retry pacing in the standard configuration. Reset on //! topology changes (parent change, new peer, first RTT, reconnection). diff --git a/src/proto/lookup/mod.rs b/src/proto/lookup/mod.rs index 22a5a12..2a5d905 100644 --- a/src/proto/lookup/mod.rs +++ b/src/proto/lookup/mod.rs @@ -2,7 +2,7 @@ //! //! Pure, runtime-agnostic mesh lookup state and rate limiting, migrated out //! of the async node shell. The async I/O handlers remain in -//! `node::handlers::discovery`. The mesh lookup wire codec now lives here in +//! `node::handlers::lookup`. The mesh lookup wire codec now lives here in //! `wire.rs` (the `LookupRequest` / `LookupResponse` structs), per the //! wire-migrates-with-subsystem policy. //!