418 lines
12 KiB
Rust
418 lines
12 KiB
Rust
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<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")]
|
|
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<NodeId, Address>);
|
|
|
|
impl From<PeerAddr<NodeId, Address>> for ConnectAddress {
|
|
fn from(value: PeerAddr<NodeId, Address>) -> Self {
|
|
Self(value)
|
|
}
|
|
}
|
|
|
|
impl From<ConnectAddress> 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<ConnectAddress> for Address {
|
|
fn from(value: ConnectAddress) -> Self {
|
|
value.0.addr
|
|
}
|
|
}
|
|
|
|
impl Deref for ConnectAddress {
|
|
type Target = PeerAddr<NodeId, Address>;
|
|
|
|
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<DefaultSeedingPolicy> 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<net::SocketAddr>,
|
|
/// 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<ConnectAddress>,
|
|
/// Specify the node's public addresses
|
|
#[serde(default)]
|
|
pub external_addresses: Vec<Address>,
|
|
/// Global proxy.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub proxy: Option<net::SocketAddr>,
|
|
/// Onion address config.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub onion: Option<AddressConfig>,
|
|
/// 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<String, json::Value>,
|
|
}
|
|
|
|
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<Item = NodeId> + '_ {
|
|
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
|
|
}
|
|
}
|