use std::collections::HashSet; use std::net; use std::ops::Deref; use cyphernet::addr::PeerAddr; use localtime::LocalDuration; use serde_json as json; use crate::node; use crate::node::policy::{Scope, SeedingPolicy}; use crate::node::{Address, Alias, NodeId}; /// Peer-to-peer protocol version. 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 = 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 = 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 = 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 { 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")] pub struct Limits { /// Number of routing table entries before we start pruning. pub routing_max_size: usize, /// How long to keep a routing table entry before being pruned. #[serde(with = "crate::serde_ext::localtime::duration")] pub routing_max_age: LocalDuration, /// How long to keep a gossip message entry before pruning it. #[serde(with = "crate::serde_ext::localtime::duration")] pub gossip_max_age: LocalDuration, /// Maximum number of concurrent fetches per peer connection. pub fetch_concurrency: usize, /// Maximum number of open files. pub max_open_files: usize, /// Rate limitter settings. #[serde(default)] pub rate: RateLimits, /// Connection limits. #[serde(default)] pub connection: ConnectionLimits, } impl Default for Limits { fn default() -> Self { Self { routing_max_size: 1000, routing_max_age: LocalDuration::from_mins(7 * 24 * 60), // One week gossip_max_age: LocalDuration::from_mins(2 * 7 * 24 * 60), // Two weeks fetch_concurrency: 1, max_open_files: 4096, rate: RateLimits::default(), connection: ConnectionLimits::default(), } } } /// Connection limits. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct ConnectionLimits { /// Max inbound connections. pub inbound: usize, /// Max outbound connections. Note that this can be higher than the *target* number. pub outbound: usize, } impl Default for ConnectionLimits { fn default() -> Self { Self { inbound: 128, outbound: 16, } } } /// Rate limts for a single connection. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct RateLimit { pub fill_rate: f64, pub capacity: usize, } /// Rate limits for inbound and outbound connections. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct RateLimits { pub inbound: RateLimit, pub outbound: RateLimit, } impl Default for RateLimits { fn default() -> Self { Self { inbound: RateLimit { fill_rate: 5.0, capacity: 1024, }, outbound: RateLimit { fill_rate: 10.0, capacity: 2048, }, } } } /// Full address used to connect to a remote node. #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash)] #[serde(transparent)] pub struct ConnectAddress(#[serde(with = "crate::serde_ext::string")] PeerAddr); impl From> for ConnectAddress { fn from(value: PeerAddr) -> Self { Self(value) } } impl From for (NodeId, Address) { fn from(value: ConnectAddress) -> Self { (value.0.id, value.0.addr) } } impl From<(NodeId, Address)> for ConnectAddress { fn from((id, addr): (NodeId, Address)) -> Self { Self(PeerAddr { id, addr }) } } impl From for Address { fn from(value: ConnectAddress) -> Self { value.0.addr } } impl Deref for ConnectAddress { type Target = PeerAddr; fn deref(&self) -> &Self::Target { &self.0 } } /// Peer configuration. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase", tag = "type")] pub enum PeerConfig { /// Static peer set. Connect to the configured peers and maintain the connections. Static, /// Dynamic peer set. Dynamic, } impl Default for PeerConfig { fn default() -> Self { Self::Dynamic } } /// Relay configuration. #[derive(Debug, Copy, Clone, Default, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub enum Relay { /// Always relay messages. Always, /// Never relay messages. Never, /// Relay messages when applicable. #[default] Auto, } /// Proxy configuration. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase", tag = "mode")] pub enum AddressConfig { /// Proxy connections to this address type. Proxy { /// Proxy address. address: net::SocketAddr, }, /// Forward address to the next layer. Either this is the global proxy, /// or the operating system, via DNS. Forward, } /// Default seeding policy. Applies when no repository policies for the given repo are found. #[derive(Debug, Copy, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase", tag = "default")] pub enum DefaultSeedingPolicy { /// Allow seeding. Allow { /// Seeding scope. #[serde(default)] scope: Scope, }, /// Block seeding. #[default] Block, } impl DefaultSeedingPolicy { /// Is this an "allow" policy. pub fn is_allow(&self) -> bool { matches!(self, Self::Allow { .. }) } /// Seed everything from anyone. pub fn permissive() -> Self { Self::Allow { scope: Scope::All } } } impl From for SeedingPolicy { fn from(policy: DefaultSeedingPolicy) -> Self { match policy { DefaultSeedingPolicy::Block => Self::Block, DefaultSeedingPolicy::Allow { scope } => Self::Allow { scope }, } } } /// Service configuration. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct Config { /// Node alias. pub alias: Alias, /// Address to listen on. #[serde(default)] pub listen: Vec, /// Peer configuration. #[serde(default)] pub peers: PeerConfig, /// Peers to connect to on startup. /// Connections to these peers will be maintained. #[serde(default)] pub connect: HashSet, /// Specify the node's public addresses #[serde(default)] pub external_addresses: Vec
, /// Global proxy. #[serde(default, skip_serializing_if = "Option::is_none")] pub proxy: Option, /// Onion address config. #[serde(default, skip_serializing_if = "Option::is_none")] pub onion: Option, /// Peer-to-peer network. #[serde(default)] pub network: Network, /// Log level. #[serde(default = "defaults::log")] #[serde(with = "crate::serde_ext::string")] pub log: log::Level, /// Whether or not our node should relay messages. #[serde(default, deserialize_with = "crate::serde_ext::ok_or_default")] pub relay: Relay, /// Configured service limits. #[serde(default)] pub limits: Limits, /// Number of worker threads to spawn. #[serde(default = "defaults::workers")] pub workers: usize, /// Default seeding policy. #[serde(default)] pub seeding_policy: DefaultSeedingPolicy, /// Extra fields that aren't supported. #[serde(flatten, skip_serializing)] pub extra: json::Map, } impl Config { pub fn test(alias: Alias) -> Self { Self { network: Network::Test, ..Self::new(alias) } } pub fn new(alias: Alias) -> Self { Self { alias, peers: PeerConfig::default(), listen: vec![], connect: HashSet::default(), external_addresses: vec![], network: Network::default(), proxy: None, onion: None, relay: Relay::default(), limits: Limits::default(), workers: DEFAULT_WORKERS, log: defaults::log(), seeding_policy: DefaultSeedingPolicy::default(), extra: json::Map::default(), } } pub fn peer(&self, id: &NodeId) -> Option<&Address> { self.connect .iter() .find(|ca| &ca.id == id) .map(|ca| &ca.addr) } pub fn peers(&self) -> impl Iterator + '_ { self.connect.iter().cloned().map(|p| p.id) } pub fn is_persistent(&self, id: &NodeId) -> bool { self.peer(id).is_some() } /// Are we a relay node? This determines what we do with gossip messages from other peers. pub fn is_relay(&self) -> bool { match self.relay { // In "auto" mode, we only relay if we're a public seed node. // This reduces traffic for private nodes, as well as message redundancy. Relay::Auto => !self.external_addresses.is_empty(), Relay::Never => false, Relay::Always => true, } } pub fn features(&self) -> node::Features { node::Features::SEED } } /// Defaults as functions, for serde. mod defaults { /// Worker count. pub fn workers() -> usize { super::DEFAULT_WORKERS } /// Log level. pub fn log() -> log::Level { log::Level::Info } }