use std::{env, net, process, thread};
use anyhow::Context as _;
use cyphernet::addr::PeerAddr;
use nakamoto_net::{LocalDuration, LocalTime};
use reactor::poller::popol;
use reactor::Reactor;
use radicle::profile;
use radicle_node::client::handle::Handle;
use radicle_node::client::{ADDRESS_DB_FILE, NODE_DIR, ROUTING_DB_FILE, TRACKING_DB_FILE};
use radicle_node::crypto::ssh::keystore::MemorySigner;
use radicle_node::prelude::{Address, NodeId};
use radicle_node::service::{routing, tracking};
use radicle_node::wire::Transport;
use radicle_node::{address, control, logger, service};
#[derive(Debug)]
struct Options {
connect: Vec<(NodeId, Address)>,
external_addresses: Vec
,
limits: service::config::Limits,
// FIXME(cloudhead): Listen on incoming connections.
#[allow(dead_code)]
listen: Vec,
}
impl Options {
fn from_env() -> Result {
use lexopt::prelude::*;
let mut parser = lexopt::Parser::from_env();
let mut connect = Vec::new();
let mut external_addresses = Vec::new();
let mut limits = service::config::Limits::default();
let mut listen = Vec::new();
while let Some(arg) = parser.next()? {
match arg {
Long("connect") => {
let peer: PeerAddr = parser.value()?.parse()?;
connect.push((*peer.id(), peer.addr().clone()));
}
Long("external-address") => {
let addr = parser.value()?.parse()?;
external_addresses.push(addr);
}
Long("limit-routing-max-age") => {
let secs: u64 = parser.value()?.parse()?;
limits.routing_max_age = LocalDuration::from_secs(secs);
}
Long("limit-routing-max-size") => {
limits.routing_max_size = parser.value()?.parse()?;
}
Long("listen") => {
let addr = parser.value()?.parse()?;
listen.push(addr);
}
Long("help") => {
println!("usage: radicle-node [--connect ]..");
process::exit(0);
}
_ => anyhow::bail!(arg.unexpected()),
}
}
if external_addresses.len() > service::ADDRESS_LIMIT {
anyhow::bail!(
"external address limit ({}) exceeded",
service::ADDRESS_LIMIT,
)
}
Ok(Self {
connect,
external_addresses,
limits,
listen,
})
}
}
fn main() -> anyhow::Result<()> {
logger::init(log::Level::Debug)?;
let options = Options::from_env()?;
let profile = radicle::Profile::load().context("Failed to load node profile")?;
let node = profile.node();
let passphrase = env::var(profile::env::RAD_PASSPHRASE)
.context("`RAD_PASSPHRASE` is required to be set for the node to establish connections")?
.into();
let signer = MemorySigner::load(&profile.keystore, passphrase)?;
let negotiator = signer.clone();
let config = service::Config {
connect: options.connect.into_iter().collect(),
external_addresses: options.external_addresses,
limits: options.limits,
..service::Config::default()
};
let proxy_addr = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 9050);
let network = config.network;
let rng = fastrand::Rng::new();
let clock = LocalTime::now();
let storage = profile.storage;
let node_dir = profile.home.join(NODE_DIR);
let address_db = node_dir.join(ADDRESS_DB_FILE);
let routing_db = node_dir.join(ROUTING_DB_FILE);
let tracking_db = node_dir.join(TRACKING_DB_FILE);
log::info!("Opening address book {}..", address_db.display());
let addresses = address::Book::open(address_db)?;
log::info!("Opening routing table {}..", routing_db.display());
let routing = routing::Table::open(routing_db)?;
log::info!("Opening tracking policy table {}..", tracking_db.display());
let tracking = tracking::Config::open(tracking_db)?;
log::info!("Initializing service ({:?})..", network);
let service = service::Service::new(
config, clock, routing, storage, addresses, tracking, signer, rng,
);
let wire = Transport::new(service, negotiator, proxy_addr, clock);
let reactor = Reactor::new(wire, popol::Poller::new());
let handle = Handle::from(reactor.controller());
let control = thread::spawn(move || control::listen(node, handle));
control.join().unwrap()?;
reactor.join().unwrap();
Ok(())
}