Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
Remove predefined bootstrap nodes
Open did:key:z6MkmiJD...V5sS opened 1 year ago

Instead, expect a “network” field that names a network file that must exist under $RAD_HOME/networks.

A network file is a JSON file containing nodes and preferred nodes, e.g.

  {
    "nodes": {
        "seed.radicle.xyz": "z6MksmpU5b1dS7oaqF2bHXhQi1DWy2hB7Mh9CuN7y1DN6QSz@seed.radicle.xyz:8776",
        "iris.radicle.xyz": "z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@iris.radicle.xyz:8776",
        "rosa.radicle.xyz": "z6Mkmqogy2qEM2ummccUthFEaaHvyYmYBYh3dbe9W4ebScxo@rosa.radicle.xyz:8776"
    },
    "preferredNodes": ["seed.radicle.xyz"]
  }
13 files changed +138 -108 f5fa84fa 7322e3ac
added networks/main
@@ -0,0 +1,8 @@
+
{
+
    "nodes": {
+
        "seed.radicle.xyz": "z6MksmpU5b1dS7oaqF2bHXhQi1DWy2hB7Mh9CuN7y1DN6QSz@seed.radicle.xyz:8776",
+
        "seed.radicle.garden", "z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@seed.radicle.garden:8776",
+
        "ash.radicle.garden", "z6Mkmqogy2qEM2ummccUthFEaaHvyYmYBYh3dbe9W4ebScxo@ash.radicle.garden:8776"
+
    },
+
    "preferredNodes": ["seed.radicle.garden"]
+
}
modified radicle-cli/examples/rad-config.md
@@ -5,10 +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@seed.radicle.garden:8776",
-
    "z6MksmpU5b1dS7oaqF2bHXhQi1DWy2hB7Mh9CuN7y1DN6QSz@seed.radicle.xyz:8776"
-
  ],
+
  "preferredSeeds": [],
  "web": {
    "pinned": {
      "repositories": []
@@ -64,8 +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@seed.radicle.garden:8776
-
z6MksmpU5b1dS7oaqF2bHXhQi1DWy2hB7Mh9CuN7y1DN6QSz@seed.radicle.xyz:8776
$ rad config get node.limits.routingMaxSize
1000
```
modified radicle-cli/tests/commands.rs
@@ -5,7 +5,6 @@ use std::{net, thread, time};
use radicle::git;
use radicle::node;
use radicle::node::address::Store as _;
-
use radicle::node::config::seeds::{RADICLE_COMMUNITY_NODE, RADICLE_TEAM_NODE};
use radicle::node::config::DefaultSeedingPolicy;
use radicle::node::routing::Store as _;
use radicle::node::Handle as _;
@@ -30,7 +29,7 @@ const RAD_SEED: &str = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff

mod config {
    use super::*;
-
    use radicle::node::config::{Config, Limits, Network, RateLimit, RateLimits};
+
    use radicle::node::config::{Config, Limits, RateLimit, RateLimits};
    use radicle::profile;

    /// Configuration for a test seed node.
@@ -41,7 +40,7 @@ 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 {
@@ -413,10 +412,7 @@ fn rad_inspect() {
fn rad_config() {
    let mut environment = Environment::new();
    let alias = Alias::new("alice");
-
    let profile = environment.profile(profile::Config {
-
        preferred_seeds: vec![RADICLE_COMMUNITY_NODE.clone(), RADICLE_TEAM_NODE.clone()],
-
        ..profile::Config::new(alias)
-
    });
+
    let profile = environment.profile(profile::Config::new(alias));
    let working = tempfile::tempdir().unwrap();

    test(
modified radicle-node/src/lib.rs
@@ -39,7 +39,7 @@ pub mod prelude {
    pub use crate::identity::{Did, RepoId};
    pub use crate::node::Address;
    pub use crate::service::filter::Filter;
-
    pub use crate::service::{DisconnectReason, Event, Message, Network, NodeId};
+
    pub use crate::service::{DisconnectReason, Event, Message, NodeId};
    pub use crate::storage::refs::Refs;
    pub use crate::storage::WriteStorage;
    pub use crate::{LocalDuration, LocalTime, Timestamp};
modified radicle-node/src/main.rs
@@ -1,14 +1,14 @@
use std::io;
use std::{env, fs, net, path::PathBuf, process};

-
use anyhow::Context;
+
use anyhow::{bail, Context};
use crossbeam_channel as chan;

use radicle::logger;
use radicle::prelude::Signer;
use radicle::profile;
use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner};
-
use radicle_node::{Runtime, VERSION};
+
use radicle_node::{runtime, Runtime, VERSION};
use radicle_signals as signals;

pub const HELP_MSG: &str = r#"
@@ -93,6 +93,11 @@ fn execute() -> anyhow::Result<()> {

    logger::init(options.log.unwrap_or(config.node.log))?;

+
    let networks = profile::Networks::load(&home)?;
+
    let Some(network) = networks.get(&config.node.network.to_owned().into()) else {
+
        bail!(runtime::Error::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..");
@@ -123,7 +128,7 @@ fn execute() -> anyhow::Result<()> {
        log::debug!(target: "node", "Removing existing control socket..");
        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 radicle-node/src/runtime.rs
@@ -21,7 +21,7 @@ use radicle::node::address::Store as _;
use radicle::node::notifications;
use radicle::node::Handle as _;
use radicle::node::UserAgent;
-
use radicle::profile::Home;
+
use radicle::profile::{Home, Network};
use radicle::{cob, git, storage, Storage};

use crate::control;
@@ -87,6 +87,9 @@ pub enum Error {
    /// A git version error.
    #[error("git version error: {0}")]
    GitVersion(#[from] git::VersionError),
+
    /// An unknown network was referenced.
+
    #[error("unknown network: {0}")]
+
    UnknownNetwork(String),
}

/// Wraps a [`UnixListener`] but tracks its origin.
@@ -116,6 +119,7 @@ impl Runtime {
    /// This function spawns threads.
    pub fn init<G>(
        home: Home,
+
        network: &Network,
        config: service::Config,
        listen: Vec<net::SocketAddr>,
        signals: chan::Receiver<Signal>,
@@ -127,7 +131,6 @@ impl Runtime {
        let id = *signer.public_key();
        let alias = config.alias.clone();
        let node_dir = home.node();
-
        let network = config.network;
        let rng = fastrand::Rng::new();
        let clock = LocalTime::now();
        let timestamp = clock.into();
@@ -155,7 +158,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 = if let Some(ann) = fs::read(node_dir.join(node::NODE_ANNOUNCEMENT_FILE))
            .ok()
@@ -210,14 +213,15 @@ 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();
+
            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,
+
                    alias,
                    0,
                    &UserAgent::default(),
                    clock.into(),
modified radicle-node/src/service.rs
@@ -59,7 +59,7 @@ use crate::Link;
use crate::{crypto, PROTOCOL_VERSION};

pub use crate::node::events::{Event, Events};
-
pub use crate::node::{config::Network, Config, NodeId};
+
pub use crate::node::{Config, NodeId};
pub use crate::service::message::{Message, ZeroBytes};
pub use crate::service::session::{QueuedFetch, Session};

modified radicle-node/src/test/environment.rs
@@ -22,7 +22,7 @@ use radicle::node::seed::Store as _;
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, Network, Profile};
use radicle::rad;
use radicle::storage::{ReadStorage as _, RemoteRepository as _, SignRepository as _};
use radicle::test::fixtures;
@@ -512,8 +512,10 @@ impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer + Clone> Node<G> {
    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 radicle/src/node.rs
@@ -291,7 +291,9 @@ impl AsRef<str> for UserAgent {
}

/// Node alias.
-
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, serde::Serialize, serde::Deserialize)]
+
#[derive(
+
    Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, serde::Serialize, serde::Deserialize,
+
)]
#[serde(try_from = "String", into = "String")]
pub struct Alias(String);

modified radicle/src/node/config.rs
@@ -17,82 +17,6 @@ pub type ProtocolVersion = u8;
/// Default number of workers to spawn.
pub const DEFAULT_WORKERS: usize = 8;

-
/// Configured public seeds.
-
pub mod seeds {
-
    use std::str::FromStr;
-

-
    use super::{ConnectAddress, PeerAddr};
-
    use once_cell::sync::Lazy;
-

-
    /// The radicle public community seed node.
-
    pub static RADICLE_COMMUNITY_NODE: Lazy<ConnectAddress> = Lazy::new(|| {
-
        // SAFETY: `ConnectAddress` is known at compile time.
-
        #[allow(clippy::unwrap_used)]
-
        PeerAddr::from_str(
-
            "z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@seed.radicle.garden:8776",
-
        )
-
        .unwrap()
-
        .into()
-
    });
-

-
    /// The radicle public `ash` seed node.
-
    pub static RADICLE_ASH_NODE: Lazy<ConnectAddress> = Lazy::new(|| {
-
        // SAFETY: `ConnectAddress` is known at compile time.
-
        #[allow(clippy::unwrap_used)]
-
        PeerAddr::from_str(
-
            "z6Mkmqogy2qEM2ummccUthFEaaHvyYmYBYh3dbe9W4ebScxo@ash.radicle.garden:8776",
-
        )
-
        .unwrap()
-
        .into()
-
    });
-

-
    /// The radicle team node.
-
    pub static RADICLE_TEAM_NODE: Lazy<ConnectAddress> = Lazy::new(|| {
-
        // SAFETY: `ConnectAddress` is known at compile time.
-
        #[allow(clippy::unwrap_used)]
-
        PeerAddr::from_str("z6MksmpU5b1dS7oaqF2bHXhQi1DWy2hB7Mh9CuN7y1DN6QSz@seed.radicle.xyz:8776")
-
            .unwrap()
-
            .into()
-
    });
-
}
-

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

-
impl Network {
-
    /// Bootstrap nodes for this network.
-
    pub fn bootstrap(&self) -> Vec<(Alias, ProtocolVersion, ConnectAddress)> {
-
        match self {
-
            Self::Main => [
-
                ("seed.radicle.garden", seeds::RADICLE_COMMUNITY_NODE.clone()),
-
                ("seed.radicle.xyz", seeds::RADICLE_TEAM_NODE.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 => vec![
-
                seeds::RADICLE_COMMUNITY_NODE.clone(),
-
                seeds::RADICLE_ASH_NODE.clone(),
-
            ],
-
            Self::Test => vec![],
-
        }
-
    }
-
}
-

/// Configuration parameters defining attributes of minima and maxima.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -404,8 +328,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 = "defaults::log")]
    #[serde(with = "crate::serde_ext::string")]
@@ -430,7 +353,7 @@ pub struct Config {
impl Config {
    pub fn test(alias: Alias) -> Self {
        Self {
-
            network: Network::Test,
+
            network: "test".to_string(),
            ..Self::new(alias)
        }
    }
@@ -442,7 +365,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(),
modified radicle/src/profile.rs
@@ -13,6 +13,8 @@

pub mod config;
pub use config::{Config, ConfigError, ConfigPath, RawConfig};
+
pub mod networks;
+
pub use networks::{Network, Networks};

use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
@@ -217,6 +219,7 @@ impl Profile {
                key: public_key,
            },
        )?;
+

        // Create DBs.
        home.policies_mut()?;
        home.notifications_mut()?;
@@ -494,7 +497,13 @@ impl Home {
            path: path.canonicalize()?,
        };

-
        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)?;
            }
@@ -527,6 +536,10 @@ impl Home {
        self.path.join("cobs")
    }

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

    pub fn socket(&self) -> PathBuf {
        env::var_os(env::RAD_SOCKET)
            .map(PathBuf::from)
modified radicle/src/profile/config.rs
@@ -50,7 +50,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 radicle/src/profile/networks.rs
@@ -0,0 +1,82 @@
+
use std::collections::HashMap;
+
use std::fs;
+
use std::path::Path;
+

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

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

+
#[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, ConfigError> {
+
        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, ConfigError> {
+
        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)
+
    }
+
}