Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
node, cli: remove predefined bootstrap nodes
✗ CI failure Ivan Stanković committed 3 months ago
commit bde727e6562e4d41463daf80dea6dae8f4be5735
parent 1abc29414c2ade8a68f7c9aa646bcf6dfafff440
3 failed (3 total) View logs
13 files changed +147 -133
modified crates/radicle-cli/examples/rad-config.md
@@ -5,9 +5,7 @@ In its simplest form, `rad config` prints the current configuration.
$ rad config
{
  "publicExplorer": "https://app.radicle.xyz/nodes/$host/$rid$path",
-
  "preferredSeeds": [
-
    "z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@iris.radicle.xyz:8776"
-
  ],
+
  "preferredSeeds": [],
  "web": {
    "pinned": {
      "repositories": []
@@ -63,7 +61,6 @@ You can also get any value in the configuration by path, eg.
$ rad config get node.alias
alice
$ rad config get preferredSeeds
-
z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@iris.radicle.xyz:8776
$ rad config get node.limits.routingMaxSize
1000
```
modified crates/radicle-cli/tests/commands.rs
@@ -7,7 +7,6 @@ use radicle::cob;
use radicle::git;
use radicle::node;
use radicle::node::address::Store as _;
-
use radicle::node::config::seeds::RADICLE_NODE_BOOTSTRAP_IRIS;
use radicle::node::config::DefaultSeedingPolicy;
use radicle::node::events::Event;
use radicle::node::policy::Scope;
@@ -338,7 +337,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()[0].clone()],
+
        preferred_seeds: vec![],
        ..profile::Config::new(alias)
    });
    let working = tempfile::tempdir().unwrap();
modified crates/radicle-cli/tests/util/environment.rs
@@ -20,7 +20,7 @@ use crate::util::formula::formula;

pub(crate) mod config {
    use super::*;
-
    use radicle::node::config::{Config, Limits, Network, RateLimit, RateLimits};
+
    use radicle::node::config::{Config, Limits, RateLimit, RateLimits};

    /// Configuration for a test seed node.
    ///
@@ -30,7 +30,7 @@ pub(crate) mod config {
    /// messages from being dropped.
    pub fn seed(alias: &'static str) -> Config {
        Config {
-
            network: Network::Test,
+
            network: "test".to_string(),
            relay: node::config::Relay::Always,
            limits: Limits {
                rate: RateLimits {
modified crates/radicle-node/src/lib.rs
@@ -49,7 +49,7 @@ pub static USER_AGENT: LazyLock<UserAgent> = LazyLock::new(|| {
pub mod prelude {
    pub use crate::crypto::{PublicKey, Signature};
    pub use crate::identity::{Did, RepoId};
-
    pub use crate::node::{config::Network, Address, Event, NodeId};
+
    pub use crate::node::{Address, Event, NodeId};
    pub use crate::service::filter::Filter;
    pub use crate::service::{DisconnectReason, Message};
    pub use crate::storage::refs::Refs;
modified crates/radicle-node/src/main.rs
@@ -190,6 +190,11 @@ enum ExecutionError {
    #[error(transparent)]
    ConfigurationLoading(#[from] profile::config::LoadError),
    #[error(transparent)]
+
    NetworkLoading(#[from] profile::networks::NetworkLoadError),
+
    /// An unknown network was referenced.
+
    #[error("unknown network: {0}")]
+
    UnknownNetwork(String),
+
    #[error(transparent)]
    Runtime(#[from] radicle_node::runtime::Error),
    #[error(transparent)]
    Fingerprint(#[from] radicle_node::fingerprint::Error),
@@ -231,6 +236,11 @@ fn execute(options: Options) -> Result<(), ExecutionError> {
        // to do in this case.
    }

+
    let networks = profile::networks::Networks::load(&home)?;
+
    let Some(network) = networks.get(&config.node.network.to_owned().into()) else {
+
        return Err(ExecutionError::UnknownNetwork(config.node.network));
+
    };
+

    log::info!(target: "node", "Starting node..");
    log::info!(target: "node", "Version {} ({})", env!("RADICLE_VERSION"), env!("GIT_HEAD"));
    log::info!(target: "node", "Unlocking node keystore..");
@@ -335,7 +345,7 @@ fn execute(options: Options) -> Result<(), ExecutionError> {
        log::debug!(target: "node", "Removing existing control socket..");
        std::fs::remove_file(home.socket()).ok();
    }
-
    Runtime::init(home, config.node, listen, signals, signer)?.run()?;
+
    Runtime::init(home, network, config.node, listen, signals, signer)?.run()?;

    Ok(())
}
modified crates/radicle-node/src/runtime.rs
@@ -26,7 +26,7 @@ use radicle::node::notifications;
use radicle::node::policy::config as policy;
use radicle::node::Event;
use radicle::node::UserAgent;
-
use radicle::profile::Home;
+
use radicle::profile::{networks::Network, Home};
use radicle::{cob, git, storage, Storage};

use crate::control;
@@ -126,6 +126,7 @@ impl Runtime {
    /// This function spawns threads.
    pub fn init<G>(
        home: Home,
+
        network: &Network,
        config: radicle::node::Config,
        listen: Vec<net::SocketAddr>,
        signals: chan::Receiver<Signal>,
@@ -140,7 +141,6 @@ impl Runtime {
    {
        let id = *signer.public_key();
        let alias = config.alias.clone();
-
        let network = config.network;
        let rng = fastrand::Rng::new();
        let clock = LocalTime::now();
        let timestamp = clock.into();
@@ -168,7 +168,7 @@ impl Runtime {
        }

        log::info!(target: "node", "Default seeding policy set to '{}'", &policy);
-
        log::info!(target: "node", "Initializing service ({network:?})..");
+
        log::info!(target: "node", "Using network {}", network.name());

        let announcement = service::gossip::node(&config, timestamp)
            .solve(Default::default())
@@ -191,21 +191,20 @@ 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, 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)],
-
                    )?;
-
                }
+
            let version = 1;
+
            for (alias, addr) in network.nodes() {
+
                let (id, addr) = addr.clone().into();
+

+
                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()?);
        }
modified crates/radicle-node/src/test/environment.rs
@@ -26,7 +26,7 @@ pub use radicle::node::Config;
use radicle::node::{Alias, Database, UserAgent, POLICIES_DB_FILE};
use radicle::node::{ConnectOptions, Handle as _};
use radicle::profile;
-
use radicle::profile::{env, Home, Profile};
+
use radicle::profile::{env, Home, networks::Network, Profile};
use radicle::rad;
use radicle::storage::{ReadStorage as _, RemoteRepository as _, SignRepository as _};
use radicle::test::fixtures;
@@ -518,8 +518,10 @@ where
    pub fn spawn(self) -> NodeHandle<G> {
        let listen = vec![([0, 0, 0, 0], 0).into()];
        let (_, signals) = chan::bounded(1);
+
        let network = Network::new("test");
        let rt = Runtime::init(
            self.home.clone(),
+
            &network,
            self.config,
            listen,
            signals,
modified crates/radicle-node/src/test/node.rs
@@ -27,7 +27,7 @@ use radicle::node::Event;
use radicle::node::{self, Alias};
use radicle::node::{ConnectOptions, Handle as _};
use radicle::node::{Database, POLICIES_DB_FILE};
-
use radicle::profile::{env, Home, Profile};
+
use radicle::profile::{env, networks::Network, Home, Profile};
use radicle::rad;
use radicle::storage::{ReadStorage as _, RemoteRepository as _, SignRepository as _};
use radicle::test::fixtures;
@@ -461,9 +461,11 @@ impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer<Signature> + Clone + Debug> Node<G
    pub fn spawn(self) -> NodeHandle<G> {
        let alias = self.config.alias.clone();
        let listen = vec![([0, 0, 0, 0], 0).into()];
+
        let network = Network::new("test");
        let (_, signals) = chan::bounded(1);
        let rt = Runtime::init(
            self.home.clone(),
+
            &network,
            self.config,
            listen,
            signals,
modified crates/radicle/src/node.rs
@@ -289,7 +289,7 @@ impl AsRef<str> for UserAgent {
}

/// Node alias, i.e. a short and memorable name for it.
-
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
+
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Alias(
modified crates/radicle/src/node/config.rs
@@ -15,102 +15,6 @@ use crate::node::{Address, Alias, NodeId};
/// Peer-to-peer protocol version.
pub type ProtocolVersion = u8;

-
/// Configured public seeds.
-
pub mod seeds {
-
    use std::{str::FromStr, 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<HostName>) -> Vec<ConnectAddress> {
-
        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<Vec<ConnectAddress>> = 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()),
-
                #[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<Vec<ConnectAddress>> = 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()),
-
                #[allow(clippy::unwrap_used)] // Value is manually verified.
-
                OnionAddrV3::from_str(
-
                    "rosarad5bxgdlgjnzzjygnsxrwxmoaj4vn7xinlstwglxvyt64jlnhyd.onion",
-
                )
-
                .unwrap()
-
                .into(),
-
            ],
-
        )
-
    });
-
}
-

-
/// Peer-to-peer network.
-
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
-
#[serde(rename_all = "camelCase")]
-
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
-
pub enum Network {
-
    #[default]
-
    Main,
-
    Test,
-
}
-

-
impl Network {
-
    /// Bootstrap nodes for this network.
-
    pub fn bootstrap(&self) -> Vec<(Alias, ProtocolVersion, Vec<ConnectAddress>)> {
-
        match self {
-
            Self::Main => [
-
                (
-
                    "iris.radicle.xyz",
-
                    seeds::RADICLE_NODE_BOOTSTRAP_IRIS.clone(),
-
                ),
-
                (
-
                    "rosa.radicle.xyz",
-
                    seeds::RADICLE_NODE_BOOTSTRAP_ROSA.clone(),
-
                ),
-
            ]
-
            .into_iter()
-
            .map(|(a, s)| (Alias::new(a), 1, s))
-
            .collect(),
-

-
            Self::Test => vec![],
-
        }
-
    }
-

-
    /// Public seeds for this network.
-
    pub fn public_seeds(&self) -> Vec<ConnectAddress> {
-
        match self {
-
            Self::Main => {
-
                let mut result = seeds::RADICLE_NODE_BOOTSTRAP_IRIS.clone();
-
                result.extend(seeds::RADICLE_NODE_BOOTSTRAP_ROSA.clone());
-
                result
-
            }
-
            Self::Test => vec![],
-
        }
-
    }
-
}
-

/// Configuration parameters defining attributes of minima and maxima.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default, rename_all = "camelCase")]
@@ -431,8 +335,7 @@ pub struct Config {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub onion: Option<AddressConfig>,
    /// Peer-to-peer network.
-
    #[serde(default)]
-
    pub network: Network,
+
    pub network: String,
    /// Log level.
    #[serde(default)]
    pub log: LogLevel,
@@ -464,7 +367,7 @@ pub struct Config {
impl Config {
    pub fn test(alias: Alias) -> Self {
        Self {
-
            network: Network::Test,
+
            network: "test".to_string(),
            ..Self::new(alias)
        }
    }
@@ -476,7 +379,7 @@ impl Config {
            listen: vec![],
            connect: HashSet::default(),
            external_addresses: vec![],
-
            network: Network::default(),
+
            network: "main".to_string(),
            proxy: None,
            onion: None,
            relay: Relay::default(),
@@ -653,6 +556,7 @@ mod test {

        let config: Config = serde_json::from_value(json!({
            "alias": "example",
+
            "network": "test",
            "limits": {
                "connection": {
                    "inbound": 1337,
@@ -669,6 +573,7 @@ mod test {

        let config: Config = serde_json::from_value(json!({
            "alias": "example",
+
            "network": "test",
            "limits": {
                "connection": {
                    "outbound": 1337,
modified crates/radicle/src/profile.rs
@@ -12,6 +12,7 @@
//!

pub mod config;
+
pub mod networks;
pub use config::{Config, ConfigPath, RawConfig, WriteError};

use std::collections::{BTreeMap, BTreeSet};
@@ -565,7 +566,13 @@ impl Home {
            path: dunce::canonicalize(path)?,
        };

-
        for dir in &[home.storage(), home.keys(), home.node(), home.cobs()] {
+
        for dir in &[
+
            home.storage(),
+
            home.keys(),
+
            home.node(),
+
            home.cobs(),
+
            home.networks(),
+
        ] {
            if !dir.exists() {
                fs::create_dir_all(dir)?;
            }
@@ -598,6 +605,10 @@ impl Home {
        self.path.join("cobs")
    }

+
    pub fn networks(&self) -> PathBuf {
+
        self.path.join("networks")
+
    }
+

    pub fn socket(&self) -> PathBuf {
        use env::RAD_SOCKET;

modified crates/radicle/src/profile/config.rs
@@ -155,7 +155,7 @@ impl Config {

        Self {
            public_explorer: Explorer::default(),
-
            preferred_seeds: node.network.public_seeds(),
+
            preferred_seeds: vec![],
            web: web::Config::default(),
            cli: cli::Config::default(),
            node,
added crates/radicle/src/profile/networks.rs
@@ -0,0 +1,89 @@
+
use std::collections::HashMap;
+
use std::fs;
+
use std::path::Path;
+

+
use crate::node::config::ConnectAddress;
+
use crate::node::Alias;
+
use crate::profile::Home;
+

+
#[derive(Debug, Default, Display, Hash, Eq, PartialEq)]
+
#[display(inner)]
+
pub struct NetworkName(String);
+

+
#[derive(Debug, thiserror::Error)]
+
pub enum NetworkLoadError {
+
    #[error("error parsing JSON: {0}")]
+
    Json(#[from] serde_json::Error),
+
    #[error("I/O error: {0}")]
+
    Io(#[from] std::io::Error),
+
}
+

+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
+
pub struct Network {
+
    #[serde(skip)]
+
    name: NetworkName,
+
    // A map node-alias -> node-address
+
    nodes: HashMap<Alias, ConnectAddress>,
+
    preferred_nodes: Vec<Alias>,
+
}
+

+
impl<T> From<T> for NetworkName
+
where
+
    T: Into<String>,
+
{
+
    fn from(name: T) -> NetworkName {
+
        NetworkName(name.into())
+
    }
+
}
+

+
impl Network {
+
    pub fn new(name: impl Into<NetworkName>) -> Network {
+
        Network {
+
            name: name.into(),
+
            nodes: HashMap::new(),
+
            preferred_nodes: vec![],
+
        }
+
    }
+

+
    pub fn add_node(&mut self, alias: Alias, addr: ConnectAddress) {
+
        self.nodes.insert(alias, addr);
+
    }
+

+
    pub fn nodes(&self) -> impl Iterator<Item = (&Alias, &ConnectAddress)> {
+
        self.nodes.iter()
+
    }
+

+
    pub fn name(&self) -> &NetworkName {
+
        &self.name
+
    }
+

+
    pub fn from_file(name: &str, path: impl AsRef<Path>) -> Result<Network, NetworkLoadError> {
+
        let file = fs::File::open(&path)?;
+
        let mut network: Network = serde_json::from_reader(file)?;
+
        network.name = name.into();
+
        Ok(network)
+
    }
+
}
+

+
#[derive(Debug)]
+
pub struct Networks(HashMap<NetworkName, Network>);
+

+
impl Networks {
+
    pub fn load(home: &Home) -> Result<Networks, NetworkLoadError> {
+
        let mut networks = HashMap::new();
+

+
        for entry in fs::read_dir(home.networks())? {
+
            let path = entry?.path();
+
            if let Some(name) = path.file_name().and_then(|x| x.to_str()) {
+
                let network = Network::from_file(name, &path)?;
+
                networks.insert(name.into(), network);
+
            }
+
        }
+

+
        Ok(Networks(networks))
+
    }
+

+
    pub fn get(&self, name: &NetworkName) -> Option<&Network> {
+
        self.0.get(name)
+
    }
+
}