diff --git a/CHANGELOG.md b/CHANGELOG.md index 46cb35531e..bda75be82a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [2024.13-magura-drift] (2024-11-29) + +- Optimised syncing bandwidth information to storage + ## [2024.13-magura-patched] (2024-11-22) - [experimental] allow clients to change between deterministic route selection based on packet headers and a pseudorandom distribution diff --git a/Cargo.lock b/Cargo.lock index cd9744933a..bc4eb9d37a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2428,7 +2428,7 @@ dependencies = [ [[package]] name = "explorer-api" -version = "1.1.42" +version = "1.1.43" dependencies = [ "chrono", "clap 4.5.20", @@ -4451,7 +4451,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nym-api" -version = "1.1.46" +version = "1.1.47" dependencies = [ "anyhow", "async-trait", @@ -4701,7 +4701,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.44" +version = "1.1.45" dependencies = [ "anyhow", "base64 0.22.1", @@ -4784,7 +4784,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.44" +version = "1.1.45" dependencies = [ "bs58", "clap 4.5.20", @@ -5892,7 +5892,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.45" +version = "1.1.46" dependencies = [ "addr", "anyhow", @@ -5943,7 +5943,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.1.11" +version = "1.1.12" dependencies = [ "anyhow", "bip39", @@ -6309,7 +6309,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.44" +version = "1.1.45" dependencies = [ "bs58", "clap 4.5.20", @@ -6889,6 +6889,7 @@ dependencies = [ "nym-task", "nym-wireguard-types", "thiserror", + "time", "tokio", "tokio-stream", "x25519-dalek", @@ -6911,7 +6912,7 @@ dependencies = [ [[package]] name = "nymvisor" -version = "0.1.9" +version = "0.1.10" dependencies = [ "anyhow", "bytes", diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index c5679d02de..03eb55186e 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.44" +version = "1.1.45" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index f5d3cd8967..4363c56387 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.44" +version = "1.1.45" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/common/credential-verification/src/bandwidth_storage_manager.rs b/common/credential-verification/src/bandwidth_storage_manager.rs index 3e35fd9eb2..7c3136e91c 100644 --- a/common/credential-verification/src/bandwidth_storage_manager.rs +++ b/common/credential-verification/src/bandwidth_storage_manager.rs @@ -111,7 +111,7 @@ impl BandwidthStorageManager { } #[instrument(level = "trace", skip_all)] - async fn sync_storage_bandwidth(&mut self) -> Result<()> { + pub async fn sync_storage_bandwidth(&mut self) -> Result<()> { trace!("syncing client bandwidth with the underlying storage"); let updated = self .storage diff --git a/common/credential-verification/src/client_bandwidth.rs b/common/credential-verification/src/client_bandwidth.rs index 9b764714e8..d98f89b511 100644 --- a/common/credential-verification/src/client_bandwidth.rs +++ b/common/credential-verification/src/client_bandwidth.rs @@ -8,8 +8,8 @@ use std::time::Duration; use time::OffsetDateTime; use tokio::sync::RwLock; -const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5); -const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB +const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_secs(5 * 60); // 5 minutes +const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 5 * 1024 * 1024; // 5MB #[derive(Debug, Clone, Copy)] pub struct BandwidthFlushingBehaviourConfig { diff --git a/common/dkg/src/interpolation/polynomial.rs b/common/dkg/src/interpolation/polynomial.rs index 08f748b649..671b8e6832 100644 --- a/common/dkg/src/interpolation/polynomial.rs +++ b/common/dkg/src/interpolation/polynomial.rs @@ -212,10 +212,10 @@ impl Add for Polynomial { } } -impl<'b> Add<&'b Polynomial> for &Polynomial { +impl<'a> Add<&'a Polynomial> for &Polynomial { type Output = Polynomial; - fn add(self, rhs: &'b Polynomial) -> Self::Output { + fn add(self, rhs: &'a Polynomial) -> Self::Output { let len = self.coefficients.len(); let rhs_len = rhs.coefficients.len(); diff --git a/common/nymcoconut/src/elgamal.rs b/common/nymcoconut/src/elgamal.rs index 28943568ce..1b814f0bc5 100644 --- a/common/nymcoconut/src/elgamal.rs +++ b/common/nymcoconut/src/elgamal.rs @@ -206,10 +206,10 @@ impl Deref for PublicKey { } } -impl<'b> Mul<&'b Scalar> for &PublicKey { +impl<'a> Mul<&'a Scalar> for &PublicKey { type Output = G1Projective; - fn mul(self, rhs: &'b Scalar) -> Self::Output { + fn mul(self, rhs: &'a Scalar) -> Self::Output { self.0 * rhs } } diff --git a/common/nymsphinx/addressing/src/nodes.rs b/common/nymsphinx/addressing/src/nodes.rs index 1126cb7afd..c748a72dcd 100644 --- a/common/nymsphinx/addressing/src/nodes.rs +++ b/common/nymsphinx/addressing/src/nodes.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -//! Encodoing and decoding node routing information. +//! Encoding and decoding node routing information. //! //! This module is responsible for encoding and decoding node routing information, so that //! they could be later put into an appropriate field in a sphinx header. diff --git a/common/socks5/requests/src/request.rs b/common/socks5/requests/src/request.rs index 59163daf55..28a5182d00 100644 --- a/common/socks5/requests/src/request.rs +++ b/common/socks5/requests/src/request.rs @@ -253,6 +253,9 @@ impl Socks5RequestContent { /// Deserialize the request type, connection id, destination address and port, /// and the request body from bytes. /// + /// The request_flag tells us whether this is a new connection request (`new_connect`), + /// an already-established connection we should send up (`new_send`), or + /// a request to close an established connection (`new_close`). // connect: // RequestFlag::Connect || CONN_ID || ADDR_LEN || ADDR || // diff --git a/common/wireguard/Cargo.toml b/common/wireguard/Cargo.toml index f20651acbd..c594e6b891 100644 --- a/common/wireguard/Cargo.toml +++ b/common/wireguard/Cargo.toml @@ -26,6 +26,7 @@ log.workspace = true thiserror = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] } tokio-stream = { workspace = true } +time = { workspace = true } nym-authenticator-requests = { path = "../authenticator-requests" } nym-credential-verification = { path = "../credential-verification" } diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index a6ae284170..48f3c1f8ed 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -20,6 +20,7 @@ use tokio::sync::mpsc::{self, Receiver, Sender}; pub(crate) mod error; pub mod peer_controller; pub mod peer_handle; +pub mod peer_storage_manager; pub struct WgApiWrapper { inner: WGApi, @@ -118,7 +119,7 @@ pub async fn start_wireguard storage .insert_wireguard_peer(peer, bandwidth_manager.is_some()) .await?; - peer_bandwidth_managers.insert(peer.public_key.clone(), bandwidth_manager); + peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone())); } wg_api.create_interface()?; let interface_config = InterfaceConfiguration { diff --git a/common/wireguard/src/peer_controller.rs b/common/wireguard/src/peer_controller.rs index 8c7d94784a..321b15462f 100644 --- a/common/wireguard/src/peer_controller.rs +++ b/common/wireguard/src/peer_controller.rs @@ -20,9 +20,9 @@ use std::{collections::HashMap, sync::Arc}; use tokio::sync::{mpsc, RwLock}; use tokio_stream::{wrappers::IntervalStream, StreamExt}; -use crate::peer_handle::PeerHandle; use crate::WgApiWrapper; use crate::{error::Error, peer_handle::SharedBandwidthStorageManager}; +use crate::{peer_handle::PeerHandle, peer_storage_manager::PeerStorageManager}; pub enum PeerControlRequest { AddPeer { @@ -79,7 +79,7 @@ impl PeerController { storage: St, wg_api: Arc, initial_host_information: Host, - bw_storage_managers: HashMap>>, + bw_storage_managers: HashMap>, Peer)>, request_tx: mpsc::Sender, request_rx: mpsc::Receiver, task_client: nym_task::TaskClient, @@ -88,11 +88,16 @@ impl PeerController { tokio::time::interval(DEFAULT_PEER_TIMEOUT_CHECK), ); let host_information = Arc::new(RwLock::new(initial_host_information)); - for (public_key, bandwidth_storage_manager) in bw_storage_managers.iter() { - let mut handle = PeerHandle::new( + for (public_key, (bandwidth_storage_manager, peer)) in bw_storage_managers.iter() { + let peer_storage_manager = PeerStorageManager::new( storage.clone(), + peer.clone(), + bandwidth_storage_manager.is_some(), + ); + let mut handle = PeerHandle::new( public_key.clone(), host_information.clone(), + peer_storage_manager, bandwidth_storage_manager.clone(), request_tx.clone(), &task_client, @@ -103,6 +108,10 @@ impl PeerController { } }); } + let bw_storage_managers = bw_storage_managers + .into_iter() + .map(|(k, (m, _))| (k, m)) + .collect(); PeerController { storage, @@ -184,10 +193,15 @@ impl PeerController { Self::generate_bandwidth_manager(self.storage.clone(), &peer.public_key) .await? .map(|bw_m| Arc::new(RwLock::new(bw_m))); - let mut handle = PeerHandle::new( + let peer_storage_manager = PeerStorageManager::new( self.storage.clone(), + peer.clone(), + bandwidth_storage_manager.is_some(), + ); + let mut handle = PeerHandle::new( peer.public_key.clone(), self.host_information.clone(), + peer_storage_manager, bandwidth_storage_manager.clone(), self.request_tx.clone(), &self.task_client, diff --git a/common/wireguard/src/peer_handle.rs b/common/wireguard/src/peer_handle.rs index 71fa06f847..a6e5f9e502 100644 --- a/common/wireguard/src/peer_handle.rs +++ b/common/wireguard/src/peer_handle.rs @@ -3,6 +3,7 @@ use crate::error::Error; use crate::peer_controller::PeerControlRequest; +use crate::peer_storage_manager::PeerStorageManager; use defguard_wireguard_rs::host::Peer; use defguard_wireguard_rs::{host::Host, key::Key}; use futures::channel::oneshot; @@ -21,9 +22,9 @@ pub(crate) type SharedBandwidthStorageManager = Arc { - storage: St, public_key: Key, host_information: Arc>, + peer_storage_manager: PeerStorageManager, bandwidth_storage_manager: Option>, request_tx: mpsc::Sender, timeout_check_interval: IntervalStream, @@ -33,9 +34,9 @@ pub struct PeerHandle { impl PeerHandle { pub fn new( - storage: St, public_key: Key, host_information: Arc>, + peer_storage_manager: PeerStorageManager, bandwidth_storage_manager: Option>, request_tx: mpsc::Sender, task_client: &TaskClient, @@ -46,9 +47,9 @@ impl PeerHandle { let mut task_client = task_client.fork(format!("peer-{public_key}")); task_client.disarm(); PeerHandle { - storage, public_key, host_information, + peer_storage_manager, bandwidth_storage_manager, request_tx, timeout_check_interval, @@ -84,16 +85,19 @@ impl PeerHandle { .ok_or(Error::InconsistentConsumedBytes)? .try_into() .map_err(|_| Error::InconsistentConsumedBytes)?; - if spent_bandwidth > 0 - && bandwidth_manager + if spent_bandwidth > 0 { + self.peer_storage_manager.update_trx(kernel_peer); + if bandwidth_manager .write() .await .try_use_bandwidth(spent_bandwidth) .await .is_err() - { - let success = self.remove_peer().await?; - return Ok(!success); + { + let success = self.remove_peer().await?; + self.peer_storage_manager.remove_peer(); + return Ok(!success); + } } } else { if SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER { @@ -132,7 +136,7 @@ impl PeerHandle { // the host information hasn't beed updated yet continue; }; - let Some(storage_peer) = self.storage.get_wireguard_peer(&self.public_key.to_string()).await? else { + let Some(storage_peer) = self.peer_storage_manager.get_peer() else { log::debug!("Peer {:?} not in storage anymore, shutting down handle", self.public_key); return Ok(()); }; @@ -141,12 +145,18 @@ impl PeerHandle { return Ok(()); } else { // Update storage values - self.storage.insert_wireguard_peer(&kernel_peer, self.bandwidth_storage_manager.is_some()).await?; + self.peer_storage_manager.sync_storage_peer().await?; } } _ = self.task_client.recv() => { log::trace!("PeerHandle: Received shutdown"); + if let Some(bandwidth_manager) = &self.bandwidth_storage_manager { + if let Err(e) = bandwidth_manager.write().await.sync_storage_bandwidth().await { + log::error!("Storage sync failed - {e}, unaccounted bandwidth might have been consumed"); + } + } + log::trace!("PeerHandle: Finished shutdown"); } } } diff --git a/common/wireguard/src/peer_storage_manager.rs b/common/wireguard/src/peer_storage_manager.rs new file mode 100644 index 0000000000..f7992b8bb0 --- /dev/null +++ b/common/wireguard/src/peer_storage_manager.rs @@ -0,0 +1,138 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::Error; +use defguard_wireguard_rs::host::Peer; +use nym_gateway_storage::models::WireguardPeer; +use nym_gateway_storage::Storage; +use std::time::Duration; +use time::OffsetDateTime; + +const DEFAULT_PEER_MAX_FLUSHING_RATE: Duration = Duration::from_secs(60 * 60 * 24); // 24h +const DEFAULT_PEER_MAX_DELTA_FLUSHING_AMOUNT: u64 = 512 * 1024 * 1024; // 512MB + +#[derive(Debug, Clone, Copy)] +pub struct PeerFlushingBehaviourConfig { + /// Defines maximum delay between peer information being flushed to the persistent storage. + pub peer_max_flushing_rate: Duration, + + /// Defines a maximum change in peer before it gets flushed to the persistent storage. + pub peer_max_delta_flushing_amount: u64, +} + +impl Default for PeerFlushingBehaviourConfig { + fn default() -> Self { + Self { + peer_max_flushing_rate: DEFAULT_PEER_MAX_FLUSHING_RATE, + peer_max_delta_flushing_amount: DEFAULT_PEER_MAX_DELTA_FLUSHING_AMOUNT, + } + } +} + +pub struct PeerStorageManager { + pub(crate) storage: S, + pub(crate) peer_information: Option, + pub(crate) cfg: PeerFlushingBehaviourConfig, + pub(crate) with_client_id: bool, +} + +impl PeerStorageManager { + pub(crate) fn new(storage: S, peer: Peer, with_client_id: bool) -> Self { + let peer_information = Some(PeerInformation::new(peer)); + Self { + storage, + peer_information, + cfg: PeerFlushingBehaviourConfig::default(), + with_client_id, + } + } + + pub(crate) fn get_peer(&self) -> Option { + self.peer_information + .as_ref() + .map(|p| p.peer.clone().into()) + } + + pub(crate) fn remove_peer(&mut self) { + self.peer_information = None; + } + + pub(crate) fn update_trx(&mut self, kernel_peer: &Peer) { + if let Some(peer_information) = self.peer_information.as_mut() { + peer_information.update_trx_bytes(kernel_peer.tx_bytes, kernel_peer.rx_bytes); + } + } + + pub(crate) async fn sync_storage_peer(&mut self) -> Result<(), Error> { + let Some(peer_information) = self.peer_information.as_mut() else { + return Ok(()); + }; + if !peer_information.should_sync(self.cfg) { + return Ok(()); + } + if self + .storage + .get_wireguard_peer(&peer_information.peer().public_key.to_string()) + .await? + .is_none() + { + self.peer_information = None; + return Ok(()); + } + self.storage + .insert_wireguard_peer(peer_information.peer(), self.with_client_id) + .await?; + + peer_information.resync_peer_with_storage(); + + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct PeerInformation { + pub(crate) peer: Peer, + pub(crate) last_synced: OffsetDateTime, + + pub(crate) bytes_delta_since_sync: u64, +} + +impl PeerInformation { + pub fn new(peer: Peer) -> PeerInformation { + PeerInformation { + peer, + last_synced: OffsetDateTime::now_utc(), + bytes_delta_since_sync: 0, + } + } + + pub(crate) fn should_sync(&self, cfg: PeerFlushingBehaviourConfig) -> bool { + if self.bytes_delta_since_sync >= cfg.peer_max_delta_flushing_amount { + return true; + } + + if self.last_synced + cfg.peer_max_flushing_rate < OffsetDateTime::now_utc() + && self.bytes_delta_since_sync != 0 + { + return true; + } + + false + } + + pub(crate) fn peer(&self) -> &Peer { + &self.peer + } + + pub(crate) fn update_trx_bytes(&mut self, tx_bytes: u64, rx_bytes: u64) { + self.bytes_delta_since_sync += tx_bytes.saturating_sub(self.peer.tx_bytes) + + rx_bytes.saturating_sub(self.peer.rx_bytes); + self.peer.tx_bytes = tx_bytes; + self.peer.rx_bytes = rx_bytes; + } + + pub(crate) fn resync_peer_with_storage(&mut self) { + self.bytes_delta_since_sync = 0; + self.last_synced = OffsetDateTime::now_utc(); + } +} diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 11f8672f98..6fe9830dae 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.42" +version = "1.1.43" edition = "2021" license.workspace = true diff --git a/gateway/src/node/helpers.rs b/gateway/src/node/helpers.rs index d0862f1688..d87b02afed 100644 --- a/gateway/src/node/helpers.rs +++ b/gateway/src/node/helpers.rs @@ -5,6 +5,8 @@ use async_trait::async_trait; use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent}; use nym_topology::{gateway, NymTopology, TopologyProvider}; use std::sync::Arc; +use std::time::Duration; +use time::OffsetDateTime; use tokio::sync::Mutex; use tracing::debug; use url::Url; @@ -17,6 +19,7 @@ pub struct GatewayTopologyProvider { impl GatewayTopologyProvider { pub fn new( gateway_node: gateway::LegacyNode, + cache_ttl: Duration, user_agent: UserAgent, nym_api_url: Vec, ) -> GatewayTopologyProvider { @@ -31,6 +34,9 @@ impl GatewayTopologyProvider { env!("CARGO_PKG_VERSION").to_string(), Some(user_agent), ), + cache_ttl, + cached_at: OffsetDateTime::UNIX_EPOCH, + cached: None, gateway_node, })), } @@ -39,25 +45,53 @@ impl GatewayTopologyProvider { struct GatewayTopologyProviderInner { inner: NymApiTopologyProvider, + cache_ttl: Duration, + cached_at: OffsetDateTime, + cached: Option, gateway_node: gateway::LegacyNode, } +impl GatewayTopologyProviderInner { + fn cached_topology(&self) -> Option { + if let Some(cached_topology) = &self.cached { + if self.cached_at + self.cache_ttl > OffsetDateTime::now_utc() { + return Some(cached_topology.clone()); + } + } + + None + } + + async fn update_cache(&mut self) -> Option { + let updated_cache = match self.inner.get_new_topology().await { + None => None, + Some(mut base) => { + if !base.gateway_exists(&self.gateway_node.identity_key) { + debug!( + "{} didn't exist in topology. inserting it.", + self.gateway_node.identity_key + ); + base.insert_gateway(self.gateway_node.clone()); + } + Some(base) + } + }; + + self.cached_at = OffsetDateTime::now_utc(); + self.cached = updated_cache.clone(); + + updated_cache + } +} + #[async_trait] impl TopologyProvider for GatewayTopologyProvider { async fn get_new_topology(&mut self) -> Option { let mut guard = self.inner.lock().await; - match guard.inner.get_new_topology().await { - None => None, - Some(mut base) => { - if !base.gateway_exists(&guard.gateway_node.identity_key) { - debug!( - "{} didn't exist in topology. inserting it.", - guard.gateway_node.identity_key - ); - base.insert_gateway(guard.gateway_node.clone()); - } - Some(base) - } + // check the cache + if let Some(cached) = guard.cached_topology() { + return Some(cached); } + guard.update_cache().await } } diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index c399deefa9..90e60f5bfc 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -30,6 +30,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::process; use std::sync::Arc; +use std::time::Duration; use tracing::*; pub(crate) mod client_handling; @@ -148,8 +149,11 @@ impl Gateway { } fn gateway_topology_provider(&self) -> GatewayTopologyProvider { + // TODO: make topology ttl configurable + // (to be done in reeses with the final smooshing) GatewayTopologyProvider::new( self.as_topology_node(), + Duration::from_secs(5 * 60), self.user_agent.clone(), self.config.gateway.nym_api_urls.clone(), ) @@ -231,22 +235,28 @@ impl Gateway { forwarding_channel, router_tx, ); - let all_peers = self.client_storage.get_all_wireguard_peers().await?; - let used_private_network_ips = all_peers - .iter() - .cloned() - .map(|wireguard_peer| { - defguard_wireguard_rs::host::Peer::try_from(wireguard_peer).map(|mut peer| { - peer.allowed_ips - .pop() - .ok_or(Box::new(GatewayError::InternalWireguardError(format!( - "no private IP set for peer {}", - peer.public_key - )))) - .map(|p| p.ip) - }) - }) - .collect::, _>, _>>()??; + let mut used_private_network_ips = vec![]; + let mut all_peers = vec![]; + for wireguard_peer in self + .client_storage + .get_all_wireguard_peers() + .await? + .into_iter() + { + let mut peer = defguard_wireguard_rs::host::Peer::try_from(wireguard_peer.clone())?; + let Some(peer) = peer.allowed_ips.pop() else { + tracing::warn!( + "Peer {} has empty allowed ips. It will be removed", + peer.public_key + ); + self.client_storage + .remove_wireguard_peer(&peer.public_key.to_string()) + .await?; + continue; + }; + used_private_network_ips.push(peer.ip); + all_peers.push(wireguard_peer); + } if let Some(wireguard_data) = self.wireguard_data.take() { let (on_start_tx, on_start_rx) = oneshot::channel(); diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 6f9602eb06..27814af1db 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-api" license = "GPL-3.0" -version = "1.1.46" +version = "1.1.47" authors.workspace = true edition = "2021" rust-version.workspace = true @@ -121,7 +121,7 @@ nym-types = { path = "../common/types" } nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa"] } nym-serde-helpers = { path = "../common/serde-helpers", features = ["date"] } nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" } -nym-statistics-common = {path ="../common/statistics" } +nym-statistics-common = { path = "../common/statistics" } [features] no-reward = [] diff --git a/nym-api/migrations/20241127110000_add_monitor_run_indexes.sql b/nym-api/migrations/20241127110000_add_monitor_run_indexes.sql new file mode 100644 index 0000000000..f639bbba7a --- /dev/null +++ b/nym-api/migrations/20241127110000_add_monitor_run_indexes.sql @@ -0,0 +1,8 @@ +/* + * Copyright 2024 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +CREATE INDEX IF NOT EXISTS monitor_run_id on monitor_run(id); +CREATE INDEX IF NOT EXISTS monitor_run_timestamp on monitor_run(timestamp); +CREATE INDEX IF NOT EXISTS testing_route_monitor_run_id on testing_route(monitor_run_id); \ No newline at end of file diff --git a/nym-api/src/ecash/api_routes/aggregation.rs b/nym-api/src/ecash/api_routes/aggregation.rs index 89478fbd09..8b7baef6e6 100644 --- a/nym-api/src/ecash/api_routes/aggregation.rs +++ b/nym-api/src/ecash/api_routes/aggregation.rs @@ -6,7 +6,7 @@ use crate::ecash::error::EcashError; use crate::ecash::state::EcashState; use crate::node_status_api::models::AxumResult; use crate::support::http::state::AppState; -use axum::extract::Path; +use axum::extract::{Query, State}; use axum::{Json, Router}; use nym_api_requests::ecash::models::{ AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse, @@ -21,28 +21,19 @@ use tracing::trace; use utoipa::IntoParams; /// routes with globally aggregated keys, signatures, etc. -pub(crate) fn aggregation_routes(ecash_state: Arc) -> Router { +pub(crate) fn aggregation_routes() -> Router { Router::new() .route( "/master-verification-key", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - |epoch_id| master_verification_key(epoch_id, ecash_state) - }), + axum::routing::get(master_verification_key), ) .route( "/aggregated-expiration-date-signatures", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - |expiration_date| expiration_date_signatures(expiration_date, ecash_state) - }), + axum::routing::get(expiration_date_signatures), ) .route( "/aggregated-coin-indices-signatures", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - |epoch_id| coin_indices_signatures(epoch_id, ecash_state) - }), + axum::routing::get(coin_indices_signatures), ) } @@ -58,8 +49,8 @@ pub(crate) fn aggregation_routes(ecash_state: Arc) -> Router, - state: Arc, + State(state): State>, + Query(EpochIdParam { epoch_id }): Query, ) -> AxumResult> { trace!("aggregated_verification_key request"); @@ -72,7 +63,6 @@ async fn master_verification_key( } #[derive(Deserialize, IntoParams)] -#[into_params(parameter_in = Path)] struct ExpirationDateParam { expiration_date: Option, } @@ -89,8 +79,8 @@ struct ExpirationDateParam { ) )] async fn expiration_date_signatures( - Path(ExpirationDateParam { expiration_date }): Path, - state: Arc, + State(state): State>, + Query(ExpirationDateParam { expiration_date }): Query, ) -> AxumResult> { trace!("aggregated_expiration_date_signatures request"); @@ -126,8 +116,8 @@ async fn expiration_date_signatures( ) )] async fn coin_indices_signatures( - Path(EpochIdParam { epoch_id }): Path, - state: Arc, + Query(EpochIdParam { epoch_id }): Query, + State(state): State>, ) -> AxumResult> { trace!("aggregated_coin_indices_signatures request"); diff --git a/nym-api/src/ecash/api_routes/handlers.rs b/nym-api/src/ecash/api_routes/handlers.rs index e9f97c48b0..9114ebba9f 100644 --- a/nym-api/src/ecash/api_routes/handlers.rs +++ b/nym-api/src/ecash/api_routes/handlers.rs @@ -5,15 +5,13 @@ use crate::ecash::api_routes::aggregation::aggregation_routes; use crate::ecash::api_routes::issued::issued_routes; use crate::ecash::api_routes::partial_signing::partial_signing_routes; use crate::ecash::api_routes::spending::spending_routes; -use crate::ecash::state::EcashState; use crate::support::http::state::AppState; use axum::Router; -use std::sync::Arc; -pub(crate) fn ecash_routes(ecash_state: Arc) -> Router { +pub(crate) fn ecash_routes() -> Router { Router::new() - .merge(aggregation_routes(Arc::clone(&ecash_state))) - .merge(issued_routes(Arc::clone(&ecash_state))) - .merge(partial_signing_routes(Arc::clone(&ecash_state))) - .merge(spending_routes(Arc::clone(&ecash_state))) + .merge(aggregation_routes()) + .merge(issued_routes()) + .merge(partial_signing_routes()) + .merge(spending_routes()) } diff --git a/nym-api/src/ecash/api_routes/helpers.rs b/nym-api/src/ecash/api_routes/helpers.rs index 0e7afa9b39..9d0270acc7 100644 --- a/nym-api/src/ecash/api_routes/helpers.rs +++ b/nym-api/src/ecash/api_routes/helpers.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-3.0-only #[derive(serde::Deserialize, utoipa::IntoParams)] -#[into_params(parameter_in = Path)] pub(super) struct EpochIdParam { pub(super) epoch_id: Option, } diff --git a/nym-api/src/ecash/api_routes/issued.rs b/nym-api/src/ecash/api_routes/issued.rs index 023b57f6a3..376b532475 100644 --- a/nym-api/src/ecash/api_routes/issued.rs +++ b/nym-api/src/ecash/api_routes/issued.rs @@ -4,7 +4,7 @@ use crate::ecash::state::EcashState; use crate::node_status_api::models::AxumResult; use crate::support::http::state::AppState; -use axum::extract::Path; +use axum::extract::{Path, State}; use axum::{Json, Router}; use nym_api_requests::ecash::models::{ IssuedTicketbooksChallengeRequest, IssuedTicketbooksChallengeResponse, @@ -17,21 +17,15 @@ use time::Date; use tracing::trace; use utoipa::{IntoParams, ToSchema}; -pub(crate) fn issued_routes(ecash_state: Arc) -> Router { +pub(crate) fn issued_routes() -> Router { Router::new() .route( "/issued-ticketbooks-for/:expiration_date", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - |expiration_date| issued_ticketbooks_for(expiration_date, ecash_state) - }), + axum::routing::get(issued_ticketbooks_for), ) .route( "/issued-ticketbooks-challenge", - axum::routing::post({ - let ecash_state = Arc::clone(&ecash_state); - |body| issued_ticketbooks_challenge(body, ecash_state) - }), + axum::routing::post(issued_ticketbooks_challenge), ) } @@ -58,8 +52,8 @@ pub(crate) struct ExpirationDatePathParam { ) )] async fn issued_ticketbooks_for( + State(state): State>, Path(ExpirationDatePathParam { expiration_date }): Path, - state: Arc, ) -> AxumResult> { state.ensure_signer().await?; @@ -83,8 +77,8 @@ async fn issued_ticketbooks_for( ) )] async fn issued_ticketbooks_challenge( + State(state): State>, Json(challenge): Json, - state: Arc, ) -> AxumResult> { trace!("replying to ticketbooks challenge: {:?}", challenge); state.ensure_signer().await?; diff --git a/nym-api/src/ecash/api_routes/partial_signing.rs b/nym-api/src/ecash/api_routes/partial_signing.rs index 7deb919d4e..afe522cbee 100644 --- a/nym-api/src/ecash/api_routes/partial_signing.rs +++ b/nym-api/src/ecash/api_routes/partial_signing.rs @@ -7,7 +7,7 @@ use crate::ecash::helpers::blind_sign; use crate::ecash::state::EcashState; use crate::node_status_api::models::AxumResult; use crate::support::http::state::AppState; -use axum::extract::Query; +use axum::extract::{Query, State}; use axum::{Json, Router}; use nym_api_requests::ecash::{ BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse, @@ -22,28 +22,16 @@ use time::Date; use tracing::{debug, trace}; use utoipa::IntoParams; -pub(crate) fn partial_signing_routes(ecash_state: Arc) -> Router { +pub(crate) fn partial_signing_routes() -> Router { Router::new() - .route( - "/blind-sign", - axum::routing::post({ - let ecash_state = Arc::clone(&ecash_state); - |body| post_blind_sign(body, ecash_state) - }), - ) + .route("/blind-sign", axum::routing::post(post_blind_sign)) .route( "/partial-expiration-date-signatures", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - |expiration_date| partial_expiration_date_signatures(expiration_date, ecash_state) - }), + axum::routing::get(partial_expiration_date_signatures), ) .route( "/partial-coin-indices-signatures", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - |epoch_id| partial_coin_indices_signatures(epoch_id, ecash_state) - }), + axum::routing::get(partial_coin_indices_signatures), ) } @@ -59,8 +47,8 @@ pub(crate) fn partial_signing_routes(ecash_state: Arc) -> Router>, Json(blind_sign_request_body): Json, - state: Arc, ) -> AxumResult> { state.ensure_signer().await?; @@ -134,8 +122,8 @@ struct ExpirationDateParam { ) )] async fn partial_expiration_date_signatures( + State(state): State>, Query(ExpirationDateParam { expiration_date }): Query, - state: Arc, ) -> AxumResult> { state.ensure_signer().await?; @@ -172,8 +160,8 @@ async fn partial_expiration_date_signatures( ) )] async fn partial_coin_indices_signatures( + State(state): State>, Query(EpochIdParam { epoch_id }): Query, - state: Arc, ) -> AxumResult> { state.ensure_signer().await?; diff --git a/nym-api/src/ecash/api_routes/spending.rs b/nym-api/src/ecash/api_routes/spending.rs index b9788a4585..3bda629174 100644 --- a/nym-api/src/ecash/api_routes/spending.rs +++ b/nym-api/src/ecash/api_routes/spending.rs @@ -5,6 +5,7 @@ use crate::ecash::error::EcashError; use crate::ecash::state::EcashState; use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; use crate::support::http::state::AppState; +use axum::extract::State; use axum::{Json, Router}; use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY; use nym_api_requests::ecash::models::{ @@ -21,28 +22,16 @@ use time::{OffsetDateTime, Time}; use tracing::{error, warn}; #[allow(deprecated)] -pub(crate) fn spending_routes(ecash_state: Arc) -> Router { +pub(crate) fn spending_routes() -> Router { Router::new() - .route( - "/verify-ecash-ticket", - axum::routing::post({ - let ecash_state = Arc::clone(&ecash_state); - |body| verify_ticket(body, ecash_state) - }), - ) + .route("/verify-ecash-ticket", axum::routing::post(verify_ticket)) .route( "/batch-redeem-ecash-tickets", - axum::routing::post({ - let ecash_state = Arc::clone(&ecash_state); - |body| batch_redeem_tickets(body, ecash_state) - }), + axum::routing::post(batch_redeem_tickets), ) .route( "/double-spending-filter-v1", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - || double_spending_filter_v1(ecash_state) - }), + axum::routing::get(double_spending_filter_v1), ) } @@ -67,9 +56,9 @@ fn reject_ticket( ) )] async fn verify_ticket( + State(state): State>, // TODO in the future: make it send binary data rather than json Json(verify_ticket_body): Json, - state: Arc, ) -> AxumResult> { state.ensure_signer().await?; @@ -170,9 +159,9 @@ async fn verify_ticket( ) )] async fn batch_redeem_tickets( + State(state): State>, // TODO in the future: make it send binary data rather than json Json(batch_redeem_credentials_body): Json, - state: Arc, ) -> AxumResult> { state.ensure_signer().await?; @@ -244,8 +233,6 @@ async fn batch_redeem_tickets( ) )] #[deprecated] -async fn double_spending_filter_v1( - _state: Arc, -) -> AxumResult> { +async fn double_spending_filter_v1() -> AxumResult> { AxumResult::Err(AxumErrorResponse::internal_msg("permanently restricted")) } diff --git a/nym-api/src/ecash/client.rs b/nym-api/src/ecash/client.rs index 805252808a..f6887f15e7 100644 --- a/nym-api/src/ecash/client.rs +++ b/nym-api/src/ecash/client.rs @@ -27,7 +27,7 @@ use nym_validator_client::EcashApiClient; #[async_trait] pub trait Client { - async fn address(&self) -> AccountId; + async fn address(&self) -> Result; async fn dkg_contract_address(&self) -> Result; diff --git a/nym-api/src/ecash/dkg/client.rs b/nym-api/src/ecash/dkg/client.rs index 5c8f993690..4cd8d41343 100644 --- a/nym-api/src/ecash/dkg/client.rs +++ b/nym-api/src/ecash/dkg/client.rs @@ -35,7 +35,7 @@ impl DkgClient { } } - pub(crate) async fn get_address(&self) -> AccountId { + pub(crate) async fn get_address(&self) -> Result { self.inner.address().await } @@ -53,7 +53,7 @@ impl DkgClient { pub(crate) async fn group_member(&self) -> Result { self.inner - .group_member(self.get_address().await.to_string()) + .group_member(self.get_address().await?.to_string()) .await } @@ -126,7 +126,7 @@ impl DkgClient { &self, epoch_id: EpochId, ) -> Result, EcashError> { - let address = self.inner.address().await; + let address = self.inner.address().await?; self.get_verification_key_share(epoch_id, address).await } @@ -138,7 +138,7 @@ impl DkgClient { } pub(crate) async fn get_vote(&self, proposal_id: u64) -> Result { - let address = self.get_address().await.to_string(); + let address = self.get_address().await?.to_string(); self.inner.get_vote(proposal_id, address).await } diff --git a/nym-api/src/ecash/dkg/dealing.rs b/nym-api/src/ecash/dkg/dealing.rs index a0ae4ac840..31da8a0ea5 100644 --- a/nym-api/src/ecash/dkg/dealing.rs +++ b/nym-api/src/ecash/dkg/dealing.rs @@ -155,7 +155,7 @@ impl DkgController { resharing: bool, ) -> Result<(), DealingGenerationError> { let dealing_state = self.state.dealing_exchange_state(epoch_id)?; - let address = self.dkg_client.get_address().await.to_string(); + let address = self.dkg_client.get_address().await?.to_string(); let status = self .dkg_client @@ -259,7 +259,7 @@ impl DkgController { .checked_sub(1) .expect("resharing epoch invariant has been broken"); - let address = self.dkg_client.get_address().await; + let address = self.dkg_client.get_address().await?; Ok(self .dkg_client .dealer_in_epoch(previous_epoch_id, address.to_string()) diff --git a/nym-api/src/ecash/dkg/key_derivation.rs b/nym-api/src/ecash/dkg/key_derivation.rs index f3e69e1fe6..46818547d4 100644 --- a/nym-api/src/ecash/dkg/key_derivation.rs +++ b/nym-api/src/ecash/dkg/key_derivation.rs @@ -494,7 +494,7 @@ impl DkgController { // submitted proposals and find the one with our address self.get_validation_proposals() .await? - .get(self.dkg_client.get_address().await.as_ref()) + .get(self.dkg_client.get_address().await?.as_ref()) .copied() .ok_or(KeyDerivationError::UnrecoverableProposalId) } diff --git a/nym-api/src/ecash/dkg/key_validation.rs b/nym-api/src/ecash/dkg/key_validation.rs index aff0ea90ac..cde57df982 100644 --- a/nym-api/src/ecash/dkg/key_validation.rs +++ b/nym-api/src/ecash/dkg/key_validation.rs @@ -155,7 +155,7 @@ impl DkgController { }; // if this is our share, obviously vote for yes without spending time on verification - if owner.as_ref() == self.dkg_client.get_address().await.as_ref() { + if owner.as_ref() == self.dkg_client.get_address().await?.as_ref() { votes.insert(*proposal_id, true); continue; } @@ -313,7 +313,7 @@ mod tests { exchange_dealings(&mut controllers, false).await; derive_keypairs(&mut controllers, false).await; - let first_dealer = controllers[0].dkg_client.get_address().await; + let first_dealer = controllers[0].dkg_client.get_address().await?; { let mut guard = chain.lock().unwrap(); @@ -365,8 +365,8 @@ mod tests { exchange_dealings(&mut controllers, false).await; derive_keypairs(&mut controllers, false).await; - let first_dealer = controllers[0].dkg_client.get_address().await; - let second_dealer = controllers[1].dkg_client.get_address().await; + let first_dealer = controllers[0].dkg_client.get_address().await?; + let second_dealer = controllers[1].dkg_client.get_address().await?; { let mut guard = chain.lock().unwrap(); diff --git a/nym-api/src/ecash/error.rs b/nym-api/src/ecash/error.rs index 7696234ea2..68fd79622f 100644 --- a/nym-api/src/ecash/error.rs +++ b/nym-api/src/ecash/error.rs @@ -25,6 +25,9 @@ pub enum EcashError { #[error(transparent)] IOError(#[from] std::io::Error), + #[error("this instance is running without on-chain signing capabilities so no transactions can be sent")] + ChainSignerNotEnabled, + #[error("this operation couldn't be completed as this nym-api is not an active ecash signer")] NotASigner, diff --git a/nym-api/src/ecash/state/mod.rs b/nym-api/src/ecash/state/mod.rs index b27280b6a6..f262177480 100644 --- a/nym-api/src/ecash/state/mod.rs +++ b/nym-api/src/ecash/state/mod.rs @@ -139,7 +139,9 @@ impl EcashState { .local .active_signer .get_or_init(epoch_id, || async { - let address = self.aux.client.address().await; + let Ok(address) = self.aux.client.address().await else { + return Ok(false); + }; let ecash_signers = self.aux.comm_channel.ecash_clients(epoch_id).await?; // check if any ecash signers for this epoch has the same cosmos address as this api @@ -246,7 +248,7 @@ impl EcashState { let threshold = self.aux.comm_channel.ecash_threshold(epoch_id).await?; // let mut shares = Mutex::new(Vec::with_capacity(all_apis.len())); - let cosmos_address = self.aux.client.address().await; + let cosmos_address = self.aux.client.address().await.ok(); let get_partial_signatures = |api: EcashApiClient| async { // move the api into the closure @@ -256,7 +258,7 @@ impl EcashState { // check if we're attempting to query ourselves, in that case just get local signature // rather than making the http query - let partial = if api.cosmos_address == cosmos_address { + let partial = if Some(api.cosmos_address) == cosmos_address { self.partial_coin_index_signatures(Some(epoch_id)) .await? .signatures @@ -380,7 +382,7 @@ impl EcashState { let all_apis = self.aux.comm_channel.ecash_clients(epoch_id).await?; let threshold = self.aux.comm_channel.ecash_threshold(epoch_id).await?; - let cosmos_address = self.aux.client.address().await; + let cosmos_address = self.aux.client.address().await.ok(); let get_partial_signatures = |api: EcashApiClient| async { // move the api into the closure @@ -390,7 +392,7 @@ impl EcashState { // check if we're attempting to query ourselves, in that case just get local signature // rather than making the http query - let partial = if api.cosmos_address == cosmos_address { + let partial = if Some(api.cosmos_address) == cosmos_address { self.partial_expiration_date_signatures(expiration_date) .await? .signatures diff --git a/nym-api/src/ecash/tests/fixtures.rs b/nym-api/src/ecash/tests/fixtures.rs index 80ee791770..b49b9fc17e 100644 --- a/nym-api/src/ecash/tests/fixtures.rs +++ b/nym-api/src/ecash/tests/fixtures.rs @@ -263,7 +263,7 @@ pub(crate) struct TestingDkgController { impl TestingDkgController { pub async fn address(&self) -> AccountId { - self.dkg_client.get_address().await + self.dkg_client.get_address().await.unwrap() } pub async fn cw_address(&self) -> Addr { diff --git a/nym-api/src/ecash/tests/helpers.rs b/nym-api/src/ecash/tests/helpers.rs index 73fac38606..458b57946e 100644 --- a/nym-api/src/ecash/tests/helpers.rs +++ b/nym-api/src/ecash/tests/helpers.rs @@ -51,7 +51,7 @@ pub(crate) async fn initialise_dkg(controllers: &mut [TestingDkgController], res // add every dealer to group contract for controller in controllers.iter() { - let address = controller.dkg_client.get_address().await; + let address = controller.dkg_client.get_address().await.unwrap(); let mut chain = controllers[0].chain_state.lock().unwrap(); chain.add_member(address.as_ref(), 10); } diff --git a/nym-api/src/ecash/tests/mod.rs b/nym-api/src/ecash/tests/mod.rs index 2b0ef471f9..9af4c78052 100644 --- a/nym-api/src/ecash/tests/mod.rs +++ b/nym-api/src/ecash/tests/mod.rs @@ -11,6 +11,7 @@ use crate::node_describe_cache::DescribedNodes; use crate::node_status_api::handlers::unstable; use crate::node_status_api::NodeStatusCache; use crate::nym_contract_cache::cache::NymContractCache; +use crate::status::ApiStatusState; use crate::support::caching::cache::SharedCache; use crate::support::config; use crate::support::http::state::{AppState, ForcedRefresh}; @@ -524,8 +525,8 @@ impl DummyClient { #[async_trait] impl super::client::Client for DummyClient { - async fn address(&self) -> AccountId { - self.validator_address.clone() + async fn address(&self) -> Result { + Ok(self.validator_address.clone()) } async fn dkg_contract_address(&self) -> Result { @@ -1262,7 +1263,7 @@ struct TestFixture { } impl TestFixture { - fn build_app_state(storage: NymApiStorage) -> AppState { + fn build_app_state(storage: NymApiStorage, ecash_state: EcashState) -> AppState { AppState { forced_refresh: ForcedRefresh::new(true), nym_contract_cache: NymContractCache::new(), @@ -1275,6 +1276,8 @@ impl TestFixture { NymNetworkDetails::new_empty(), ), node_info_cache: unstable::NodeInfoCache::default(), + api_status: ApiStatusState::new(None), + ecash_state: Arc::new(ecash_state), } } @@ -1337,8 +1340,8 @@ impl TestFixture { TestFixture { axum: TestServer::new( Router::new() - .nest("/v1/ecash", ecash_routes(Arc::new(ecash_state))) - .with_state(Self::build_app_state(storage.clone())), + .nest("/v1/ecash", ecash_routes()) + .with_state(Self::build_app_state(storage.clone(), ecash_state)), ) .unwrap(), storage, diff --git a/nym-api/src/epoch_operations/error.rs b/nym-api/src/epoch_operations/error.rs index 65fc822dff..a608817425 100644 --- a/nym-api/src/epoch_operations/error.rs +++ b/nym-api/src/epoch_operations/error.rs @@ -11,6 +11,9 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum RewardingError { + #[error("this instance is running without on-chain signing capabilities so no transactions can be sent")] + ChainSignerNotEnabled, + #[error("Our account ({our_address}) is not permitted to update rewarded set and perform rewarding. The allowed address is {allowed_address}")] Unauthorised { our_address: AccountId, diff --git a/nym-api/src/epoch_operations/mod.rs b/nym-api/src/epoch_operations/mod.rs index 6744679411..fe4fd5792d 100644 --- a/nym-api/src/epoch_operations/mod.rs +++ b/nym-api/src/epoch_operations/mod.rs @@ -134,9 +134,12 @@ impl EpochAdvancer { let epoch_status = self.nyxd_client.get_current_epoch_status().await?; if !epoch_status.is_in_progress() { - if epoch_status.being_advanced_by.as_str() - != self.nyxd_client.client_address().await.as_ref() - { + // SAFETY: before `EpochAdvancer` is started, `ensure_rewarding_permission` is called + // which is not allowed to progress if this instance is not using a signing client + #[allow(clippy::unwrap_used)] + let address = self.nyxd_client.client_address().await.unwrap(); + + if epoch_status.being_advanced_by.as_str() != address.as_ref() { // another nym-api is already handling error!("another nym-api ({}) is already advancing the epoch... but we shouldn't have other nym-apis yet!", epoch_status.being_advanced_by); return Ok(()); @@ -318,8 +321,10 @@ impl EpochAdvancer { pub(crate) async fn ensure_rewarding_permission( nyxd_client: &Client, ) -> Result<(), RewardingError> { + let Some(our_address) = nyxd_client.client_address().await else { + return Err(RewardingError::ChainSignerNotEnabled); + }; let allowed_address = nyxd_client.get_rewarding_validator_address().await?; - let our_address = nyxd_client.client_address().await; if allowed_address != our_address { Err(RewardingError::Unauthorised { our_address, diff --git a/nym-api/src/status/handlers.rs b/nym-api/src/status/handlers.rs index 7dc316aac2..8b177cadd6 100644 --- a/nym-api/src/status/handlers.rs +++ b/nym-api/src/status/handlers.rs @@ -4,37 +4,20 @@ use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; use crate::status::ApiStatusState; use crate::support::http::state::AppState; +use axum::extract::State; use axum::Json; use axum::Router; use nym_api_requests::models::{ApiHealthResponse, SignerInformationResponse}; use nym_bin_common::build_information::BinaryBuildInformationOwned; use nym_compact_ecash::Base58; -use std::sync::Arc; pub(crate) fn api_status_routes() -> Router { - let api_status_state = Arc::new(ApiStatusState::new()); - Router::new() - .route( - "/health", - axum::routing::get({ - let state = Arc::clone(&api_status_state); - || health(state) - }), - ) - .route( - "/build-information", - axum::routing::get({ - let state = Arc::clone(&api_status_state); - || build_information(state) - }), - ) + .route("/health", axum::routing::get(health)) + .route("/build-information", axum::routing::get(build_information)) .route( "/signer-information", - axum::routing::get({ - let state = Arc::clone(&api_status_state); - || signer_information(state) - }), + axum::routing::get(signer_information), ) } @@ -46,7 +29,7 @@ pub(crate) fn api_status_routes() -> Router { (status = 200, body = ApiHealthResponse) ) )] -async fn health(state: Arc) -> Json { +async fn health(State(state): State) -> Json { let uptime = state.startup_time.elapsed(); let health = ApiHealthResponse::new_healthy(uptime); Json(health) @@ -60,7 +43,9 @@ async fn health(state: Arc) -> Json { (status = 200, body = BinaryBuildInformationOwned) ) )] -async fn build_information(state: Arc) -> Json { +async fn build_information( + State(state): State, +) -> Json { Json(state.build_information.to_owned()) } @@ -73,7 +58,7 @@ async fn build_information(state: Arc) -> Json, + State(state): State, ) -> AxumResult> { let signer_state = state.signer_information.as_ref().ok_or_else(|| { AxumErrorResponse::internal_msg("this api does not expose zk-nym signing functionalities") diff --git a/nym-api/src/status/mod.rs b/nym-api/src/status/mod.rs index 0ed94a1819..f7ff611d6d 100644 --- a/nym-api/src/status/mod.rs +++ b/nym-api/src/status/mod.rs @@ -4,12 +4,25 @@ use crate::ecash; use nym_bin_common::bin_info; use nym_bin_common::build_information::BinaryBuildInformation; - +use std::ops::Deref; +use std::sync::Arc; use tokio::time::Instant; pub(crate) mod handlers; +#[derive(Clone)] pub(crate) struct ApiStatusState { + inner: Arc, +} + +impl Deref for ApiStatusState { + type Target = ApiStatusStateInner; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +pub(crate) struct ApiStatusStateInner { startup_time: Instant, build_information: BinaryBuildInformation, signer_information: Option, @@ -27,15 +40,13 @@ pub(crate) struct SignerState { } impl ApiStatusState { - pub fn new() -> Self { + pub fn new(signer_information: Option) -> Self { ApiStatusState { - startup_time: Instant::now(), - build_information: bin_info!(), - signer_information: None, + inner: Arc::new(ApiStatusStateInner { + startup_time: Instant::now(), + build_information: bin_info!(), + signer_information, + }), } } - - pub fn add_zk_nym_signer(&mut self, signer_information: SignerState) { - self.signer_information = Some(signer_information) - } } diff --git a/nym-api/src/support/cli/run.rs b/nym-api/src/support/cli/run.rs index e7bf4c0776..6436f19be1 100644 --- a/nym-api/src/support/cli/run.rs +++ b/nym-api/src/support/cli/run.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::circulating_supply_api::cache::CirculatingSupplyCache; -use crate::ecash::api_routes::handlers::ecash_routes; use crate::ecash::client::Client; use crate::ecash::comm::QueryCommunicationChannel; use crate::ecash::dkg::controller::keys::{ @@ -138,8 +137,6 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result::new(); let node_info_cache = unstable::NodeInfoCache::default(); - let mut status_state = ApiStatusState::new(); - let ecash_contract = nyxd_client .get_ecash_contract_address() .await @@ -161,8 +158,8 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result anyhow::Result anyhow::Result) -> Self { Self { unfinished_router: self.unfinished_router.nest(path, router), diff --git a/nym-api/src/support/http/state.rs b/nym-api/src/support/http/state.rs index f45d25bbd2..6c277441ea 100644 --- a/nym-api/src/support/http/state.rs +++ b/nym-api/src/support/http/state.rs @@ -2,15 +2,18 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::circulating_supply_api::cache::CirculatingSupplyCache; +use crate::ecash::state::EcashState; use crate::network::models::NetworkDetails; use crate::node_describe_cache::DescribedNodes; use crate::node_status_api::handlers::unstable; use crate::node_status_api::models::AxumErrorResponse; use crate::node_status_api::NodeStatusCache; use crate::nym_contract_cache::cache::{CachedRewardedSet, NymContractCache}; +use crate::status::ApiStatusState; use crate::support::caching::cache::SharedCache; use crate::support::caching::Cache; use crate::support::storage; +use axum::extract::FromRef; use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation}; use nym_mixnet_contract_common::NodeId; use nym_task::TaskManager; @@ -80,6 +83,21 @@ pub(crate) struct AppState { pub(crate) described_nodes_cache: SharedCache, pub(crate) network_details: NetworkDetails, pub(crate) node_info_cache: unstable::NodeInfoCache, + pub(crate) api_status: ApiStatusState, + // todo: refactor it into inner: Arc + pub(crate) ecash_state: Arc, +} + +impl FromRef for ApiStatusState { + fn from_ref(app_state: &AppState) -> Self { + app_state.api_status.clone() + } +} + +impl FromRef for Arc { + fn from_ref(app_state: &AppState) -> Self { + app_state.ecash_state.clone() + } } #[derive(Clone)] diff --git a/nym-api/src/support/nyxd/mod.rs b/nym-api/src/support/nyxd/mod.rs index 012f9d369d..5e129058b9 100644 --- a/nym-api/src/support/nyxd/mod.rs +++ b/nym-api/src/support/nyxd/mod.rs @@ -77,16 +77,6 @@ macro_rules! nyxd_query { }}; } -macro_rules! nyxd_signing_shared { - ($self:expr, $($op:tt)*) => {{ - let guard = $self.inner.read().await; - match &*guard { - $crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*, - $crate::support::nyxd::ClientInner::Query(_) => panic!("attempted to use a signing method on a query client"), - } - }}; -} - macro_rules! nyxd_signing { ($self:expr, $($op:tt)*) => {{ let guard = $self.inner.write().await; @@ -140,13 +130,19 @@ impl Client { self.inner.read().await } - pub(crate) async fn client_address(&self) -> AccountId { - nyxd_signing_shared!(self, address()) + pub(crate) async fn client_address(&self) -> Option { + let guard = self.inner.read().await; + match &*guard { + ClientInner::Signing(client) => Some(client.address()), + ClientInner::Query(_) => None, + } } pub(crate) async fn balance>(&self, denom: S) -> Result { - let address = self.client_address().await; let denom = denom.into(); + let Some(address) = self.client_address().await else { + return Ok(Coin::new(0, denom)); + }; let balance = nyxd_query!(self, get_balance(&address, denom.clone()).await?); match balance { @@ -394,8 +390,10 @@ impl Client { #[async_trait] impl crate::ecash::client::Client for Client { - async fn address(&self) -> AccountId { - self.client_address().await + async fn address(&self) -> Result { + self.client_address() + .await + .ok_or(EcashError::ChainSignerNotEnabled) } async fn dkg_contract_address(&self) -> Result { @@ -481,7 +479,7 @@ impl crate::ecash::client::Client for Client { async fn get_self_registered_dealer_details( &self, ) -> crate::ecash::error::Result { - let self_address = &self.address().await; + let self_address = &self.address().await?; Ok(nyxd_query!(self, get_dealer_details(self_address).await?)) } diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 16f66a6fee..92dec65fea 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node" -version = "1.1.11" +version = "1.1.12" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/nym-node/src/config/mixnode.rs b/nym-node/src/config/mixnode.rs index 92f9279c2f..a62ee4b075 100644 --- a/nym-node/src/config/mixnode.rs +++ b/nym-node/src/config/mixnode.rs @@ -36,7 +36,7 @@ impl MixnodeConfig { } #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] +#[serde(deny_unknown_fields, default)] pub struct Verloc { /// Socket address this node will use for binding its verloc API. /// default: `0.0.0.0:1790` diff --git a/nym-node/src/config/old_configs/old_config_v1.rs b/nym-node/src/config/old_configs/old_config_v1.rs index a09eee36e7..c67f9358ae 100644 --- a/nym-node/src/config/old_configs/old_config_v1.rs +++ b/nym-node/src/config/old_configs/old_config_v1.rs @@ -11,6 +11,7 @@ use nym_pemstore::store_keypair; use old_configs::old_config_v2::*; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; +use tracing::instrument; #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] @@ -756,15 +757,16 @@ fn initialise(config: &WireguardV2) -> std::io::Result<()> { Ok(()) } +#[instrument(skip_all)] pub async fn try_upgrade_config_v1>( path: P, prev_config: Option, ) -> Result { - tracing::debug!("Updating from 1.1.2"); + debug!("attempting to load v1 config..."); let old_cfg = if let Some(prev_config) = prev_config { prev_config } else { - ConfigV1::read_from_path(&path)? + ConfigV1::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))? }; let wireguard = WireguardV2 { enabled: old_cfg.wireguard.enabled, diff --git a/nym-node/src/config/old_configs/old_config_v2.rs b/nym-node/src/config/old_configs/old_config_v2.rs index 28e35e66bc..2082ed4f70 100644 --- a/nym-node/src/config/old_configs/old_config_v2.rs +++ b/nym-node/src/config/old_configs/old_config_v2.rs @@ -16,6 +16,7 @@ use nym_sphinx_acknowledgements::AckKey; use old_configs::old_config_v3::*; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; +use tracing::instrument; #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] @@ -786,15 +787,16 @@ pub async fn initialise( Ok(()) } +#[instrument(skip_all)] pub async fn try_upgrade_config_v2>( path: P, prev_config: Option, ) -> Result { - tracing::debug!("Updating from 1.1.3"); + debug!("attempting to load v2 config..."); let old_cfg = if let Some(prev_config) = prev_config { prev_config } else { - ConfigV2::read_from_path(&path)? + ConfigV2::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))? }; let authenticator_paths = AuthenticatorPathsV3::new( diff --git a/nym-node/src/config/old_configs/old_config_v3.rs b/nym-node/src/config/old_configs/old_config_v3.rs index 01ce8a9e49..1301230167 100644 --- a/nym-node/src/config/old_configs/old_config_v3.rs +++ b/nym-node/src/config/old_configs/old_config_v3.rs @@ -17,6 +17,7 @@ use old_configs::old_config_v4::*; use persistence::*; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; +use tracing::instrument; #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] @@ -938,15 +939,16 @@ pub async fn initialise( Ok(()) } +#[instrument(skip_all)] pub async fn try_upgrade_config_v3>( path: P, prev_config: Option, ) -> Result { - tracing::debug!("Updating from 1.1.4"); + debug!("attempting to load v3 config..."); let old_cfg = if let Some(prev_config) = prev_config { prev_config } else { - ConfigV3::read_from_path(&path)? + ConfigV3::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))? }; let exit_gateway_paths = ExitGatewayPaths::new( diff --git a/nym-node/src/config/old_configs/old_config_v4.rs b/nym-node/src/config/old_configs/old_config_v4.rs index 8e8e3758d0..03a32b2b39 100644 --- a/nym-node/src/config/old_configs/old_config_v4.rs +++ b/nym-node/src/config/old_configs/old_config_v4.rs @@ -20,6 +20,7 @@ use old_configs::old_config_v5::*; use persistence::*; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; +use tracing::instrument; #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] @@ -203,7 +204,7 @@ pub struct MixnetV4 { /// If applicable, custom port announced in the self-described API that other clients and nodes /// will use. /// Useful when the node is behind a proxy. - #[serde(deserialize_with = "de_maybe_port")] + #[serde(deserialize_with = "de_maybe_port", default)] pub announce_port: Option, /// Addresses to nym APIs from which the node gets the view of the network. @@ -385,7 +386,7 @@ pub struct VerlocV4 { /// default: `0.0.0.0:1790` pub bind_address: SocketAddr, - #[serde(deserialize_with = "de_maybe_port")] + #[serde(deserialize_with = "de_maybe_port", default)] pub announce_port: Option, #[serde(default)] @@ -1060,15 +1061,16 @@ pub async fn initialise( Ok(()) } +#[instrument(skip_all)] pub async fn try_upgrade_config_v4>( path: P, prev_config: Option, ) -> Result { - tracing::debug!("Updating from 1.1.5"); + debug!("attempting to load v4 config..."); let old_cfg = if let Some(prev_config) = prev_config { prev_config } else { - ConfigV4::read_from_path(&path)? + ConfigV4::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))? }; let exit_gateway_paths = ExitGatewayPaths::new( diff --git a/nym-node/src/config/old_configs/old_config_v5.rs b/nym-node/src/config/old_configs/old_config_v5.rs index 936882f113..4a1cb57c64 100644 --- a/nym-node/src/config/old_configs/old_config_v5.rs +++ b/nym-node/src/config/old_configs/old_config_v5.rs @@ -25,6 +25,7 @@ use nym_sphinx_acknowledgements::AckKey; use persistence::*; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; +use tracing::instrument; #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] @@ -208,7 +209,7 @@ pub struct MixnetV5 { /// If applicable, custom port announced in the self-described API that other clients and nodes /// will use. /// Useful when the node is behind a proxy. - #[serde(deserialize_with = "de_maybe_port")] + #[serde(deserialize_with = "de_maybe_port", default)] pub announce_port: Option, /// Addresses to nym APIs from which the node gets the view of the network. @@ -390,7 +391,7 @@ pub struct VerlocV5 { /// default: `0.0.0.0:1790` pub bind_address: SocketAddr, - #[serde(deserialize_with = "de_maybe_port")] + #[serde(deserialize_with = "de_maybe_port", default)] pub announce_port: Option, #[serde(default)] @@ -1069,15 +1070,16 @@ pub async fn initialise( Ok(()) } +#[instrument(skip_all)] pub async fn try_upgrade_config_v5>( path: P, prev_config: Option, ) -> Result { - tracing::debug!("Updating from 1.1.6"); + debug!("attempting to load v5 config..."); let old_cfg = if let Some(prev_config) = prev_config { prev_config } else { - ConfigV5::read_from_path(&path)? + ConfigV5::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))? }; let (private_ipv4, private_ipv6) = match old_cfg.wireguard.private_ip { diff --git a/nym-node/src/config/upgrade_helpers.rs b/nym-node/src/config/upgrade_helpers.rs index bae75192ec..c7596b69a6 100644 --- a/nym-node/src/config/upgrade_helpers.rs +++ b/nym-node/src/config/upgrade_helpers.rs @@ -5,6 +5,7 @@ use crate::config::old_configs::*; use crate::config::Config; use crate::error::NymNodeError; use std::path::Path; +use tracing::debug; // currently there are no upgrades async fn try_upgrade_config(path: &Path) -> Result<(), NymNodeError> { @@ -24,7 +25,10 @@ async fn try_upgrade_config(path: &Path) -> Result<(), NymNodeError> { pub async fn try_load_current_config>( config_path: P, ) -> Result { - if let Ok(cfg) = Config::read_from_toml_file(config_path.as_ref()) { + if let Ok(cfg) = Config::read_from_toml_file(config_path.as_ref()) + .inspect_err(|err| debug!("didn't manage to load the current config: {err}")) + { + debug!("managed to load the current version of the config"); return Ok(cfg); } diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index a419abd4f6..1508c7d775 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-network-requester" license = "GPL-3.0" -version = "1.1.45" +version = "1.1.46" authors.workspace = true edition.workspace = true rust-version = "1.70" diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index 2a4bdf0186..c156e28679 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.44" +version = "1.1.45" authors.workspace = true edition = "2021" license.workspace = true diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml index a1f0590c8d..d44e9fc12b 100644 --- a/tools/nymvisor/Cargo.toml +++ b/tools/nymvisor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nymvisor" -version = "0.1.9" +version = "0.1.10" authors.workspace = true repository.workspace = true homepage.workspace = true