Drop the nostr-discovery cargo feature flag

Make Nostr-mediated overlay discovery unconditional, mirroring the
philosophy of PR #79's collapse of the tui/ble/gateway features in
favor of platform cfg gates. nostr / nostr-sdk are pure Rust over
WebSockets/TCP, so they build cleanly on every FIPS-supported
platform — there is no need for the parallel feature gate.

The flag was already in `default = [...]`, so no behavior change for
anyone using `cargo build` without `--no-default-features`. Operators
who explicitly disabled the feature will now find Nostr code present
in the binary; the runtime check `node.discovery.nostr.enabled` still
controls whether the runtime starts.

Cargo.toml:
- Remove the `[features]` table entirely.
- Drop `optional = true` from `nostr` and `nostr-sdk`.

Source: 27 cfg sites collapsed across 5 files —
`src/discovery.rs`, `src/discovery/nostr/mod.rs`,
`src/node/handlers/rx_loop.rs`, `src/node/lifecycle.rs`,
`src/node/mod.rs`. Two `#[cfg(not(feature = "nostr-discovery"))]`
fallback blocks (the udp:nat-without-runtime debug-log path and the
"feature not compiled in" warning at startup) were removed as dead
code; the always-on path already handles the missing-runtime case
via `nostr_discovery: Option<NostrDiscovery>`.

Packaging and tooling:
- `packaging/openwrt-ipk/Makefile`: drop a stale `--features gateway`
  flag (the `gateway` feature was already removed in PR #79; this
  was a leftover that the build path tolerated only because cargo
  ignored unknown feature names).
- `testing/scripts/build.sh`: drop `DEFAULT_CARGO_BUILD_ARGS=(--features
  nostr-discovery)`; defaults are empty.
- `packaging/common/fips.yaml`: drop the "requires the
  nostr-discovery feature" comment from the discovery section.

Bundled cleanup:
- Apply `cargo clippy --fix` against three pre-existing warnings in
  `src/discovery/nostr/runtime.rs` and `src/discovery/nostr/stun.rs`
  (collapsed `if let Some` chain; two redundant `as i32` casts).
  These were always present but masked when the feature gate was
  off; they surface now that the code is unconditionally compiled.
- `cargo fmt` settled two minor formatting drift sites in
  `src/bin/fips-gateway.rs` and `src/config/mod.rs`.

Tests: 1083 passed, 0 failed, 4 ignored. clippy clean. fmt clean.
This commit is contained in:
Johnathan Corgan
2026-04-30 10:24:32 +00:00
parent 37c2973e2f
commit e641eb5b5f
15 changed files with 36 additions and 78 deletions
+9
View File
@@ -197,6 +197,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Nostr-mediated overlay discovery is now always-on. The
`nostr-discovery` cargo feature flag has been dropped along with the
`optional = true` markers on `nostr` / `nostr-sdk` dependencies and
every `#[cfg(feature = "nostr-discovery")]` source-level gate. Plain
`cargo build` produces a binary with overlay discovery available
whether or not the operator enables it via
`node.discovery.nostr.enabled`. Mirrors PR #79's collapse of the
`tui` / `ble` / `gateway` features in favor of platform cfg gates.
No runtime behavior change — the feature was in `default` already
- MMP link-layer report intervals retuned for constrained transports:
steady-state floor raised from 100ms to 1000ms, ceiling from 2000ms
to 5000ms. Cold-start uses a 200ms floor for the first 5 SRTT samples
+2 -6
View File
@@ -8,10 +8,6 @@ authors = ["Johnathan Corgan <jcorgan@corganlabs.com>"]
repository = "https://github.com/jmcorgan/fips"
readme = "README.md"
[features]
default = ["nostr-discovery"]
nostr-discovery = ["dep:nostr", "dep:nostr-sdk"]
[dependencies]
ratatui = "0.30"
secp256k1 = { version = "0.30", features = ["rand", "global-context"] }
@@ -36,8 +32,8 @@ socket2 = { version = "0.6.2", features = ["all"] }
tokio-socks = "0.5"
portable-atomic = { version = "1", features = ["std"] }
nostr = { version = "0.44", features = ["std", "nip59"], optional = true }
nostr-sdk = { version = "0.44", optional = true }
nostr = { version = "0.44", features = ["std", "nip59"] }
nostr-sdk = "0.44"
[target.'cfg(unix)'.dependencies]
tun = { version = "0.8.5", features = ["async"] }
-1
View File
@@ -12,7 +12,6 @@ node:
# nsec: "nsec1..."
discovery:
# Optional Nostr-mediated overlay endpoint discovery.
# Requires a build of fips compiled with the 'nostr-discovery' feature.
# nostr:
# enabled: true
# policy: configured_only # disabled | configured_only | open
-1
View File
@@ -82,7 +82,6 @@ define Build/Compile
cargo build \
--release \
--target $(RUST_TARGET) \
--features gateway \
--bin fips \
--bin fipsctl \
--bin fipstop \
+1 -4
View File
@@ -242,10 +242,7 @@ async fn main() {
last_failure = Some(format!("recv_from failed: {}", e));
}
Err(_) => {
last_failure = Some(format!(
"no response within {}s",
PROBE_TIMEOUT_SECS
));
last_failure = Some(format!("no response within {}s", PROBE_TIMEOUT_SECS));
}
}
}
+1 -4
View File
@@ -71,10 +71,7 @@ fn is_loopback_addr_str(addr: &str) -> bool {
Some((h, _)) => h,
None => addr,
};
host == "localhost"
|| host == "::1"
|| host == "0:0:0:0:0:0:0:1"
|| host.starts_with("127.")
host == "localhost" || host == "::1" || host == "0:0:0:0:0:0:0:1" || host.starts_with("127.")
}
/// Derive the key file path from a config file path.
+3 -4
View File
@@ -6,7 +6,6 @@
//! it hands the live socket and selected remote endpoint to FIPS so the
//! existing Noise/FMP transport path can take over.
#[cfg(feature = "nostr-discovery")]
pub mod nostr;
use crate::config::UdpConfig;
@@ -16,9 +15,9 @@ use std::net::{SocketAddr, UdpSocket};
/// Punch-probe magic ("NPTC", network byte order). First byte `0x4E`
/// collides with FMP's prefix-version high-nibble check, so the UDP
/// transport silently filters packets carrying this magic to keep
/// post-adoption handshake logs clean. Defined here (unconditionally
/// compiled) rather than inside the `nostr-discovery`-gated submodule so
/// the filter applies regardless of feature configuration.
/// post-adoption handshake logs clean. Defined at the top-level
/// `discovery` module so the UDP filter and the nostr submodule's
/// punch sender share the same constant.
pub const PUNCH_MAGIC: u32 = 0x4E505443;
/// Punch-probe-ack magic ("NPTA", network byte order). Same filter as
-2
View File
@@ -1,5 +1,3 @@
#![cfg(feature = "nostr-discovery")]
mod runtime;
mod signal;
mod stun;
+3 -3
View File
@@ -829,15 +829,15 @@ impl NostrDiscovery {
advert: Option<&OverlayAdvert>,
) -> Result<Vec<String>, BootstrapError> {
let mut merged = self.find_recipient_inbox_relays(target_pubkey).await?;
if let Some(advert) = advert {
if let Some(relays) = advert.signal_relays.as_ref() {
if let Some(advert) = advert
&& let Some(relays) = advert.signal_relays.as_ref()
{
for relay in relays {
if !merged.contains(relay) {
merged.push(relay.clone());
}
}
}
}
for relay in &self.config.dm_relays {
if !merged.contains(relay) {
merged.push(relay.clone());
+2 -2
View File
@@ -268,8 +268,8 @@ fn private_interface_ips() -> Vec<IpAddr> {
// SAFETY: `cursor` points at a valid node from the `getifaddrs` list.
let entry = unsafe { &*cursor };
let flags = entry.ifa_flags as i32;
let is_up = (flags & libc::IFF_UP as i32) != 0;
let is_loopback = (flags & libc::IFF_LOOPBACK as i32) != 0;
let is_up = (flags & libc::IFF_UP) != 0;
let is_loopback = (flags & libc::IFF_LOOPBACK) != 0;
if is_up && !is_loopback && !entry.ifa_addr.is_null() {
// SAFETY: `ifa_addr` is non-null and its concrete type matches
+2 -2
View File
@@ -6,8 +6,8 @@ pub const ADVERT_KIND: u16 = 37195;
pub const ADVERT_IDENTIFIER: &str = "fips-overlay-v1";
pub const ADVERT_VERSION: u32 = 1;
pub const SIGNAL_KIND: u16 = 21059;
// Re-exported from `crate::discovery` so the UDP transport's stray-probe
// filter compiles regardless of the `nostr-discovery` cargo feature.
// Defined at the top-level `discovery` module; re-exported here so the
// existing punch sender / receiver imports remain unchanged.
pub use crate::discovery::{PUNCH_ACK_MAGIC, PUNCH_MAGIC};
pub const PROTOCOL_VERSION: &str = "1";
-1
View File
@@ -115,7 +115,6 @@ impl Node {
let now_ms = Self::now_ms();
self.reload_peer_acl();
self.poll_pending_connects().await;
#[cfg(feature = "nostr-discovery")]
self.poll_nostr_discovery().await;
self.resend_pending_handshakes(now_ms).await;
self.resend_pending_rekeys(now_ms).await;
-33
View File
@@ -2,7 +2,6 @@
use super::{Node, NodeError, NodeState};
use crate::config::{ConnectPolicy, PeerAddress, PeerConfig};
#[cfg(feature = "nostr-discovery")]
use crate::discovery::nostr::{
ADVERT_IDENTIFIER, ADVERT_VERSION, BootstrapEvent, NostrDiscovery, OverlayAdvert,
OverlayEndpointAdvert, OverlayTransportKind,
@@ -15,13 +14,11 @@ use crate::protocol::{Disconnect, DisconnectReason};
use crate::transport::{Link, LinkDirection, LinkId, TransportAddr, TransportId, packet_channel};
use crate::upper::tun::{TunDevice, TunState, run_tun_reader, shutdown_tun_interface};
use crate::{NodeAddr, PeerIdentity};
#[cfg(feature = "nostr-discovery")]
use std::collections::HashSet;
use std::thread;
use std::time::Duration;
use tracing::{debug, info, warn};
#[cfg(feature = "nostr-discovery")]
const OPEN_DISCOVERY_RETRY_LIFETIME_MULTIPLIER: u64 = 2;
impl Node {
@@ -381,7 +378,6 @@ impl Node {
}
}
#[cfg(feature = "nostr-discovery")]
pub(super) async fn poll_nostr_discovery(&mut self) {
let Some(bootstrap) = self.nostr_discovery.clone() else {
return;
@@ -569,7 +565,6 @@ impl Node {
info!(count = self.transports.len(), "Transports initialized");
}
#[cfg(feature = "nostr-discovery")]
if self.config.node.discovery.nostr.enabled {
match NostrDiscovery::start(&self.identity, self.config.node.discovery.nostr.clone())
.await
@@ -587,13 +582,6 @@ impl Node {
}
}
#[cfg(not(feature = "nostr-discovery"))]
if self.config.node.discovery.nostr.enabled {
warn!(
"Nostr overlay discovery configured but this build was compiled without the 'nostr-discovery' feature"
);
}
// Connect to static peers before TUN is active
// This allows handshake messages to be sent before we start accepting packets
self.initiate_peer_connections().await;
@@ -867,7 +855,6 @@ impl Node {
.await;
// Stop Nostr overlay discovery background work and withdraw any advert.
#[cfg(feature = "nostr-discovery")]
if let Some(bootstrap) = self.nostr_discovery.take()
&& let Err(e) = bootstrap.shutdown().await
{
@@ -989,7 +976,6 @@ impl Node {
.collect()
}
#[cfg(feature = "nostr-discovery")]
async fn nostr_peer_fallback_addresses(
&self,
peer_config: &PeerConfig,
@@ -1045,7 +1031,6 @@ impl Node {
fallback
}
#[cfg(feature = "nostr-discovery")]
fn overlay_endpoint_to_peer_address(
endpoint: &OverlayEndpointAdvert,
priority: u8,
@@ -1074,13 +1059,6 @@ impl Node {
if !allow_bootstrap_nat {
continue;
}
#[cfg(not(feature = "nostr-discovery"))]
{
debug!(npub = %peer_config.npub, "Skipping udp:nat address because this build does not include the nostr-discovery feature");
continue;
}
#[cfg(feature = "nostr-discovery")]
{
let Some(bootstrap) = self.nostr_discovery.clone() else {
debug!(npub = %peer_config.npub, "No Nostr overlay runtime for udp:nat address");
continue;
@@ -1089,7 +1067,6 @@ impl Node {
info!(npub = %peer_config.npub, "Started Nostr UDP NAT traversal attempt");
return Ok(());
}
}
let (transport_id, remote_addr) = if addr.transport == "ethernet" {
match self.resolve_ethernet_addr(&addr.addr) {
@@ -1163,7 +1140,6 @@ impl Node {
)))
}
#[cfg(feature = "nostr-discovery")]
async fn queue_open_discovery_retries(&mut self, bootstrap: &std::sync::Arc<NostrDiscovery>) {
if !self.config.node.discovery.nostr.enabled
|| self.config.node.discovery.nostr.policy != crate::config::NostrDiscoveryPolicy::Open
@@ -1251,7 +1227,6 @@ impl Node {
}
}
#[cfg(feature = "nostr-discovery")]
fn available_outbound_slots(&self) -> usize {
let connection_used = self
.connections
@@ -1272,7 +1247,6 @@ impl Node {
connection_slots.min(peer_slots)
}
#[cfg(feature = "nostr-discovery")]
fn open_discovery_enqueue_budget(&self, configured_npubs: &HashSet<String>) -> usize {
let current_open_discovery_pending = self
.retry_pending
@@ -1291,7 +1265,6 @@ impl Node {
cap_remaining.min(self.available_outbound_slots())
}
#[cfg(feature = "nostr-discovery")]
fn open_discovery_retry_expires_at_ms(&self, now_ms: u64) -> u64 {
now_ms.saturating_add(
self.config
@@ -1304,7 +1277,6 @@ impl Node {
)
}
#[cfg(feature = "nostr-discovery")]
fn build_overlay_advert(&self) -> Option<OverlayAdvert> {
if !self.config.node.discovery.nostr.enabled {
return None;
@@ -1391,7 +1363,6 @@ impl Node {
})
}
#[cfg(feature = "nostr-discovery")]
async fn refresh_overlay_advert(
&self,
bootstrap: &std::sync::Arc<NostrDiscovery>,
@@ -1400,7 +1371,6 @@ impl Node {
bootstrap.update_local_advert(advert).await
}
#[cfg(feature = "nostr-discovery")]
fn lookup_udp_config(&self, transport_name: Option<&str>) -> Option<&crate::config::UdpConfig> {
match (&self.config.transports.udp, transport_name) {
(crate::config::TransportInstances::Single(cfg), None) => Some(cfg),
@@ -1409,7 +1379,6 @@ impl Node {
}
}
#[cfg(feature = "nostr-discovery")]
fn lookup_tcp_config(&self, transport_name: Option<&str>) -> Option<&crate::config::TcpConfig> {
match (&self.config.transports.tcp, transport_name) {
(crate::config::TransportInstances::Single(cfg), None) => Some(cfg),
@@ -1418,7 +1387,6 @@ impl Node {
}
}
#[cfg(feature = "nostr-discovery")]
fn lookup_tor_config(&self, transport_name: Option<&str>) -> Option<&crate::config::TorConfig> {
match (&self.config.transports.tor, transport_name) {
(crate::config::TransportInstances::Single(cfg), None) => Some(cfg),
@@ -1449,7 +1417,6 @@ impl Node {
return Ok(());
}
#[cfg(feature = "nostr-discovery")]
{
let fallback = self
.nostr_peer_fallback_addresses(peer_config, &static_addresses)
-3
View File
@@ -431,7 +431,6 @@ pub struct Node {
retry_pending: HashMap<NodeAddr, retry::RetryState>,
/// Optional Nostr/STUN overlay discovery coordinator for `udp:nat` peers.
#[cfg(feature = "nostr-discovery")]
nostr_discovery: Option<Arc<crate::discovery::nostr::NostrDiscovery>>,
/// Per-peer UDP transports adopted from NAT traversal handoff.
bootstrap_transports: HashSet<TransportId>,
@@ -596,7 +595,6 @@ impl Node {
),
pending_connects: Vec::new(),
retry_pending: HashMap::new(),
#[cfg(feature = "nostr-discovery")]
nostr_discovery: None,
bootstrap_transports: HashSet::new(),
last_parent_reeval: None,
@@ -724,7 +722,6 @@ impl Node {
discovery_forward_limiter: DiscoveryForwardRateLimiter::new(),
pending_connects: Vec::new(),
retry_pending: HashMap::new(),
#[cfg(feature = "nostr-discovery")]
nostr_discovery: None,
bootstrap_transports: HashSet::new(),
last_parent_reeval: None,
+4 -3
View File
@@ -19,9 +19,10 @@ if [ ! -f "$PROJECT_ROOT/Cargo.toml" ]; then
fi
BUILD_DOCKER=true
# NAT harness binaries need Nostr bootstrap support; everything else is
# governed by platform cfg gates after PR #79's feature-matrix collapse.
DEFAULT_CARGO_BUILD_ARGS=(--features nostr-discovery)
# Default flags for the container-image cargo build. Empty by default;
# every subsystem is governed by platform cfg gates with no feature
# flags required.
DEFAULT_CARGO_BUILD_ARGS=()
if [ -n "${FIPS_CARGO_BUILD_ARGS:-}" ]; then
# shellcheck disable=SC2206
CARGO_BUILD_ARGS=($FIPS_CARGO_BUILD_ARGS)