From 0200e84aeaa2a507b6037875413ef37763f485d5 Mon Sep 17 00:00:00 2001 From: Lorenz Leutgeb Date: Mon, 18 Aug 2025 17:18:21 +0200 Subject: [PATCH] bootstrap: Add IPv6, IPv4, Onion addresses This should allow bootstrapping when DNS is not available or a connection via Tor is desired. --- crates/radicle-cli/tests/commands.rs | 2 +- crates/radicle-node/src/runtime.rs | 26 +++++----- crates/radicle/src/node/config.rs | 74 ++++++++++++++++++++-------- 3 files changed, 69 insertions(+), 33 deletions(-) diff --git a/crates/radicle-cli/tests/commands.rs b/crates/radicle-cli/tests/commands.rs index e2f15fb3..fffeee2d 100644 --- a/crates/radicle-cli/tests/commands.rs +++ b/crates/radicle-cli/tests/commands.rs @@ -250,7 +250,7 @@ fn rad_config() { let mut environment = Environment::new(); let alias = Alias::new("alice"); let profile = environment.profile_with(profile::Config { - preferred_seeds: vec![RADICLE_NODE_BOOTSTRAP_IRIS.clone()], + preferred_seeds: vec![RADICLE_NODE_BOOTSTRAP_IRIS.clone()[0].clone()], ..profile::Config::new(alias) }); let working = tempfile::tempdir().unwrap(); diff --git a/crates/radicle-node/src/runtime.rs b/crates/radicle-node/src/runtime.rs index eb679c01..45e471ec 100644 --- a/crates/radicle-node/src/runtime.rs +++ b/crates/radicle-node/src/runtime.rs @@ -185,19 +185,21 @@ impl Runtime { if config.connect.is_empty() && stores.addresses().is_empty()? { log::info!(target: "node", "Address book is empty. Adding bootstrap nodes.."); - for (alias, version, addr) in config.network.bootstrap() { - let (id, addr) = addr.into(); + for (alias, version, addrs) in config.network.bootstrap() { + for addr in addrs { + let (id, addr) = addr.into(); - stores.addresses_mut().insert( - &id, - version, - radicle::node::Features::SEED, - &alias, - 0, - &UserAgent::default(), - clock.into(), - [node::KnownAddress::new(addr, address::Source::Bootstrap)], - )?; + stores.addresses_mut().insert( + &id, + version, + radicle::node::Features::SEED, + &alias, + 0, + &UserAgent::default(), + clock.into(), + [node::KnownAddress::new(addr, address::Source::Bootstrap)], + )?; + } } log::info!(target: "node", "{} nodes added to address book", stores.addresses().len()?); } diff --git a/crates/radicle/src/node/config.rs b/crates/radicle/src/node/config.rs index 164591dd..d65df8af 100644 --- a/crates/radicle/src/node/config.rs +++ b/crates/radicle/src/node/config.rs @@ -20,27 +20,60 @@ pub const DEFAULT_WORKERS: usize = 8; /// Configured public seeds. pub mod seeds { - use std::str::FromStr; + use std::{ + net::{Ipv4Addr, Ipv6Addr}, + str::FromStr, + sync::LazyLock, + }; - use super::{ConnectAddress, PeerAddr}; - use std::sync::LazyLock; + use cyphernet::addr::{tor::OnionAddrV3, HostName, NetAddr}; + + use super::{ConnectAddress, NodeId, PeerAddr}; + + /// A helper to generate many connect addresses for a node, using port 8776. + fn to_connect_addresses(id: NodeId, hostnames: Vec) -> Vec { + hostnames + .into_iter() + .map(|hostname| PeerAddr::new(id, NetAddr::new(hostname, 8776).into()).into()) + .collect() + } /// A public Radicle seed node for the community. - pub static RADICLE_NODE_BOOTSTRAP_IRIS: LazyLock = LazyLock::new(|| { - // SAFETY: `ConnectAddress` is known at compile time. - #[allow(clippy::unwrap_used)] - PeerAddr::from_str("z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@iris.radicle.xyz:8776") - .unwrap() - .into() + pub static RADICLE_NODE_BOOTSTRAP_IRIS: LazyLock> = LazyLock::new(|| { + to_connect_addresses( + #[allow(clippy::unwrap_used)] // Value is manually verified. + NodeId::from_str("z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7").unwrap(), + vec![ + HostName::Dns("iris.radicle.xyz".to_owned()), + Ipv6Addr::new(0x2a01, 0x4f9, 0xc010, 0xdfaa, 0, 0, 0, 1).into(), + Ipv4Addr::new(95, 217, 156, 6).into(), + #[allow(clippy::unwrap_used)] // Value is manually verified. + OnionAddrV3::from_str( + "irisradizskwweumpydlj4oammoshkxxjur3ztcmo7cou5emc6s5lfid.onion", + ) + .unwrap() + .into(), + ], + ) }); /// A public Radicle seed node for the community. - pub static RADICLE_NODE_BOOTSTRAP_ROSA: LazyLock = LazyLock::new(|| { - // SAFETY: `ConnectAddress` is known at compile time. - #[allow(clippy::unwrap_used)] - PeerAddr::from_str("z6Mkmqogy2qEM2ummccUthFEaaHvyYmYBYh3dbe9W4ebScxo@rosa.radicle.xyz:8776") - .unwrap() - .into() + pub static RADICLE_NODE_BOOTSTRAP_ROSA: LazyLock> = LazyLock::new(|| { + to_connect_addresses( + #[allow(clippy::unwrap_used)] // Value is manually verified. + NodeId::from_str("z6Mkmqogy2qEM2ummccUthFEaaHvyYmYBYh3dbe9W4ebScxo").unwrap(), + vec![ + HostName::Dns("rosa.radicle.xyz".to_owned()), + Ipv6Addr::new(0x2a01, 0x4ff, 0xf0, 0xabd3, 0, 0, 0, 1).into(), + Ipv4Addr::new(5, 161, 85, 124).into(), + #[allow(clippy::unwrap_used)] // Value is manually verified. + OnionAddrV3::from_str( + "rosarad5bxgdlgjnzzjygnsxrwxmoaj4vn7xinlstwglxvyt64jlnhyd.onion", + ) + .unwrap() + .into(), + ], + ) }); } @@ -56,7 +89,7 @@ pub enum Network { impl Network { /// Bootstrap nodes for this network. - pub fn bootstrap(&self) -> Vec<(Alias, ProtocolVersion, ConnectAddress)> { + pub fn bootstrap(&self) -> Vec<(Alias, ProtocolVersion, Vec)> { match self { Self::Main => [ ( @@ -79,10 +112,11 @@ impl Network { /// Public seeds for this network. pub fn public_seeds(&self) -> Vec { match self { - Self::Main => vec![ - seeds::RADICLE_NODE_BOOTSTRAP_IRIS.clone(), - seeds::RADICLE_NODE_BOOTSTRAP_ROSA.clone(), - ], + Self::Main => { + let mut result = seeds::RADICLE_NODE_BOOTSTRAP_IRIS.clone(); + result.extend(seeds::RADICLE_NODE_BOOTSTRAP_ROSA.clone()); + result + } Self::Test => vec![], } }