diff --git a/Cargo.lock b/Cargo.lock index 614ed8738a..388c0697fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6697,6 +6697,7 @@ name = "nym-mixnode" version = "1.1.31" dependencies = [ "anyhow", + "axum", "bs58 0.4.0", "cfg-if", "clap 4.4.6", @@ -6714,6 +6715,7 @@ dependencies = [ "nym-crypto", "nym-mixnet-client", "nym-mixnode-common", + "nym-node", "nym-nonexhaustive-delayqueue", "nym-pemstore", "nym-sphinx", @@ -6726,10 +6728,10 @@ dependencies = [ "opentelemetry", "pretty_env_logger", "rand 0.7.3", - "rocket", "serde", "serde_json", "sysinfo", + "thiserror", "tokio", "tokio-util", "toml 0.5.11", diff --git a/common/mixnode-common/src/verloc/measurement.rs b/common/mixnode-common/src/verloc/measurement.rs index 6f3d93bb65..a1584dee99 100644 --- a/common/mixnode-common/src/verloc/measurement.rs +++ b/common/mixnode-common/src/verloc/measurement.rs @@ -9,6 +9,7 @@ use std::sync::Arc; use std::time::Duration; use tokio::sync::RwLock; +#[derive(Clone)] pub struct AtomicVerlocResult { inner: Arc>, } @@ -35,13 +36,6 @@ impl AtomicVerlocResult { } } - // this could have also been achieved with a normal #[derive(Clone)] but I prefer to be explicit about it - pub(crate) fn clone_data_pointer(&self) -> Self { - AtomicVerlocResult { - inner: Arc::clone(&self.inner), - } - } - pub(crate) async fn reset_results(&self, new_tested: usize) { let mut write_permit = self.inner.write().await; write_permit.total_tested = new_tested; diff --git a/common/mixnode-common/src/verloc/mod.rs b/common/mixnode-common/src/verloc/mod.rs index 3872f8002e..8fa66e3445 100644 --- a/common/mixnode-common/src/verloc/mod.rs +++ b/common/mixnode-common/src/verloc/mod.rs @@ -226,7 +226,7 @@ impl VerlocMeasurer { } pub fn get_verloc_results_pointer(&self) -> AtomicVerlocResult { - self.results.clone_data_pointer() + self.results.clone() } fn start_listening(&self) -> JoinHandle<()> { diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index b968ee7b7e..c1d6503199 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -52,7 +52,7 @@ tokio = { workspace = true, features = [ tokio-stream = { version = "0.1.11", features = ["fs"] } tokio-tungstenite = { version = "0.20.1" } tokio-util = { version = "0.7.4", features = ["codec"] } -url = { version = "2.2", features = ["serde"] } +url = { workspace = true, features = ["serde"] } zeroize = { workspace = true } # internal diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 37268c654d..39061c4ca4 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -16,6 +16,7 @@ rust-version = "1.58.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +axum = { workspace = true } anyhow = "1.0.40" bs58 = "0.4.0" clap = { version = "4.0", features = ["cargo", "derive"] } @@ -28,22 +29,24 @@ lazy_static = "1.4.0" log = { workspace = true } pretty_env_logger = "0.4.0" rand = "0.7.3" -rocket = { version = "0.5.0-rc.2", features = ["json"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sysinfo = "0.27.7" tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } tokio-util = { version = "0.7.3", features = ["codec"] } toml = "0.5.8" -url = { version = "2.2", features = ["serde"] } +url = { workspace = true, features = ["serde"] } cfg-if = "1.0.0" +thiserror = { workspace = true } ## tracing tracing = { version = "0.1.37", optional = true } opentelemetry = { version = "0.19.0", optional = true } -## internal +# internal +nym-node = { path = "../nym-node" } + nym-config = { path = "../common/config" } nym-crypto = { path = "../common/crypto" } nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index e79df8e2ab..17a2bd3a59 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -7,6 +7,7 @@ use anyhow::anyhow; use clap::CommandFactory; use clap::Subcommand; use colored::Colorize; +use log::{error, info, warn}; use nym_bin_common::completions::{fig_generate, ArgShell}; use nym_bin_common::version_checker; use nym_config::defaults::var_names::{BECH32_PREFIX, NYM_API}; diff --git a/mixnode/src/commands/run.rs b/mixnode/src/commands/run.rs index f617494aad..0fded783b8 100644 --- a/mixnode/src/commands/run.rs +++ b/mixnode/src/commands/run.rs @@ -6,11 +6,11 @@ use crate::commands::{override_config, try_load_current_config, version_check}; use crate::node::MixNode; use anyhow::bail; use clap::Args; +use log::error; use nym_bin_common::output_format::OutputFormat; use nym_config::helpers::SPECIAL_ADDRESSES; use nym_validator_client::nyxd; use std::net::IpAddr; - #[derive(Args, Clone)] pub(crate) struct Run { /// Id of the nym-mixnode we want to run diff --git a/mixnode/src/commands/sign.rs b/mixnode/src/commands/sign.rs index 8c10bdb995..9650f53273 100644 --- a/mixnode/src/commands/sign.rs +++ b/mixnode/src/commands/sign.rs @@ -5,15 +5,13 @@ use crate::commands::{try_load_current_config, validate_bech32_address_or_exit}; use crate::node::MixNode; use anyhow::{bail, Result}; use clap::{ArgGroup, Args}; +use log::error; use nym_bin_common::output_format::OutputFormat; use nym_crypto::asymmetric::identity; use nym_types::helpers::ConsoleSigningOutput; use nym_validator_client::nyxd; use std::convert::TryFrom; -#[cfg(feature = "cpucycles")] -use tracing::error; - use super::version_check; #[derive(Args, Clone)] diff --git a/mixnode/src/error.rs b/mixnode/src/error.rs new file mode 100644 index 0000000000..a0ee647852 --- /dev/null +++ b/mixnode/src/error.rs @@ -0,0 +1,11 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use thiserror::Error; + +#[derive(Debug, Error)] +pub(crate) enum MixnodeError { + // TODO: in the future this should work the other way, i.e. NymNode depending on Gateway errors + #[error(transparent)] + NymNodeError(#[from] nym_node::error::NymNodeError), +} diff --git a/mixnode/src/main.rs b/mixnode/src/main.rs index bdda653edf..34ca2d613f 100644 --- a/mixnode/src/main.rs +++ b/mixnode/src/main.rs @@ -1,12 +1,10 @@ // Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -#[macro_use] -extern crate rocket; - use ::nym_config::defaults::setup_env; use clap::{crate_name, crate_version, Parser}; use lazy_static::lazy_static; +use log::info; use nym_bin_common::bin_info; #[allow(unused_imports)] @@ -20,6 +18,7 @@ use tracing::instrument; mod commands; mod config; +pub(crate) mod error; mod node; lazy_static! { diff --git a/mixnode/src/node/http/description.rs b/mixnode/src/node/http/description.rs index 3c37014645..7c6ec2b580 100644 --- a/mixnode/src/node/http/description.rs +++ b/mixnode/src/node/http/description.rs @@ -1,9 +1,14 @@ use crate::node::node_description::NodeDescription; -use rocket::serde::json::Json; -use rocket::State; +use axum::extract::Query; +use nym_node::http::api::{FormattedResponse, OutputParams}; /// Returns a description of the node and why someone might want to delegate stake to it. -#[get("/description")] -pub(crate) fn description(description: &State) -> Json { - Json(description.inner().clone()) +pub(crate) async fn description( + description: NodeDescription, + Query(output): Query, +) -> MixnodeDescriptionResponse { + let output = output.output.unwrap_or_default(); + output.to_response(description) } + +pub type MixnodeDescriptionResponse = FormattedResponse; diff --git a/mixnode/src/node/http/hardware.rs b/mixnode/src/node/http/hardware.rs index 57e2e68bbf..c4e24b4911 100644 --- a/mixnode/src/node/http/hardware.rs +++ b/mixnode/src/node/http/hardware.rs @@ -1,10 +1,11 @@ +use axum::extract::Query; use cupid::TopologyType; -use rocket::serde::{json::Json, Serialize}; +use nym_node::http::api::{FormattedResponse, OutputParams}; +use serde::Serialize; use sysinfo::{System, SystemExt}; #[derive(Serialize, Debug)] -#[serde(crate = "rocket::serde")] -pub(crate) struct Hardware { +pub struct Hardware { ram: String, num_cores: usize, crypto_hardware: Option, @@ -12,7 +13,6 @@ pub(crate) struct Hardware { #[allow(clippy::struct_excessive_bools)] #[derive(Serialize, Debug)] -#[serde(crate = "rocket::serde")] pub(crate) struct CryptoHardware { aesni: bool, avx2: bool, @@ -24,11 +24,13 @@ pub(crate) struct CryptoHardware { } /// Provides hardware information which Nym can use to optimize mixnet speed over time (memory, crypto hardware, CPU, cores, etc). -#[get("/hardware")] -pub(crate) fn hardware() -> Json> { - Json(hardware_info()) +pub(crate) async fn hardware(Query(output): Query) -> MixnodeHardwareResponse { + let output = output.output.unwrap_or_default(); + output.to_response(hardware_info()) } +pub type MixnodeHardwareResponse = FormattedResponse>; + /// Gives back a summary report of whatever system hardware info we can get for this platform. fn hardware_info() -> Option { let crypto_hardware = hardware_info_from_cupid(); diff --git a/mixnode/src/node/http/mod.rs b/mixnode/src/node/http/mod.rs index f92212b9aa..ddedce8215 100644 --- a/mixnode/src/node/http/mod.rs +++ b/mixnode/src/node/http/mod.rs @@ -1,11 +1,15 @@ pub(crate) mod description; pub(crate) mod hardware; +pub(crate) mod state; pub(crate) mod stats; pub(crate) mod verloc; -use rocket::Request; +use axum::http::{StatusCode, Uri}; +use axum::response::IntoResponse; -#[catch(404)] -pub(crate) fn not_found(req: &Request<'_>) -> String { - format!("I couldn't find '{}'. Try something else?", req.uri()) +pub(crate) async fn not_found(uri: Uri) -> impl IntoResponse { + ( + StatusCode::NOT_FOUND, + format!("I couldn't find '{uri}'. Try something else?"), + ) } diff --git a/mixnode/src/node/http/state.rs b/mixnode/src/node/http/state.rs new file mode 100644 index 0000000000..4cc3e7b8d6 --- /dev/null +++ b/mixnode/src/node/http/state.rs @@ -0,0 +1,25 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::http::verloc::VerlocState; +use crate::node::node_statistics::SharedNodeStats; +use axum::extract::FromRef; + +// this is a temporary thing for the transition period +#[derive(Clone)] +pub(crate) struct MixnodeAppState { + pub(crate) verloc: VerlocState, + pub(crate) stats: SharedNodeStats, +} + +impl FromRef for VerlocState { + fn from_ref(app_state: &MixnodeAppState) -> Self { + app_state.verloc.clone() + } +} + +impl FromRef for SharedNodeStats { + fn from_ref(app_state: &MixnodeAppState) -> Self { + app_state.stats.clone() + } +} diff --git a/mixnode/src/node/http/stats.rs b/mixnode/src/node/http/stats.rs index c285954cb9..093932288a 100644 --- a/mixnode/src/node/http/stats.rs +++ b/mixnode/src/node/http/stats.rs @@ -1,29 +1,38 @@ use crate::node::node_statistics::{NodeStats, NodeStatsSimple, SharedNodeStats}; -use rocket::serde::json::Json; -use rocket::State; -use serde::Serialize; +use axum::extract::{Query, State}; +use nym_node::http::api::{FormattedResponse, Output}; +use serde::{Deserialize, Serialize}; #[derive(Serialize)] #[serde(untagged)] -pub(crate) enum NodeStatsResponse { +pub enum NodeStatsResponse { Full(NodeStats), Simple(NodeStatsSimple), } -/// Returns a running stats of the node. -#[get("/stats?")] pub(crate) async fn stats( - stats: &State, - debug: Option, -) -> Json { + Query(params): Query, + State(stats): State, +) -> MixnodeStatsResponse { + let output = params.output.unwrap_or_default(); + let snapshot_data = stats.clone_data().await; // there's no point in returning the entire hashmap of sending destinations in regular mode - if let Some(debug) = debug { - if debug { - return Json(NodeStatsResponse::Full(snapshot_data)); - } - } - - Json(NodeStatsResponse::Simple(snapshot_data.simplify())) + let response = if params.debug { + NodeStatsResponse::Full(snapshot_data) + } else { + NodeStatsResponse::Simple(snapshot_data.simplify()) + }; + output.to_response(response) +} + +pub type MixnodeStatsResponse = FormattedResponse; + +#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)] +// #[derive(Default, Debug, Serialize, Deserialize, Copy, Clone, IntoParams, ToSchema)] +#[serde(default)] +pub(crate) struct StatsQueryParams { + debug: bool, + pub output: Option, } diff --git a/mixnode/src/node/http/verloc.rs b/mixnode/src/node/http/verloc.rs index ff26a1b52d..1848f48f6c 100644 --- a/mixnode/src/node/http/verloc.rs +++ b/mixnode/src/node/http/verloc.rs @@ -1,7 +1,8 @@ +use axum::extract::{Query, State}; use nym_mixnode_common::verloc::{AtomicVerlocResult, VerlocResult}; -use rocket::serde::json::Json; -use rocket::State; +use nym_node::http::api::{FormattedResponse, OutputParams}; +#[derive(Clone)] pub(crate) struct VerlocState { shared: AtomicVerlocResult, } @@ -16,8 +17,12 @@ impl VerlocState { /// 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. -#[get("/verloc")] -pub(crate) async fn verloc(state: &State) -> Json { - // since it's impossible to get a mutable reference to the state, we can't cache any results outside the lock : ( - Json(state.shared.clone_data().await) +pub(crate) async fn verloc( + State(verloc): State, + Query(output): Query, +) -> MixnodeVerlocResponse { + let output = output.output.unwrap_or_default(); + output.to_response(verloc.shared.clone_data().await) } + +pub type MixnodeVerlocResponse = FormattedResponse; diff --git a/mixnode/src/node/listener/connection_handler/mod.rs b/mixnode/src/node/listener/connection_handler/mod.rs index 733d2660f8..8d0895cbea 100644 --- a/mixnode/src/node/listener/connection_handler/mod.rs +++ b/mixnode/src/node/listener/connection_handler/mod.rs @@ -7,6 +7,8 @@ use crate::node::listener::connection_handler::packet_processing::{ use crate::node::packet_delayforwarder::PacketDelayForwardSender; use crate::node::TaskClient; use futures::StreamExt; +use log::debug; +use log::{error, info, warn}; use nym_mixnode_common::measure; use nym_sphinx::forwarding::packet::MixPacket; use nym_sphinx::framing::codec::NymCodec; @@ -16,8 +18,9 @@ use std::net::SocketAddr; use tokio::net::TcpStream; use tokio::time::Instant; use tokio_util::codec::Framed; + #[cfg(feature = "cpucycles")] -use tracing::{error, info, instrument}; +use tracing::instrument; pub(crate) mod packet_processing; diff --git a/mixnode/src/node/listener/mod.rs b/mixnode/src/node/listener/mod.rs index f8fad07b23..fc553b41ed 100644 --- a/mixnode/src/node/listener/mod.rs +++ b/mixnode/src/node/listener/mod.rs @@ -2,12 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node::listener::connection_handler::ConnectionHandler; +use log::{error, info, warn}; use std::net::SocketAddr; use std::process; use tokio::net::TcpListener; use tokio::task::JoinHandle; -#[cfg(feature = "cpucycles")] -use tracing::error; use super::TaskClient; diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index 98b0c769dc..a73c5a10a4 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -2,23 +2,26 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::Config; -use crate::node::http::{ - description::description, - hardware::hardware, - not_found, - stats::stats, - verloc::{verloc as verloc_route, VerlocState}, -}; +use crate::node::http::description::description; +use crate::node::http::hardware::hardware; +use crate::node::http::not_found; +use crate::node::http::state::MixnodeAppState; +use crate::node::http::stats::stats; +use crate::node::http::verloc::{verloc, VerlocState}; 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 axum::routing::get; +use axum::Router; +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_node::http::middleware::logging; use nym_task::{TaskClient, TaskManager}; use rand::seq::SliceRandom; use rand::thread_rng; @@ -26,9 +29,6 @@ use std::net::SocketAddr; use std::process; use std::sync::Arc; -#[cfg(feature = "cpucycles")] -use tracing::{error, info, warn}; - mod http; mod listener; pub(crate) mod node_description; @@ -99,29 +99,36 @@ impl MixNode { atomic_verloc_result: AtomicVerlocResult, node_stats_pointer: SharedNodeStats, ) { - info!( - "Starting HTTP API on http://{}:{}", - self.config.mixnode.listening_address, self.config.mixnode.http_api_port + let bind_address = SocketAddr::new( + self.config.mixnode.listening_address, + self.config.mixnode.http_api_port, ); - let mut config = rocket::config::Config::release_default(); + info!("Starting HTTP API on http://{bind_address}",); - // bind to the same address as we are using for mixnodes - config.address = self.config.mixnode.listening_address; - config.port = self.config.mixnode.http_api_port; + let state = MixnodeAppState { + verloc: VerlocState::new(atomic_verloc_result), + stats: node_stats_pointer, + }; - let verloc_state = VerlocState::new(atomic_verloc_result); - let descriptor = self.descriptor.clone(); + let router = Router::new() + .route("/verloc", get(verloc)) + .route( + "/description", + get({ + let descriptor = self.descriptor.clone(); + move |query| description(descriptor, query) + }), + ) + .route("/stats", get(stats)) + .route("/hardware", get(hardware)) + .fallback(not_found) + .layer(axum::middleware::from_fn(logging::logger)) + .with_state(state); tokio::spawn(async move { - rocket::build() - .configure(config) - .mount("/", routes![verloc_route, description, stats, hardware]) - .register("/", catchers![not_found]) - .manage(verloc_state) - .manage(descriptor) - .manage(node_stats_pointer) - .launch() + axum::Server::bind(&bind_address) + .serve(router.into_make_service_with_connect_info::()) .await }); } diff --git a/mixnode/src/node/node_statistics.rs b/mixnode/src/node/node_statistics.rs index 2187d77a91..72f85c6001 100644 --- a/mixnode/src/node/node_statistics.rs +++ b/mixnode/src/node/node_statistics.rs @@ -1,6 +1,8 @@ +use super::TaskClient; use futures::channel::mpsc; use futures::lock::Mutex; use futures::StreamExt; +use log::{debug, info, trace}; use serde::Serialize; use std::collections::HashMap; use std::ops::DerefMut; @@ -9,8 +11,6 @@ use std::sync::Arc; use std::time::{Duration, SystemTime}; use tokio::sync::{RwLock, RwLockReadGuard}; -use super::TaskClient; - // convenience aliases type PacketsMap = HashMap; type PacketDataReceiver = mpsc::UnboundedReceiver; @@ -80,7 +80,7 @@ impl SharedNodeStats { } #[derive(Serialize, Clone)] -pub(crate) struct NodeStats { +pub struct NodeStats { #[serde(serialize_with = "humantime_serde::serialize")] update_time: SystemTime, @@ -126,7 +126,7 @@ impl NodeStats { } #[derive(Serialize, Clone)] -pub(crate) struct NodeStatsSimple { +pub struct NodeStatsSimple { #[serde(serialize_with = "humantime_serde::serialize")] update_time: SystemTime,