Feature/one binary to rule them all (#4500)
This commit is contained in:
committed by
GitHub
parent
fa1e9988b3
commit
4c5351ba60
@@ -34,7 +34,7 @@ where
|
||||
}
|
||||
|
||||
/// Loads identity keys stored on disk
|
||||
pub(crate) fn load_identity_keys(config: &Config) -> Result<identity::KeyPair, MixnodeError> {
|
||||
pub fn load_identity_keys(config: &Config) -> Result<identity::KeyPair, MixnodeError> {
|
||||
let identity_paths = KeyPairPath::new(
|
||||
config.storage_paths.keys.private_identity_key(),
|
||||
config.storage_paths.keys.public_identity_key(),
|
||||
@@ -43,7 +43,7 @@ pub(crate) fn load_identity_keys(config: &Config) -> Result<identity::KeyPair, M
|
||||
}
|
||||
|
||||
/// Loads Sphinx keys stored on disk
|
||||
pub(crate) fn load_sphinx_keys(config: &Config) -> Result<encryption::KeyPair, MixnodeError> {
|
||||
pub fn load_sphinx_keys(config: &Config) -> Result<encryption::KeyPair, MixnodeError> {
|
||||
let sphinx_paths = KeyPairPath::new(
|
||||
config.storage_paths.keys.private_encryption_key(),
|
||||
config.storage_paths.keys.public_encryption_key(),
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::node::node_description::NodeDescription;
|
||||
use axum::extract::Query;
|
||||
use nym_node::http::api::{FormattedResponse, OutputParams};
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
|
||||
/// Returns a description of the node and why someone might want to delegate stake to it.
|
||||
pub(crate) async fn description(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use axum::extract::Query;
|
||||
use cupid::TopologyType;
|
||||
use nym_node::http::api::{FormattedResponse, OutputParams};
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use serde::Serialize;
|
||||
use sysinfo::{System, SystemExt};
|
||||
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::http::legacy::verloc::VerlocState;
|
||||
use crate::node::node_statistics::SharedNodeStats;
|
||||
use axum::extract::FromRef;
|
||||
use nym_node_http_api::state::metrics::{SharedMixingStats, SharedVerlocStats};
|
||||
|
||||
// this is a temporary thing for the transition period
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct MixnodeAppState {
|
||||
pub(crate) verloc: VerlocState,
|
||||
pub(crate) stats: SharedNodeStats,
|
||||
pub(crate) verloc: SharedVerlocStats,
|
||||
pub(crate) stats: SharedMixingStats,
|
||||
pub(crate) metrics_key: Option<String>,
|
||||
}
|
||||
|
||||
impl FromRef<MixnodeAppState> for VerlocState {
|
||||
impl FromRef<MixnodeAppState> for SharedVerlocStats {
|
||||
fn from_ref(app_state: &MixnodeAppState) -> Self {
|
||||
app_state.verloc.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<MixnodeAppState> for SharedNodeStats {
|
||||
impl FromRef<MixnodeAppState> for SharedMixingStats {
|
||||
fn from_ref(app_state: &MixnodeAppState) -> Self {
|
||||
app_state.stats.clone()
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::node_statistics::{NodeStatsSimple, SharedNodeStats};
|
||||
|
||||
use axum::{
|
||||
extract::{Query, State},
|
||||
http::HeaderMap,
|
||||
};
|
||||
use nym_http_api_common::{FormattedResponse, Output};
|
||||
use nym_metrics::metrics;
|
||||
use nym_node::http::api::{FormattedResponse, Output};
|
||||
use nym_node_http_api::api::api_requests::v1::metrics::models::MixingStats;
|
||||
use nym_node_http_api::state::metrics::SharedMixingStats;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::state::MixnodeAppState;
|
||||
@@ -17,7 +17,7 @@ use super::state::MixnodeAppState;
|
||||
#[serde(untagged)]
|
||||
pub enum NodeStatsResponse {
|
||||
Full(String),
|
||||
Simple(NodeStatsSimple),
|
||||
Simple(MixingStats),
|
||||
}
|
||||
|
||||
pub(crate) async fn metrics(State(state): State<MixnodeAppState>, headers: HeaderMap) -> String {
|
||||
@@ -38,7 +38,7 @@ pub(crate) async fn metrics(State(state): State<MixnodeAppState>, headers: Heade
|
||||
|
||||
pub(crate) async fn stats(
|
||||
Query(params): Query<StatsQueryParams>,
|
||||
State(stats): State<SharedNodeStats>,
|
||||
State(stats): State<SharedMixingStats>,
|
||||
) -> MixnodeStatsResponse {
|
||||
let output = params.output.unwrap_or_default();
|
||||
|
||||
@@ -47,12 +47,11 @@ pub(crate) async fn stats(
|
||||
output.to_response(response)
|
||||
}
|
||||
|
||||
async fn generate_stats(full: bool, stats: SharedNodeStats) -> NodeStatsResponse {
|
||||
let snapshot_data = stats.clone_data().await;
|
||||
async fn generate_stats(full: bool, stats: SharedMixingStats) -> NodeStatsResponse {
|
||||
if full {
|
||||
NodeStatsResponse::Full(metrics!())
|
||||
} else {
|
||||
NodeStatsResponse::Simple(snapshot_data.simplify())
|
||||
NodeStatsResponse::Simple(stats.read().await.as_response())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,30 +2,18 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use axum::extract::{Query, State};
|
||||
use nym_mixnode_common::verloc::{AtomicVerlocResult, VerlocResult};
|
||||
use nym_node::http::api::{FormattedResponse, OutputParams};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct VerlocState {
|
||||
shared: AtomicVerlocResult,
|
||||
}
|
||||
|
||||
impl VerlocState {
|
||||
pub fn new(atomic_verloc_result: AtomicVerlocResult) -> Self {
|
||||
VerlocState {
|
||||
shared: atomic_verloc_result,
|
||||
}
|
||||
}
|
||||
}
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_node_http_api::api::api_requests::v1::metrics::models::VerlocResultData;
|
||||
use nym_node_http_api::state::metrics::SharedVerlocStats;
|
||||
|
||||
/// Provides verifiable location (verloc) measurements for this mixnode - a list of the
|
||||
/// round-trip times, in milliseconds, for all other mixnodes that this node knows about.
|
||||
pub(crate) async fn verloc(
|
||||
State(verloc): State<VerlocState>,
|
||||
State(verloc): State<SharedVerlocStats>,
|
||||
Query(output): Query<OutputParams>,
|
||||
) -> MixnodeVerlocResponse {
|
||||
let output = output.output.unwrap_or_default();
|
||||
output.to_response(verloc.shared.clone_data().await)
|
||||
output.to_response(verloc.read().await.current_run_data.clone())
|
||||
}
|
||||
|
||||
pub type MixnodeVerlocResponse = FormattedResponse<VerlocResult>;
|
||||
pub type MixnodeVerlocResponse = FormattedResponse<VerlocResultData>;
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::MixnodeError;
|
||||
use crate::node::http::legacy::verloc::VerlocState;
|
||||
use crate::node::node_description::NodeDescription;
|
||||
use crate::node::node_statistics::SharedNodeStats;
|
||||
use log::info;
|
||||
use nym_bin_common::bin_info_owned;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_node::error::NymNodeError;
|
||||
use nym_node::http::api::api_requests;
|
||||
use nym_node::http::api::api_requests::SignedHostInformation;
|
||||
use nym_node_http_api::api::api_requests;
|
||||
use nym_node_http_api::api::api_requests::SignedHostInformation;
|
||||
use nym_node_http_api::state::metrics::{SharedMixingStats, SharedVerlocStats};
|
||||
use nym_node_http_api::NymNodeHttpError;
|
||||
use nym_task::TaskClient;
|
||||
|
||||
pub(crate) mod legacy;
|
||||
@@ -25,13 +24,14 @@ fn load_host_details(
|
||||
ip_address: config.host.public_ips.clone(),
|
||||
hostname: config.host.hostname.clone(),
|
||||
keys: api_requests::v1::node::models::HostKeys {
|
||||
ed25519: identity_keypair.public_key().to_base58_string(),
|
||||
x25519: sphinx_key.to_base58_string(),
|
||||
ed25519_identity: identity_keypair.public_key().to_base58_string(),
|
||||
x25519_sphinx: sphinx_key.to_base58_string(),
|
||||
x25519_noise: "".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let signed_info = SignedHostInformation::new(host_info, identity_keypair.private_key())
|
||||
.map_err(NymNodeError::from)?;
|
||||
.map_err(NymNodeHttpError::from)?;
|
||||
Ok(signed_info)
|
||||
}
|
||||
|
||||
@@ -71,13 +71,13 @@ impl<'a> HttpApiBuilder<'a> {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn with_verloc(mut self, verloc: VerlocState) -> Self {
|
||||
pub(crate) fn with_verloc(mut self, verloc: SharedVerlocStats) -> Self {
|
||||
self.legacy_mixnode.verloc = verloc;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn with_mixing_stats(mut self, stats: SharedNodeStats) -> Self {
|
||||
pub(crate) fn with_mixing_stats(mut self, stats: SharedMixingStats) -> Self {
|
||||
self.legacy_mixnode.stats = stats;
|
||||
self
|
||||
}
|
||||
@@ -92,7 +92,7 @@ impl<'a> HttpApiBuilder<'a> {
|
||||
let bind_address = self.mixnode_config.http.bind_address;
|
||||
info!("Starting HTTP API on http://{bind_address}",);
|
||||
|
||||
let config = nym_node::http::Config::new(
|
||||
let config = nym_node_http_api::Config::new(
|
||||
bin_info_owned!(),
|
||||
load_host_details(
|
||||
self.mixnode_config,
|
||||
@@ -103,7 +103,7 @@ impl<'a> HttpApiBuilder<'a> {
|
||||
.with_mixnode(load_mixnode_details(self.mixnode_config)?)
|
||||
.with_landing_page_assets(self.mixnode_config.http.landing_page_assets_path.as_ref());
|
||||
|
||||
let router = nym_node::http::NymNodeRouter::new(config, None);
|
||||
let router = nym_node_http_api::NymNodeRouter::new(config, None, None);
|
||||
let server = router
|
||||
.with_merged(legacy::routes(self.legacy_mixnode, self.legacy_descriptor))
|
||||
.build_server(&bind_address)?
|
||||
|
||||
+87
-45
@@ -4,30 +4,29 @@
|
||||
use crate::config::Config;
|
||||
use crate::error::MixnodeError;
|
||||
use crate::node::helpers::{load_identity_keys, load_sphinx_keys};
|
||||
use crate::node::http::legacy::verloc::VerlocState;
|
||||
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::node_statistics::SharedNodeStats;
|
||||
use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSender};
|
||||
use log::{error, info, warn};
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_bin_common::version_checker::parse_version;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_mixnode_common::verloc::{self, AtomicVerlocResult, VerlocMeasurer};
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
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(crate) mod helpers;
|
||||
pub mod helpers;
|
||||
mod http;
|
||||
mod listener;
|
||||
pub(crate) mod node_description;
|
||||
pub mod node_description;
|
||||
mod node_statistics;
|
||||
mod packet_delayforwarder;
|
||||
|
||||
@@ -37,24 +36,67 @@ pub struct MixNode {
|
||||
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: false,
|
||||
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: false,
|
||||
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(crate) fn print_node_details(&self, output: OutputFormat) {
|
||||
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(),
|
||||
@@ -70,13 +112,13 @@ impl MixNode {
|
||||
|
||||
fn start_http_api(
|
||||
&self,
|
||||
atomic_verloc_result: AtomicVerlocResult,
|
||||
node_stats_pointer: SharedNodeStats,
|
||||
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(VerlocState::new(atomic_verloc_result))
|
||||
.with_verloc(atomic_verloc_result)
|
||||
.with_mixing_stats(node_stats_pointer)
|
||||
.with_metrics_key(metrics_key)
|
||||
.with_descriptor(self.descriptor.clone())
|
||||
@@ -84,19 +126,21 @@ impl MixNode {
|
||||
}
|
||||
|
||||
fn start_node_stats_controller(
|
||||
&self,
|
||||
&mut self,
|
||||
shutdown: TaskClient,
|
||||
) -> (SharedNodeStats, node_statistics::UpdateSender) {
|
||||
) -> (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 node_stats_pointer = controller.get_node_stats_data_pointer();
|
||||
let update_sender = controller.start();
|
||||
|
||||
(node_stats_pointer, update_sender)
|
||||
(mixing_stats, update_sender)
|
||||
}
|
||||
|
||||
fn start_socket_listener(
|
||||
@@ -147,22 +191,10 @@ impl MixNode {
|
||||
packet_sender
|
||||
}
|
||||
|
||||
fn start_verloc_measurements(&self, shutdown: TaskClient) -> AtomicVerlocResult {
|
||||
fn start_verloc_measurements(&mut self, shutdown: TaskClient) -> SharedVerlocStats {
|
||||
info!("Starting the round-trip-time measurer...");
|
||||
|
||||
// this is a sanity check to make sure we didn't mess up with the minimum version at some point
|
||||
// and whether the user has run update if they're using old config
|
||||
// if this code exists in the node, it MUST BE compatible
|
||||
let config_version = parse_version(&self.config.mixnode.version)
|
||||
.expect("malformed version in the config file");
|
||||
let minimum_version = parse_version(verloc::MINIMUM_NODE_VERSION).unwrap();
|
||||
if config_version < minimum_version {
|
||||
error!("You seem to have not updated your mixnode configuration file - please run `upgrade` before attempting again");
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
// 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,
|
||||
@@ -180,11 +212,13 @@ impl MixNode {
|
||||
.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);
|
||||
let atomic_verloc_results = verloc_measurer.get_verloc_results_pointer();
|
||||
verloc_measurer.set_shared_state(verloc_state.clone());
|
||||
|
||||
tokio::spawn(async move { verloc_measurer.run().await });
|
||||
atomic_verloc_results
|
||||
verloc_state
|
||||
}
|
||||
|
||||
fn random_api_client(&self) -> nym_validator_client::NymApiClient {
|
||||
@@ -217,8 +251,8 @@ impl MixNode {
|
||||
})
|
||||
}
|
||||
|
||||
async fn wait_for_interrupt(&self, mut shutdown: TaskManager) {
|
||||
let _res = shutdown.catch_interrupt().await;
|
||||
async fn wait_for_interrupt(&self, shutdown: TaskHandle) {
|
||||
let _res = shutdown.wait_for_shutdown().await;
|
||||
log::info!("Stopping nym mixnode");
|
||||
}
|
||||
|
||||
@@ -229,34 +263,42 @@ impl MixNode {
|
||||
warn!("You seem to have bonded your mixnode before starting it - that's highly unrecommended as in the future it might result in slashing");
|
||||
}
|
||||
|
||||
let shutdown = TaskManager::default();
|
||||
// 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.subscribe().named("node_statistics::Controller"));
|
||||
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.subscribe().named("DelayForwarder"),
|
||||
shutdown.fork("DelayForwarder"),
|
||||
);
|
||||
self.start_socket_listener(
|
||||
node_stats_update_sender,
|
||||
delay_forwarding_channel,
|
||||
shutdown.subscribe().named("Listener"),
|
||||
shutdown.fork("Listener"),
|
||||
);
|
||||
let atomic_verloc_results =
|
||||
self.start_verloc_measurements(shutdown.subscribe().named("VerlocMeasurer"));
|
||||
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.
|
||||
self.start_http_api(
|
||||
atomic_verloc_results,
|
||||
node_stats_pointer,
|
||||
self.config.metrics_key(),
|
||||
shutdown.subscribe().named("http-api"),
|
||||
)?;
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,23 @@
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::Path;
|
||||
use std::{fs, io};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct NodeDescription {
|
||||
pub(crate) name: String,
|
||||
pub(crate) description: String,
|
||||
pub(crate) link: String,
|
||||
pub(crate) location: String,
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub description: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub link: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub location: String,
|
||||
}
|
||||
|
||||
impl Default for NodeDescription {
|
||||
@@ -26,7 +34,7 @@ impl Default for NodeDescription {
|
||||
}
|
||||
|
||||
impl NodeDescription {
|
||||
pub(crate) fn load_from_file<P: AsRef<Path>>(path: P) -> io::Result<NodeDescription> {
|
||||
pub fn load_from_file<P: AsRef<Path>>(path: P) -> io::Result<NodeDescription> {
|
||||
// let description_file_path: PathBuf = [config_path.to_str().unwrap(), DESCRIPTION_FILE]
|
||||
// .iter()
|
||||
// .collect();
|
||||
@@ -36,15 +44,14 @@ impl NodeDescription {
|
||||
toml::from_str(&toml).map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
|
||||
}
|
||||
|
||||
pub(crate) fn save_to_file<P: AsRef<Path>>(
|
||||
description: &NodeDescription,
|
||||
path: P,
|
||||
) -> io::Result<()> {
|
||||
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||
// let description_file_path: PathBuf = [config_path.to_str().unwrap(), DESCRIPTION_FILE]
|
||||
// .iter()
|
||||
// .collect();
|
||||
let description_toml =
|
||||
toml::to_string(description).expect("could not encode description to toml");
|
||||
let description_toml = toml::to_string(self).expect("could not encode description to toml");
|
||||
if let Some(parent) = path.as_ref().parent() {
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
fs::write(path, description_toml)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -8,50 +8,27 @@ use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::StreamExt;
|
||||
use log::{debug, info, trace};
|
||||
use serde::Serialize;
|
||||
use nym_node_http_api::state::metrics::SharedMixingStats;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
// convenience aliases
|
||||
type PacketsMap = HashMap<String, u64>;
|
||||
type PacketDataReceiver = mpsc::UnboundedReceiver<PacketEvent>;
|
||||
type PacketDataSender = mpsc::UnboundedSender<PacketEvent>;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct SharedNodeStats {
|
||||
inner: Arc<RwLock<NodeStats>>,
|
||||
trait MixingStatsUpdateExt {
|
||||
async fn update(&self, new_received: u64, new_sent: PacketsMap, new_dropped: PacketsMap);
|
||||
}
|
||||
|
||||
impl SharedNodeStats {
|
||||
pub(crate) fn new() -> Self {
|
||||
let now = SystemTime::now();
|
||||
|
||||
SharedNodeStats {
|
||||
inner: Arc::new(RwLock::new(NodeStats {
|
||||
update_time: now,
|
||||
previous_update_time: now,
|
||||
packets_received_since_startup: 0,
|
||||
packets_sent_since_startup_all: 0,
|
||||
packets_dropped_since_startup_all: 0,
|
||||
packets_received_since_last_update: 0,
|
||||
packets_sent_since_last_update: HashMap::new(),
|
||||
packets_explicitly_dropped_since_last_update: HashMap::new(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn update(
|
||||
&self,
|
||||
new_received: u64,
|
||||
new_sent: PacketsMap,
|
||||
new_dropped: PacketsMap,
|
||||
) {
|
||||
let mut guard = self.inner.write().await;
|
||||
let snapshot_time = SystemTime::now();
|
||||
impl MixingStatsUpdateExt for SharedMixingStats {
|
||||
async fn update(&self, new_received: u64, new_sent: PacketsMap, new_dropped: PacketsMap) {
|
||||
let mut guard = self.write().await;
|
||||
let snapshot_time = OffsetDateTime::now_utc();
|
||||
|
||||
guard.previous_update_time = guard.update_time;
|
||||
guard.update_time = snapshot_time;
|
||||
@@ -79,88 +56,6 @@ impl SharedNodeStats {
|
||||
guard.packets_sent_since_last_update = new_sent;
|
||||
guard.packets_explicitly_dropped_since_last_update = new_dropped;
|
||||
}
|
||||
|
||||
pub(crate) async fn clone_data(&self) -> NodeStats {
|
||||
self.inner.read().await.clone()
|
||||
}
|
||||
|
||||
async fn read(&self) -> RwLockReadGuard<'_, NodeStats> {
|
||||
self.inner.read().await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NodeStats {
|
||||
update_time: SystemTime,
|
||||
|
||||
previous_update_time: SystemTime,
|
||||
|
||||
packets_received_since_startup: u64,
|
||||
packets_sent_since_startup_all: u64,
|
||||
packets_dropped_since_startup_all: u64,
|
||||
packets_received_since_last_update: u64,
|
||||
// note: sent does not imply forwarded. We don't know if it was delivered successfully
|
||||
packets_sent_since_last_update: PacketsMap,
|
||||
// we know for sure we dropped packets to those destinations
|
||||
packets_explicitly_dropped_since_last_update: PacketsMap,
|
||||
}
|
||||
|
||||
impl Default for NodeStats {
|
||||
fn default() -> Self {
|
||||
NodeStats {
|
||||
update_time: SystemTime::UNIX_EPOCH,
|
||||
previous_update_time: SystemTime::UNIX_EPOCH,
|
||||
packets_received_since_startup: 0,
|
||||
packets_sent_since_startup_all: 0,
|
||||
packets_dropped_since_startup_all: 0,
|
||||
packets_received_since_last_update: 0,
|
||||
packets_sent_since_last_update: Default::default(),
|
||||
packets_explicitly_dropped_since_last_update: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeStats {
|
||||
pub(crate) fn simplify(&self) -> NodeStatsSimple {
|
||||
NodeStatsSimple {
|
||||
update_time: self.update_time,
|
||||
previous_update_time: self.previous_update_time,
|
||||
packets_received_since_startup: self.packets_received_since_startup,
|
||||
packets_sent_since_startup: self.packets_sent_since_startup_all,
|
||||
packets_explicitly_dropped_since_startup: self.packets_dropped_since_startup_all,
|
||||
packets_received_since_last_update: self.packets_received_since_last_update,
|
||||
packets_sent_since_last_update: self.packets_sent_since_last_update.values().sum(),
|
||||
packets_explicitly_dropped_since_last_update: self
|
||||
.packets_explicitly_dropped_since_last_update
|
||||
.values()
|
||||
.sum(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct NodeStatsSimple {
|
||||
#[serde(serialize_with = "humantime_serde::serialize")]
|
||||
update_time: SystemTime,
|
||||
|
||||
#[serde(serialize_with = "humantime_serde::serialize")]
|
||||
previous_update_time: SystemTime,
|
||||
|
||||
packets_received_since_startup: u64,
|
||||
|
||||
// note: sent does not imply forwarded. We don't know if it was delivered successfully
|
||||
packets_sent_since_startup: u64,
|
||||
|
||||
// we know for sure we dropped those packets
|
||||
packets_explicitly_dropped_since_startup: u64,
|
||||
|
||||
packets_received_since_last_update: u64,
|
||||
|
||||
// note: sent does not imply forwarded. We don't know if it was delivered successfully
|
||||
packets_sent_since_last_update: u64,
|
||||
|
||||
// we know for sure we dropped those packets
|
||||
packets_explicitly_dropped_since_last_update: u64,
|
||||
}
|
||||
|
||||
pub(crate) enum PacketEvent {
|
||||
@@ -305,7 +200,7 @@ impl UpdateSender {
|
||||
struct StatsUpdater {
|
||||
updating_delay: Duration,
|
||||
current_packet_data: CurrentPacketData,
|
||||
current_stats: SharedNodeStats,
|
||||
current_stats: SharedMixingStats,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
@@ -313,7 +208,7 @@ impl StatsUpdater {
|
||||
fn new(
|
||||
updating_delay: Duration,
|
||||
current_packet_data: CurrentPacketData,
|
||||
current_stats: SharedNodeStats,
|
||||
current_stats: SharedMixingStats,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
StatsUpdater {
|
||||
@@ -347,12 +242,12 @@ impl StatsUpdater {
|
||||
// since we have the http endpoint now?
|
||||
struct PacketStatsConsoleLogger {
|
||||
logging_delay: Duration,
|
||||
stats: SharedNodeStats,
|
||||
stats: SharedMixingStats,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl PacketStatsConsoleLogger {
|
||||
fn new(logging_delay: Duration, stats: SharedNodeStats, shutdown: TaskClient) -> Self {
|
||||
fn new(logging_delay: Duration, stats: SharedMixingStats, shutdown: TaskClient) -> Self {
|
||||
PacketStatsConsoleLogger {
|
||||
logging_delay,
|
||||
stats,
|
||||
@@ -365,9 +260,10 @@ impl PacketStatsConsoleLogger {
|
||||
|
||||
// it's super unlikely this will ever fail, but anything involving time is super weird
|
||||
// so let's just guard against it
|
||||
if let Ok(time_difference) = stats.update_time.duration_since(stats.previous_update_time) {
|
||||
let time_difference = stats.update_time - stats.previous_update_time;
|
||||
if time_difference.is_positive() {
|
||||
// we honestly don't care if it was 30.000828427s or 30.002461449s, 30s is enough
|
||||
let difference_secs = time_difference.as_secs();
|
||||
let difference_secs = time_difference.whole_seconds();
|
||||
|
||||
info!(
|
||||
"Since startup mixed {} packets! ({} in last {} seconds)",
|
||||
@@ -449,20 +345,17 @@ pub struct Controller {
|
||||
|
||||
/// Responsible for updating stats at given interval
|
||||
stats_updater: StatsUpdater,
|
||||
|
||||
/// Pointer to the current node stats
|
||||
node_stats: SharedNodeStats,
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
pub(crate) fn new(
|
||||
logging_delay: Duration,
|
||||
stats_updating_delay: Duration,
|
||||
mixing_stats: SharedMixingStats,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
let (sender, receiver) = mpsc::unbounded();
|
||||
let shared_packet_data = CurrentPacketData::new();
|
||||
let shared_node_stats = SharedNodeStats::new();
|
||||
|
||||
Controller {
|
||||
update_handler: UpdateHandler::new(
|
||||
@@ -473,22 +366,15 @@ impl Controller {
|
||||
update_sender: UpdateSender::new(sender),
|
||||
console_logger: PacketStatsConsoleLogger::new(
|
||||
logging_delay,
|
||||
shared_node_stats.clone(),
|
||||
mixing_stats.clone(),
|
||||
shutdown.clone(),
|
||||
),
|
||||
stats_updater: StatsUpdater::new(
|
||||
stats_updating_delay,
|
||||
shared_packet_data,
|
||||
shared_node_stats.clone(),
|
||||
mixing_stats.clone(),
|
||||
shutdown,
|
||||
),
|
||||
node_stats: shared_node_stats,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_node_stats_data_pointer(&self) -> SharedNodeStats {
|
||||
SharedNodeStats {
|
||||
inner: Arc::clone(&self.node_stats.inner),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,10 +404,14 @@ mod tests {
|
||||
let logging_delay = Duration::from_millis(20);
|
||||
let stats_updating_delay = Duration::from_millis(10);
|
||||
let shutdown = TaskManager::default();
|
||||
let node_stats_controller =
|
||||
Controller::new(logging_delay, stats_updating_delay, shutdown.subscribe());
|
||||
let stats = SharedMixingStats::new();
|
||||
let node_stats_controller = Controller::new(
|
||||
logging_delay,
|
||||
stats_updating_delay,
|
||||
stats.clone(),
|
||||
shutdown.subscribe(),
|
||||
);
|
||||
|
||||
let node_stats_pointer = node_stats_controller.get_node_stats_data_pointer();
|
||||
let update_sender = node_stats_controller.start();
|
||||
tokio::time::pause();
|
||||
|
||||
@@ -534,7 +424,7 @@ mod tests {
|
||||
tokio::task::yield_now().await;
|
||||
|
||||
// Get output (stats)
|
||||
let stats = node_stats_pointer.read().await;
|
||||
let stats = stats.read().await;
|
||||
assert_eq!(&stats.packets_sent_since_startup_all, &2);
|
||||
assert_eq!(&stats.packets_sent_since_last_update.get("foo"), &Some(&2));
|
||||
assert_eq!(&stats.packets_sent_since_last_update.len(), &1);
|
||||
|
||||
Reference in New Issue
Block a user