mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 08:14:42 +00:00
node: extract immutable state into a shared context and atomic metric registry
Store node counters in an atomic metric registry read through &self, and introduce a shared NodeContext bundle holding the effectively-immutable fields (config, identity, startup epoch, capability limits). Source the immutable config and identity reads across the receive hot path, the handshake/session/mmp/encrypted state machines, and the discovery, tree, bloom, retry, and lifecycle modules through the context accessors rather than direct field reads. The Node fields and the context are rebuilt in lockstep at every mutation site.
This commit is contained in:
+63
-49
@@ -55,6 +55,10 @@ impl Node {
|
||||
new_by_addr.insert(*identity.node_addr(), peer);
|
||||
}
|
||||
|
||||
// Read the current peer set directly from the field: update_peers is the
|
||||
// config-source mutation owner (it writes self.config.peers below and then
|
||||
// rebuilds the context), so it manages config.peers directly rather than
|
||||
// through the context accessor — same rationale as the write at line ~124.
|
||||
let current_by_addr: HashMap<NodeAddr, PeerConfig> = self
|
||||
.config
|
||||
.peers()
|
||||
@@ -121,6 +125,7 @@ impl Node {
|
||||
.collect();
|
||||
|
||||
self.config.peers = new_by_addr.into_values().collect();
|
||||
self.rebuild_context();
|
||||
|
||||
for peer_config in added_configs {
|
||||
outcome.added += 1;
|
||||
@@ -171,11 +176,12 @@ impl Node {
|
||||
} else {
|
||||
match self.initiate_peer_connection(&peer_config).await {
|
||||
Ok(()) => {
|
||||
let handshake_timeout_secs =
|
||||
self.config().node.rate_limit.handshake_timeout_secs;
|
||||
if let Some(state) = self.retry_pending.get_mut(&node_addr) {
|
||||
state.peer_config = peer_config;
|
||||
state.retry_after_ms = Self::now_ms().saturating_add(
|
||||
self.config.node.rate_limit.handshake_timeout_secs * 1000,
|
||||
);
|
||||
state.retry_after_ms =
|
||||
Self::now_ms().saturating_add(handshake_timeout_secs * 1000);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -204,7 +210,7 @@ impl Node {
|
||||
// initiation) immediately on startup — without waiting for the link-layer
|
||||
// handshake to complete first.
|
||||
let peer_identities: Vec<(PeerIdentity, Option<String>)> = self
|
||||
.config
|
||||
.config()
|
||||
.peers()
|
||||
.iter()
|
||||
.filter_map(|pc| {
|
||||
@@ -224,7 +230,7 @@ impl Node {
|
||||
}
|
||||
|
||||
// Collect peer configs to avoid borrow conflicts
|
||||
let peer_configs: Vec<_> = self.config.auto_connect_peers().cloned().collect();
|
||||
let peer_configs: Vec<_> = self.config().auto_connect_peers().cloned().collect();
|
||||
|
||||
if peer_configs.is_empty() {
|
||||
debug!("No static peers configured");
|
||||
@@ -397,7 +403,7 @@ impl Node {
|
||||
transport_id,
|
||||
remote_addr.clone(),
|
||||
LinkDirection::Outbound,
|
||||
Duration::from_millis(self.config.node.base_rtt_ms),
|
||||
Duration::from_millis(self.config().node.base_rtt_ms),
|
||||
)
|
||||
} else {
|
||||
Link::connectionless(
|
||||
@@ -405,7 +411,7 @@ impl Node {
|
||||
transport_id,
|
||||
remote_addr.clone(),
|
||||
LinkDirection::Outbound,
|
||||
Duration::from_millis(self.config.node.base_rtt_ms),
|
||||
Duration::from_millis(self.config().node.base_rtt_ms),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -479,7 +485,7 @@ impl Node {
|
||||
};
|
||||
|
||||
// Start the Noise handshake and get message 1
|
||||
let our_keypair = self.identity.keypair();
|
||||
let our_keypair = self.identity().keypair();
|
||||
let noise_msg1 =
|
||||
match connection.start_handshake(our_keypair, self.startup_epoch, current_time_ms) {
|
||||
Ok(msg) => msg,
|
||||
@@ -510,7 +516,7 @@ impl Node {
|
||||
);
|
||||
|
||||
// Store msg1 for resend and schedule first resend
|
||||
let resend_interval = self.config.node.rate_limit.handshake_resend_interval_ms;
|
||||
let resend_interval = self.config().node.rate_limit.handshake_resend_interval_ms;
|
||||
connection.set_handshake_msg1(wire_msg1.clone(), current_time_ms + resend_interval);
|
||||
|
||||
// Track in pending_outbound for msg2 dispatch
|
||||
@@ -581,7 +587,7 @@ impl Node {
|
||||
let node_addr = *identity.node_addr();
|
||||
|
||||
// Skip self
|
||||
if node_addr == *self.identity.node_addr() {
|
||||
if node_addr == *self.identity().node_addr() {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -720,7 +726,7 @@ impl Node {
|
||||
// semantics, and its existing cross-connection
|
||||
// logic in handle_msg1 reconciles when the winner's
|
||||
// fresh msg1 arrives over the adopted socket.
|
||||
let our_addr = self.identity.node_addr();
|
||||
let our_addr = self.identity().node_addr();
|
||||
if our_addr >= &peer_addr {
|
||||
debug!(
|
||||
peer_npub = %peer_npub,
|
||||
@@ -868,14 +874,14 @@ impl Node {
|
||||
/// extracts a scope from the Nostr app tag used by default scoped
|
||||
/// discovery.
|
||||
pub(super) fn lan_discovery_scope(&self) -> Option<String> {
|
||||
if let Some(scope) = self.config.node.discovery.lan.scope.as_deref() {
|
||||
if let Some(scope) = self.config().node.discovery.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.discovery.nostr.app.trim();
|
||||
if app.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -1051,7 +1057,7 @@ impl Node {
|
||||
self.state = NodeState::Starting;
|
||||
|
||||
// Create packet channel for transport -> Node communication
|
||||
let packet_buffer_size = self.config.node.buffers.packet_channel;
|
||||
let packet_buffer_size = self.config().node.buffers.packet_channel;
|
||||
let (packet_tx, packet_rx) = packet_channel(packet_buffer_size);
|
||||
self.packet_tx = Some(packet_tx.clone());
|
||||
self.packet_rx = Some(packet_rx);
|
||||
@@ -1128,8 +1134,8 @@ impl Node {
|
||||
}
|
||||
}
|
||||
|
||||
if self.config.node.discovery.nostr.enabled {
|
||||
match NostrDiscovery::start(&self.identity, self.config.node.discovery.nostr.clone())
|
||||
if self.config().node.discovery.nostr.enabled {
|
||||
match NostrDiscovery::start(self.identity(), self.config().node.discovery.nostr.clone())
|
||||
.await
|
||||
{
|
||||
Ok(runtime) => {
|
||||
@@ -1149,7 +1155,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.discovery.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
|
||||
@@ -1170,10 +1176,10 @@ impl Node {
|
||||
.unwrap_or(0);
|
||||
let scope = self.lan_discovery_scope();
|
||||
match crate::discovery::lan::LanDiscovery::start(
|
||||
&self.identity,
|
||||
self.identity(),
|
||||
scope,
|
||||
advertised_udp_port,
|
||||
self.config.node.discovery.lan.clone(),
|
||||
self.config().node.discovery.lan.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -1192,9 +1198,9 @@ impl Node {
|
||||
self.initiate_peer_connections().await;
|
||||
|
||||
// Initialize TUN interface last, after transports and peers are ready
|
||||
if self.config.tun.enabled {
|
||||
let address = *self.identity.address();
|
||||
match TunDevice::create(&self.config.tun, address).await {
|
||||
if self.config().tun.enabled {
|
||||
let address = *self.identity().address();
|
||||
match TunDevice::create(&self.config().tun, address).await {
|
||||
Ok(device) => {
|
||||
let mtu = device.mtu();
|
||||
let name = device.name().to_string();
|
||||
@@ -1241,7 +1247,7 @@ impl Node {
|
||||
let reader_tun_tx = tun_tx.clone();
|
||||
|
||||
// Create outbound channel for TUN reader → Node
|
||||
let tun_channel_size = self.config.node.buffers.tun_channel;
|
||||
let tun_channel_size = self.config().node.buffers.tun_channel;
|
||||
let (outbound_tx, outbound_rx) = tokio::sync::mpsc::channel(tun_channel_size);
|
||||
|
||||
// Spawn reader thread
|
||||
@@ -1307,19 +1313,19 @@ impl Node {
|
||||
// `IPV6_V6ONLY=0` is set explicitly so IPv4 clients on
|
||||
// 127.0.0.1 still reach us regardless of kernel sysctl
|
||||
// defaults — but only when bind is on a wildcard / IPv6 path.
|
||||
if self.config.dns.enabled {
|
||||
let addr_str = self.config.dns.bind_addr();
|
||||
if self.config().dns.enabled {
|
||||
let addr_str = self.config().dns.bind_addr();
|
||||
match addr_str.parse::<std::net::IpAddr>() {
|
||||
Ok(ip) => {
|
||||
let bind = std::net::SocketAddr::new(ip, self.config.dns.port());
|
||||
let bind = std::net::SocketAddr::new(ip, self.config().dns.port());
|
||||
match Self::bind_dns_socket(bind) {
|
||||
Ok(socket) => {
|
||||
let dns_channel_size = self.config.node.buffers.dns_channel;
|
||||
let dns_channel_size = self.config().node.buffers.dns_channel;
|
||||
let (identity_tx, identity_rx) =
|
||||
tokio::sync::mpsc::channel(dns_channel_size);
|
||||
let dns_ttl = self.config.dns.ttl();
|
||||
let dns_ttl = self.config().dns.ttl();
|
||||
let base_hosts = crate::upper::hosts::HostMap::from_peer_configs(
|
||||
self.config.peers(),
|
||||
self.config().peers(),
|
||||
);
|
||||
let hosts_path =
|
||||
std::path::PathBuf::from(crate::upper::hosts::DEFAULT_HOSTS_PATH);
|
||||
@@ -1332,7 +1338,7 @@ impl Node {
|
||||
// When TUN isn't enabled or the name can't be
|
||||
// resolved, `None` disables the filter (there
|
||||
// is no mesh surface to defend anyway).
|
||||
let mesh_ifindex = Self::lookup_mesh_ifindex(self.config.tun.name());
|
||||
let mesh_ifindex = Self::lookup_mesh_ifindex(self.config().tun.name());
|
||||
info!(
|
||||
bind = %bind,
|
||||
hosts = reloader.hosts().len(),
|
||||
@@ -1599,9 +1605,9 @@ impl Node {
|
||||
peer_config: &PeerConfig,
|
||||
existing: &[PeerAddress],
|
||||
) -> Vec<PeerAddress> {
|
||||
if !self.config.node.discovery.nostr.enabled
|
||||
if !self.config().node.discovery.nostr.enabled
|
||||
|| !peer_config.via_nostr
|
||||
|| self.config.node.discovery.nostr.policy
|
||||
|| self.config().node.discovery.nostr.policy
|
||||
== crate::config::NostrDiscoveryPolicy::Disabled
|
||||
{
|
||||
return Vec::new();
|
||||
@@ -1825,14 +1831,15 @@ impl Node {
|
||||
max_age_secs: Option<u64>,
|
||||
caller: &'static str,
|
||||
) {
|
||||
if !self.config.node.discovery.nostr.enabled
|
||||
|| self.config.node.discovery.nostr.policy != crate::config::NostrDiscoveryPolicy::Open
|
||||
if !self.config().node.discovery.nostr.enabled
|
||||
|| self.config().node.discovery.nostr.policy
|
||||
!= crate::config::NostrDiscoveryPolicy::Open
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let configured_npubs = self
|
||||
.config
|
||||
.config()
|
||||
.peers()
|
||||
.iter()
|
||||
.map(|peer| peer.npub.clone())
|
||||
@@ -1901,7 +1908,7 @@ impl Node {
|
||||
}
|
||||
};
|
||||
let node_addr = *peer_identity.node_addr();
|
||||
if node_addr == *self.identity.node_addr() {
|
||||
if node_addr == *self.identity().node_addr() {
|
||||
skipped_self = skipped_self.saturating_add(1);
|
||||
continue;
|
||||
}
|
||||
@@ -2023,8 +2030,9 @@ impl Node {
|
||||
if self.startup_open_discovery_sweep_done {
|
||||
return;
|
||||
}
|
||||
if !self.config.node.discovery.nostr.enabled
|
||||
|| self.config.node.discovery.nostr.policy != crate::config::NostrDiscoveryPolicy::Open
|
||||
if !self.config().node.discovery.nostr.enabled
|
||||
|| self.config().node.discovery.nostr.policy
|
||||
!= crate::config::NostrDiscoveryPolicy::Open
|
||||
{
|
||||
// Mark done so we don't keep re-checking on every tick.
|
||||
self.startup_open_discovery_sweep_done = true;
|
||||
@@ -2035,7 +2043,7 @@ impl Node {
|
||||
};
|
||||
let now_ms = Self::now_ms();
|
||||
let delay_ms = self
|
||||
.config
|
||||
.config()
|
||||
.node
|
||||
.discovery
|
||||
.nostr
|
||||
@@ -2045,7 +2053,12 @@ impl Node {
|
||||
return;
|
||||
}
|
||||
|
||||
let max_age_secs = self.config.node.discovery.nostr.startup_sweep_max_age_secs;
|
||||
let max_age_secs = self
|
||||
.config()
|
||||
.node
|
||||
.discovery
|
||||
.nostr
|
||||
.startup_sweep_max_age_secs;
|
||||
self.run_open_discovery_sweep(bootstrap, Some(max_age_secs), "startup")
|
||||
.await;
|
||||
self.startup_open_discovery_sweep_done = true;
|
||||
@@ -2134,7 +2147,7 @@ impl Node {
|
||||
.count();
|
||||
|
||||
let cap_remaining = self
|
||||
.config
|
||||
.config()
|
||||
.node
|
||||
.discovery
|
||||
.nostr
|
||||
@@ -2146,7 +2159,7 @@ impl Node {
|
||||
|
||||
fn open_discovery_retry_expires_at_ms(&self, now_ms: u64) -> u64 {
|
||||
now_ms.saturating_add(
|
||||
self.config
|
||||
self.config()
|
||||
.node
|
||||
.discovery
|
||||
.nostr
|
||||
@@ -2160,7 +2173,7 @@ impl Node {
|
||||
&self,
|
||||
bootstrap: &std::sync::Arc<NostrDiscovery>,
|
||||
) -> Option<OverlayAdvert> {
|
||||
if !self.config.node.discovery.nostr.enabled {
|
||||
if !self.config().node.discovery.nostr.enabled {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -2300,9 +2313,10 @@ impl Node {
|
||||
identifier: ADVERT_IDENTIFIER.to_string(),
|
||||
version: ADVERT_VERSION,
|
||||
endpoints,
|
||||
signal_relays: has_udp_nat.then(|| self.config.node.discovery.nostr.dm_relays.clone()),
|
||||
signal_relays: has_udp_nat
|
||||
.then(|| self.config().node.discovery.nostr.dm_relays.clone()),
|
||||
stun_servers: has_udp_nat
|
||||
.then(|| self.config.node.discovery.nostr.stun_servers.clone()),
|
||||
.then(|| self.config().node.discovery.nostr.stun_servers.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2315,7 +2329,7 @@ impl Node {
|
||||
}
|
||||
|
||||
fn lookup_udp_config(&self, transport_name: Option<&str>) -> Option<&crate::config::UdpConfig> {
|
||||
match (&self.config.transports.udp, transport_name) {
|
||||
match (&self.config().transports.udp, transport_name) {
|
||||
(crate::config::TransportInstances::Single(cfg), None) => Some(cfg),
|
||||
(crate::config::TransportInstances::Named(configs), Some(name)) => configs.get(name),
|
||||
_ => None,
|
||||
@@ -2323,7 +2337,7 @@ impl Node {
|
||||
}
|
||||
|
||||
fn lookup_tcp_config(&self, transport_name: Option<&str>) -> Option<&crate::config::TcpConfig> {
|
||||
match (&self.config.transports.tcp, transport_name) {
|
||||
match (&self.config().transports.tcp, transport_name) {
|
||||
(crate::config::TransportInstances::Single(cfg), None) => Some(cfg),
|
||||
(crate::config::TransportInstances::Named(configs), Some(name)) => configs.get(name),
|
||||
_ => None,
|
||||
@@ -2331,7 +2345,7 @@ impl Node {
|
||||
}
|
||||
|
||||
fn lookup_tor_config(&self, transport_name: Option<&str>) -> Option<&crate::config::TorConfig> {
|
||||
match (&self.config.transports.tor, transport_name) {
|
||||
match (&self.config().transports.tor, transport_name) {
|
||||
(crate::config::TransportInstances::Single(cfg), None) => Some(cfg),
|
||||
(crate::config::TransportInstances::Named(configs), Some(name)) => configs.get(name),
|
||||
_ => None,
|
||||
@@ -2465,7 +2479,7 @@ impl Node {
|
||||
return false;
|
||||
};
|
||||
let stale_after_ms = self
|
||||
.config
|
||||
.config()
|
||||
.node
|
||||
.heartbeat_interval_secs
|
||||
.saturating_mul(1000)
|
||||
|
||||
Reference in New Issue
Block a user