Files
nym/mixnode/src/node/mod.rs
T

304 lines
11 KiB
Rust

// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::Config;
use crate::error::MixnodeError;
use crate::node::helpers::{load_identity_keys, load_sphinx_keys};
use crate::node::http::HttpApiBuilder;
use crate::node::listener::connection_handler::packet_processing::PacketProcessor;
use crate::node::listener::connection_handler::ConnectionHandler;
use crate::node::listener::Listener;
use crate::node::node_description::NodeDescription;
use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSender};
use log::{error, info, warn};
use nym_bin_common::output_format::OutputFormat;
use nym_crypto::asymmetric::{encryption, identity};
use nym_mixnode_common::verloc;
use nym_mixnode_common::verloc::VerlocMeasurer;
use nym_node_http_api::state::metrics::{SharedMixingStats, SharedVerlocStats};
use nym_task::{TaskClient, TaskHandle};
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::net::SocketAddr;
use std::process;
use std::sync::Arc;
pub mod helpers;
mod http;
mod listener;
pub mod node_description;
mod node_statistics;
mod packet_delayforwarder;
// the MixNode will live for whole duration of this program
pub struct MixNode {
config: Config,
descriptor: NodeDescription,
identity_keypair: Arc<identity::KeyPair>,
sphinx_keypair: Arc<encryption::KeyPair>,
run_http_server: bool,
task_client: Option<TaskClient>,
mixing_stats: Option<SharedMixingStats>,
verloc_stats: Option<SharedVerlocStats>,
}
impl MixNode {
pub fn new(config: Config) -> Result<Self, MixnodeError> {
Ok(MixNode {
run_http_server: true,
descriptor: Self::load_node_description(&config),
identity_keypair: Arc::new(load_identity_keys(&config)?),
sphinx_keypair: Arc::new(load_sphinx_keys(&config)?),
config,
task_client: None,
mixing_stats: None,
verloc_stats: None,
})
}
pub fn new_loaded(
config: Config,
descriptor: NodeDescription,
identity_keypair: Arc<identity::KeyPair>,
sphinx_keypair: Arc<encryption::KeyPair>,
) -> Self {
MixNode {
run_http_server: true,
task_client: None,
config,
descriptor,
identity_keypair,
sphinx_keypair,
mixing_stats: None,
verloc_stats: None,
}
}
pub fn disable_http_server(&mut self) {
self.run_http_server = false
}
pub fn set_task_client(&mut self, task_client: TaskClient) {
self.task_client = Some(task_client)
}
pub fn set_mixing_stats(&mut self, mixing_stats: SharedMixingStats) {
self.mixing_stats = Some(mixing_stats);
}
pub fn set_verloc_stats(&mut self, verloc_stats: SharedVerlocStats) {
self.verloc_stats = Some(verloc_stats)
}
fn load_node_description(config: &Config) -> NodeDescription {
NodeDescription::load_from_file(&config.storage_paths.node_description).unwrap_or_default()
}
/// Prints relevant node details to the console
pub fn print_node_details(&self, output: OutputFormat) {
let node_details = nym_types::mixnode::MixnodeNodeDetailsResponse {
identity_key: self.identity_keypair.public_key().to_base58_string(),
sphinx_key: self.sphinx_keypair.public_key().to_base58_string(),
bind_address: self.config.mixnode.listening_address,
version: self.config.mixnode.version.clone(),
mix_port: self.config.mixnode.mix_port,
http_api_port: self.config.http.bind_address.port(),
verloc_port: self.config.mixnode.verloc_port,
};
println!("{}", output.format(&node_details));
}
fn start_http_api(
&self,
atomic_verloc_result: SharedVerlocStats,
node_stats_pointer: SharedMixingStats,
metrics_key: Option<&String>,
task_client: TaskClient,
) -> Result<(), MixnodeError> {
HttpApiBuilder::new(&self.config, &self.identity_keypair, &self.sphinx_keypair)
.with_verloc(atomic_verloc_result)
.with_mixing_stats(node_stats_pointer)
.with_metrics_key(metrics_key)
.with_descriptor(self.descriptor.clone())
.start(task_client)
}
fn start_node_stats_controller(
&mut self,
shutdown: TaskClient,
) -> (SharedMixingStats, node_statistics::UpdateSender) {
info!("Starting node stats controller...");
let mixing_stats = self.mixing_stats.take().unwrap_or_default();
let controller = node_statistics::Controller::new(
self.config.debug.node_stats_logging_delay,
self.config.debug.node_stats_updating_delay,
mixing_stats.clone(),
shutdown,
);
let update_sender = controller.start();
(mixing_stats, update_sender)
}
fn start_socket_listener(
&self,
node_stats_update_sender: node_statistics::UpdateSender,
delay_forwarding_channel: PacketDelayForwardSender,
shutdown: TaskClient,
) {
info!("Starting socket listener...");
let packet_processor =
PacketProcessor::new(self.sphinx_keypair.private_key(), node_stats_update_sender);
let connection_handler = ConnectionHandler::new(packet_processor, delay_forwarding_channel);
let listening_address = SocketAddr::new(
self.config.mixnode.listening_address,
self.config.mixnode.mix_port,
);
Listener::new(listening_address, shutdown).start(connection_handler);
}
fn start_packet_delay_forwarder(
&mut self,
node_stats_update_sender: node_statistics::UpdateSender,
shutdown: TaskClient,
) -> PacketDelayForwardSender {
info!("Starting packet delay-forwarder...");
let client_config = nym_mixnet_client::Config::new(
self.config.debug.packet_forwarding_initial_backoff,
self.config.debug.packet_forwarding_maximum_backoff,
self.config.debug.initial_connection_timeout,
self.config.debug.maximum_connection_buffer_size,
self.config.debug.use_legacy_framed_packet_version,
);
let mut packet_forwarder = DelayForwarder::new(
nym_mixnet_client::Client::new(client_config),
node_stats_update_sender,
shutdown,
);
let packet_sender = packet_forwarder.sender();
tokio::spawn(async move { packet_forwarder.run().await });
packet_sender
}
fn start_verloc_measurements(&mut self, shutdown: TaskClient) -> SharedVerlocStats {
info!("Starting the round-trip-time measurer...");
// use the same binding address with the HARDCODED port for time being (I don't like that approach personally)
let listening_address = SocketAddr::new(
self.config.mixnode.listening_address,
self.config.mixnode.verloc_port,
);
let config = verloc::ConfigBuilder::new()
.listening_address(listening_address)
.packets_per_node(self.config.verloc.packets_per_node)
.connection_timeout(self.config.verloc.connection_timeout)
.packet_timeout(self.config.verloc.packet_timeout)
.delay_between_packets(self.config.verloc.delay_between_packets)
.tested_nodes_batch_size(self.config.verloc.tested_nodes_batch_size)
.testing_interval(self.config.verloc.testing_interval)
.retry_timeout(self.config.verloc.retry_timeout)
.nym_api_urls(self.config.get_nym_api_endpoints())
.build();
let verloc_state = self.verloc_stats.take().unwrap_or_default();
let mut verloc_measurer =
VerlocMeasurer::new(config, Arc::clone(&self.identity_keypair), shutdown);
verloc_measurer.set_shared_state(verloc_state.clone());
tokio::spawn(async move { verloc_measurer.run().await });
verloc_state
}
fn random_api_client(&self) -> nym_validator_client::NymApiClient {
let endpoints = self.config.get_nym_api_endpoints();
let nym_api = endpoints
.choose(&mut thread_rng())
.expect("The list of validator apis is empty");
nym_validator_client::NymApiClient::new(nym_api.clone())
}
async fn check_if_bonded(&self) -> bool {
// TODO: if anything, this should be getting data directly from the contract
// as opposed to the validator API
let validator_client = self.random_api_client();
let existing_nodes = match validator_client.get_all_basic_nodes(None).await {
Ok(nodes) => nodes,
Err(err) => {
error!(
"failed to grab initial network mixnodes - {err}\n \
Please try to startup again in few minutes",
);
process::exit(1);
}
};
existing_nodes
.iter()
.any(|node| &node.ed25519_identity_pubkey == self.identity_keypair.public_key())
}
async fn wait_for_interrupt(&self, shutdown: TaskHandle) {
let _res = shutdown.wait_for_shutdown().await;
log::info!("Stopping nym mixnode");
}
pub async fn run(&mut self) -> Result<(), MixnodeError> {
info!("Starting nym mixnode");
if self.check_if_bonded().await {
warn!("You seem to have bonded your mixnode before starting it - that's highly unrecommended as in the future it might result in slashing");
}
// Shutdown notifier for signalling tasks to stop
let shutdown = self
.task_client
.take()
.map(Into::<TaskHandle>::into)
.unwrap_or_default()
.name_if_unnamed("mixnode");
let (node_stats_pointer, node_stats_update_sender) =
self.start_node_stats_controller(shutdown.fork("node_statistics::Controller"));
let delay_forwarding_channel = self.start_packet_delay_forwarder(
node_stats_update_sender.clone(),
shutdown.fork("DelayForwarder"),
);
self.start_socket_listener(
node_stats_update_sender,
delay_forwarding_channel,
shutdown.fork("Listener"),
);
let atomic_verloc_results = self.start_verloc_measurements(shutdown.fork("VerlocMeasurer"));
// Rocket handles shutdown on it's own, but its shutdown handling should be incorporated
// with that of the rest of the tasks.
// Currently it's runtime is forcefully terminated once the mixnode exits.
if self.run_http_server {
self.start_http_api(
atomic_verloc_results,
node_stats_pointer,
self.config.metrics_key(),
shutdown.fork("http-api"),
)?;
}
info!("Finished nym mixnode startup procedure - it should now be able to receive mix traffic!");
self.wait_for_interrupt(shutdown).await;
Ok(())
}
}