From 886e68ab0606ba844f0a5c1be5d24d79c5b66928 Mon Sep 17 00:00:00 2001 From: dkijania Date: Thu, 30 Jul 2026 23:01:36 +0200 Subject: [PATCH] =?UTF-8?q?server:=20LIGHT=5FNODE=5FPEERS=20=E2=80=94=20ov?= =?UTF-8?q?erride=20the=20seed=20peers=20(relay-fleet=20spoke)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lets a node dial a chosen peer set instead of the network's published seeds, keeping the network's chain_id. Paired with LIGHT_NODE_STATIC_PEERS=1 this pins a node to a trusted relay fleet (the wallet/exchange spoke in #17) and never dials the public seeds — the missing piece that makes static-peer mode usable by an external customer. `LIGHT_NODE_PEERS` is comma-separated multiaddrs, parsed by `parse_peer_override` (trims entries, drops blanks, all-blank => fall back to seeds) and leaked to 'static once at startup so the peer plumbing (gossip / sync-ledger reads / broadcast) stays borrow-free. Multiaddr validity is checked at dial time as before. Tested: 3 hermetic unit tests for parse_peer_override; clippy -D warnings + fmt clean; 14 bin tests pass. The static-peer connect behavior this feeds is already validated on the lightnet (gossip_relay / submit_mempool). A live devnet override smoke test is left for CI / a clean env. Refs #17 Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Ma8isK1FQpvLuKAPahhw5U --- crates/mina-light-node/src/bin/server.rs | 68 +++++++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/crates/mina-light-node/src/bin/server.rs b/crates/mina-light-node/src/bin/server.rs index 8734d75..0915b47 100644 --- a/crates/mina-light-node/src/bin/server.rs +++ b/crates/mina-light-node/src/bin/server.rs @@ -17,7 +17,9 @@ //! POST /submit {"tx_hex":"…"} — broadcast a signed user command to gossip //! //! Env: MINA_NETWORK (devnet|mainnet), LIGHT_NODE_HTTP_ADDR (default 127.0.0.1:8645), -//! MINA_VK_JSON (optional, for networks without an embedded VK). +//! MINA_VK_JSON (optional, for networks without an embedded VK), +//! LIGHT_NODE_PEERS (optional comma-separated multiaddrs — override the seed peers, +//! e.g. to pin a static-peer spoke to a relay fleet; see LIGHT_NODE_STATIC_PEERS). // jemalloc returns freed memory to the OS far better than glibc malloc, whose per-thread // arenas retain the verifier's large transient allocations and ratchet RSS to a high @@ -169,6 +171,19 @@ fn now_unix() -> u64 { .unwrap_or(0) } +/// Parse a `LIGHT_NODE_PEERS` value into a peer list, or `None` to fall back to the +/// network's default seeds. Comma-separated; each entry trimmed; blank entries dropped; +/// an all-blank value yields `None`. Multiaddr validity is checked later at dial time. +fn parse_peer_override(raw: &str) -> Option> { + let list: Vec = raw + .split(',') + .map(str::trim) + .filter(|p| !p.is_empty()) + .map(String::from) + .collect(); + (!list.is_empty()).then_some(list) +} + /// Adopt `info` as the tip if it's strictly newer than the current one. Used by the /// precomputed-block source (the gossip worker adopts via fork-choice instead). fn adopt_tip(state: &Arc, info: TipInfo) { @@ -261,8 +276,32 @@ fn precomputed_block_loop(verifier: &Verifier, state: &Arc, dir: &str) async fn main() { env_logger::init(); let network = std::env::var("MINA_NETWORK").unwrap_or_else(|_| "devnet".into()); - let (chain_id, peers) = + let (chain_id, seed_peers) = network_seeds(&network).unwrap_or_else(|| panic!("unknown MINA_NETWORK {network:?}")); + + // Peer set: the network's published seeds by default, or an explicit override via + // `LIGHT_NODE_PEERS` (comma-separated multiaddrs). The override keeps the network's + // chain_id — it just changes *who* we dial. Paired with `LIGHT_NODE_STATIC_PEERS=1` + // this pins the node to a chosen relay fleet (a wallet/exchange spoke) and never + // dials the public seeds. Leaked to `'static` once at startup so the whole peer + // plumbing (gossip, sync-ledger reads, broadcast) stays borrow-free. + let peers: &'static [&'static str] = match std::env::var("LIGHT_NODE_PEERS") + .ok() + .and_then(|s| parse_peer_override(&s)) + { + Some(list) => { + log::info!( + "LIGHT_NODE_PEERS override: {} configured peer(s)", + list.len() + ); + let leaked: Vec<&'static str> = list + .into_iter() + .map(|p| &*Box::leak(p.into_boxed_str())) + .collect(); + Box::leak(leaked.into_boxed_slice()) + } + None => seed_peers, + }; let addr: SocketAddr = std::env::var("LIGHT_NODE_HTTP_ADDR") .unwrap_or_else(|_| "127.0.0.1:8645".into()) .parse() @@ -1235,4 +1274,29 @@ mod tests { let (_, p3) = page_slice(items, 3, Some(10)); assert!(p3.is_empty()); } + + #[test] + fn parse_peer_override_splits_and_trims() { + assert_eq!( + parse_peer_override("/ip4/1.2.3.4/tcp/8302 , /dns4/hub/tcp/10003"), + Some(vec![ + "/ip4/1.2.3.4/tcp/8302".to_string(), + "/dns4/hub/tcp/10003".to_string(), + ]) + ); + } + + #[test] + fn parse_peer_override_drops_blank_entries() { + assert_eq!( + parse_peer_override("/ip4/1.2.3.4/tcp/8302,, ,"), + Some(vec!["/ip4/1.2.3.4/tcp/8302".to_string()]) + ); + } + + #[test] + fn parse_peer_override_all_blank_is_none() { + assert_eq!(parse_peer_override(""), None); + assert_eq!(parse_peer_override(" , , "), None); + } }