diff --git a/Cargo.lock b/Cargo.lock index 631d45ae7e..c8015cdf21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4502,6 +4502,7 @@ dependencies = [ "nym-pemstore", "nym-serde-helpers", "nym-sphinx", + "nym-statistics-common", "nym-task", "nym-topology", "nym-types", @@ -5496,6 +5497,7 @@ dependencies = [ "nym-network-defaults", "nym-pemstore", "nym-sphinx", + "nym-statistics-common", "nym-task", "nym-validator-client", "rand", @@ -10946,6 +10948,7 @@ dependencies = [ "nym-gateway-client", "nym-sphinx", "nym-sphinx-acknowledgements", + "nym-statistics-common", "nym-task", "nym-topology", "nym-validator-client", diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index b9ea8ef8ae..b91e532376 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -358,6 +358,7 @@ where bandwidth_controller: Option>, details_store: &S::GatewaysDetailsStore, packet_router: PacketRouter, + stats_reporter: ClientStatsSender, shutdown: TaskClient, ) -> Result, ClientCoreError> where @@ -373,7 +374,12 @@ where let mut gateway_client = if let Some(existing_client) = initialisation_result.authenticated_ephemeral_client { - existing_client.upgrade(packet_router, bandwidth_controller, shutdown) + existing_client.upgrade( + packet_router, + bandwidth_controller, + stats_reporter, + shutdown, + ) } else { let cfg = GatewayConfig::new( details.gateway_id, @@ -394,6 +400,7 @@ where Some(details.shared_key), packet_router, bandwidth_controller, + stats_reporter, shutdown, ) }; @@ -446,6 +453,7 @@ where Ok(gateway_client) } + #[allow(clippy::too_many_arguments)] async fn setup_gateway_transceiver( custom_gateway_transceiver: Option>, config: &Config, @@ -453,6 +461,7 @@ where bandwidth_controller: Option>, details_store: &S::GatewaysDetailsStore, packet_router: PacketRouter, + stats_reporter: ClientStatsSender, mut shutdown: TaskClient, ) -> Result, ClientCoreError> where @@ -483,6 +492,7 @@ where bandwidth_controller, details_store, packet_router, + stats_reporter, shutdown, ) .await?; @@ -766,6 +776,7 @@ where bandwidth_controller, &details_store, gateway_packet_router, + stats_reporter.clone(), shutdown.fork("gateway_transceiver"), ) .await?; diff --git a/common/client-libs/gateway-client/Cargo.toml b/common/client-libs/gateway-client/Cargo.toml index 81b9b4d7f4..6aa2795d3f 100644 --- a/common/client-libs/gateway-client/Cargo.toml +++ b/common/client-libs/gateway-client/Cargo.toml @@ -29,6 +29,7 @@ nym-crypto = { path = "../../crypto" } nym-gateway-requests = { path = "../../gateway-requests" } nym-network-defaults = { path = "../../network-defaults" } nym-sphinx = { path = "../../nymsphinx" } +nym-statistics-common = { path = "../../statistics" } nym-pemstore = { path = "../../pemstore" } nym-validator-client = { path = "../validator-client", default-features = false } nym-task = { path = "../../task" } diff --git a/common/client-libs/gateway-client/src/client/mod.rs b/common/client-libs/gateway-client/src/client/mod.rs index 58e061887e..f9b8ec34d2 100644 --- a/common/client-libs/gateway-client/src/client/mod.rs +++ b/common/client-libs/gateway-client/src/client/mod.rs @@ -25,6 +25,8 @@ use nym_gateway_requests::{ CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION, }; use nym_sphinx::forwarding::packet::MixPacket; +use nym_statistics_common::clients::credential::CredentialStatsEvent; +use nym_statistics_common::clients::ClientStatsSender; use nym_task::TaskClient; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; use rand::rngs::OsRng; @@ -94,6 +96,7 @@ pub struct GatewayClient { connection: SocketState, packet_router: PacketRouter, bandwidth_controller: Option>, + stats_reporter: ClientStatsSender, // currently unused (but populated) negotiated_protocol: Option, @@ -103,6 +106,7 @@ pub struct GatewayClient { } impl GatewayClient { + #[allow(clippy::too_many_arguments)] pub fn new( cfg: GatewayClientConfig, gateway_config: GatewayConfig, @@ -111,6 +115,7 @@ impl GatewayClient { shared_key: Option>, packet_router: PacketRouter, bandwidth_controller: Option>, + stats_reporter: ClientStatsSender, task_client: TaskClient, ) -> Self { GatewayClient { @@ -124,6 +129,7 @@ impl GatewayClient { connection: SocketState::NotConnected, packet_router, bandwidth_controller, + stats_reporter, negotiated_protocol: None, task_client, } @@ -714,6 +720,7 @@ impl GatewayClient { { // TODO: make it configurable const TICKETS_TO_SPEND: u32 = 1; + const MIXNET_TICKET: TicketType = TicketType::V1MixnetEntry; if !self.authenticated { return Err(GatewayClientError::NotAuthenticated); @@ -750,14 +757,23 @@ impl GatewayClient { let prepared_credential = self .unchecked_bandwidth_controller() .prepare_ecash_ticket( - TicketType::V1MixnetEntry, + MIXNET_TICKET, self.gateway_identity.to_bytes(), TICKETS_TO_SPEND, ) .await?; match self.claim_ecash_bandwidth(prepared_credential.data).await { - Ok(_) => Ok(()), + Ok(_) => { + self.stats_reporter.report( + CredentialStatsEvent::TicketSpent { + typ: MIXNET_TICKET, + amount: TICKETS_TO_SPEND, + } + .into(), + ); + Ok(()) + } Err(err) => { error!("failed to claim ecash bandwidth with the gateway...: {err}"); if err.is_ticket_replay() { @@ -1030,6 +1046,7 @@ impl GatewayClient { connection: SocketState::NotConnected, packet_router, bandwidth_controller: None, + stats_reporter: ClientStatsSender::new(None), negotiated_protocol: None, task_client, } @@ -1039,6 +1056,7 @@ impl GatewayClient { self, packet_router: PacketRouter, bandwidth_controller: Option>, + stats_reporter: ClientStatsSender, task_client: TaskClient, ) -> GatewayClient { // invariants that can't be broken @@ -1058,6 +1076,7 @@ impl GatewayClient { connection: self.connection, packet_router, bandwidth_controller, + stats_reporter, negotiated_protocol: self.negotiated_protocol, task_client, } diff --git a/common/statistics/src/clients/credential.rs b/common/statistics/src/clients/credential.rs new file mode 100644 index 0000000000..f09affc88a --- /dev/null +++ b/common/statistics/src/clients/credential.rs @@ -0,0 +1,52 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use super::ClientStatsEvents; + +use nym_credentials_interface::TicketType; +use serde::{Deserialize, Serialize}; + +#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)] +pub(crate) struct CredentialStats { + mixnet_entry_spent: u32, + vpn_entry_spent: u32, + mixnet_exit_spent: u32, + vpn_exit_spent: u32, +} + +/// Event space for Nym API statistics tracking +#[derive(Debug)] +pub enum CredentialStatsEvent { + /// ecash ticket was spend + TicketSpent { typ: TicketType, amount: u32 }, +} + +impl From for ClientStatsEvents { + fn from(event: CredentialStatsEvent) -> ClientStatsEvents { + ClientStatsEvents::Credential(event) + } +} + +/// Nym API statistics tracking object +#[derive(Default)] +pub struct CredentialStatsControl { + // Keep track of packet statistics over time + stats: CredentialStats, +} + +impl CredentialStatsControl { + pub(crate) fn handle_event(&mut self, event: CredentialStatsEvent) { + match event { + CredentialStatsEvent::TicketSpent { typ, amount } => match typ { + TicketType::V1MixnetEntry => self.stats.mixnet_entry_spent += amount, + TicketType::V1MixnetExit => self.stats.mixnet_exit_spent += amount, + TicketType::V1WireguardEntry => self.stats.vpn_entry_spent += amount, + TicketType::V1WireguardExit => self.stats.vpn_exit_spent += amount, + }, + } + } + + pub(crate) fn report(&self) -> CredentialStats { + self.stats + } +} diff --git a/common/statistics/src/clients/mod.rs b/common/statistics/src/clients/mod.rs index 1f82be1b04..c3d6e5c350 100644 --- a/common/statistics/src/clients/mod.rs +++ b/common/statistics/src/clients/mod.rs @@ -16,6 +16,8 @@ pub mod nym_api_statistics; /// Packet count based statistics. pub mod packet_statistics; +pub mod credential; + /// Channel receiving generic stats events to be used by a statistics aggregator. pub type ClientStatsReceiver = tokio::sync::mpsc::UnboundedReceiver; @@ -49,6 +51,8 @@ pub enum ClientStatsEvents { GatewayConn(gateway_conn_statistics::GatewayStatsEvent), /// Nym API connection events NymApi(nym_api_statistics::NymApiStatsEvent), + /// Credential events + Credential(credential::CredentialStatsEvent), } /// Controls stats event handling and reporting @@ -63,6 +67,7 @@ pub struct ClientStatsController { packet_stats: packet_statistics::PacketStatisticsControl, gateway_conn_stats: gateway_conn_statistics::GatewayStatsControl, nym_api_stats: nym_api_statistics::NymApiStatsControl, + credential_stats: credential::CredentialStatsControl, } impl ClientStatsController { @@ -76,6 +81,7 @@ impl ClientStatsController { packet_stats: Default::default(), gateway_conn_stats: Default::default(), nym_api_stats: Default::default(), + credential_stats: Default::default(), } } /// Returns a static ClientStatsReport that can be sent somewhere @@ -88,6 +94,7 @@ impl ClientStatsController { packet_stats: self.packet_stats.report(), gateway_conn_stats: self.gateway_conn_stats.report(), nym_api_stats: self.nym_api_stats.report(), + credential_stats: self.credential_stats.report(), } } @@ -97,6 +104,7 @@ impl ClientStatsController { ClientStatsEvents::PacketStatistics(event) => self.packet_stats.handle_event(event), ClientStatsEvents::GatewayConn(event) => self.gateway_conn_stats.handle_event(event), ClientStatsEvents::NymApi(event) => self.nym_api_stats.handle_event(event), + ClientStatsEvents::Credential(event) => self.credential_stats.handle_event(event), } } @@ -106,6 +114,7 @@ impl ClientStatsController { pub fn reset(&mut self) { self.nym_api_stats = Default::default(); self.gateway_conn_stats = Default::default(); + self.credential_stats = Default::default(); //no periodic reset for packet stats self.last_update_time = ClientStatsController::get_update_time(); diff --git a/common/statistics/src/report.rs b/common/statistics/src/report.rs index 0ca15f9d80..31a61db86c 100644 --- a/common/statistics/src/report.rs +++ b/common/statistics/src/report.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::clients::{ - gateway_conn_statistics::GatewayStats, nym_api_statistics::NymApiStats, - packet_statistics::PacketStatistics, + credential::CredentialStats, gateway_conn_statistics::GatewayStats, + nym_api_statistics::NymApiStats, packet_statistics::PacketStatistics, }; use super::error::StatsError; @@ -22,6 +22,7 @@ pub struct ClientStatsReport { pub(crate) packet_stats: PacketStatistics, pub(crate) gateway_conn_stats: GatewayStats, pub(crate) nym_api_stats: NymApiStats, + pub(crate) credential_stats: CredentialStats, } impl From for Vec { diff --git a/common/wasm/client-core/Cargo.toml b/common/wasm/client-core/Cargo.toml index 015ff09a82..1b0b3df67f 100644 --- a/common/wasm/client-core/Cargo.toml +++ b/common/wasm/client-core/Cargo.toml @@ -30,6 +30,7 @@ nym-crypto = { path = "../../crypto", features = ["asymmetric", "serde"] } nym-gateway-client = { path = "../../client-libs/gateway-client", default-features = false, features = ["wasm"] } nym-sphinx = { path = "../../nymsphinx" } nym-sphinx-acknowledgements = { path = "../../nymsphinx/acknowledgements", features = ["serde"]} +nym-statistics-common = { path = "../../statistics" } nym-task = { path = "../../task" } nym-topology = { path = "../../topology", features = ["serializable", "wasm-serde-types"] } nym-validator-client = { path = "../../client-libs/validator-client", default-features = false } diff --git a/common/wasm/client-core/src/lib.rs b/common/wasm/client-core/src/lib.rs index c985e92587..8569dd7ad9 100644 --- a/common/wasm/client-core/src/lib.rs +++ b/common/wasm/client-core/src/lib.rs @@ -26,6 +26,7 @@ pub use nym_sphinx::{ params::PacketType, receiver::ReconstructedMessage, }; +pub use nym_statistics_common::clients::ClientStatsSender; pub use nym_task; pub use nym_topology::{HardcodedTopologyProvider, MixLayer, NymTopology, TopologyProvider}; pub use nym_validator_client::nym_api::Client as ApiClient; diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index d1078c997c..246d8f60da 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -119,6 +119,7 @@ nym-node-requests = { path = "../nym-node/nym-node-requests" } 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-statistics-common = {path ="../common/statistics" } [features] no-reward = [] diff --git a/nym-api/src/network_monitor/monitor/sender.rs b/nym-api/src/network_monitor/monitor/sender.rs index 0b371f1003..ac69a0bc45 100644 --- a/nym-api/src/network_monitor/monitor/sender.rs +++ b/nym-api/src/network_monitor/monitor/sender.rs @@ -213,6 +213,7 @@ impl PacketSender { None, gateway_packet_router, Some(fresh_gateway_client_data.bandwidth_controller.clone()), + nym_statistics_common::clients::ClientStatsSender::new(None), task_client, ); diff --git a/wasm/node-tester/src/tester.rs b/wasm/node-tester/src/tester.rs index 5d4958fa9f..c370aa7c2b 100644 --- a/wasm/node-tester/src/tester.rs +++ b/wasm/node-tester/src/tester.rs @@ -34,9 +34,9 @@ use wasm_client_core::helpers::{ use wasm_client_core::storage::ClientStorage; use wasm_client_core::topology::SerializableNymTopology; use wasm_client_core::{ - nym_task, BandwidthController, ClientKeys, GatewayClient, GatewayClientConfig, GatewayConfig, - IdentityKey, InitialisationResult, NodeIdentity, NymTopology, QueryReqwestRpcNyxdClient, - Recipient, + nym_task, BandwidthController, ClientKeys, ClientStatsSender, GatewayClient, + GatewayClientConfig, GatewayConfig, IdentityKey, InitialisationResult, NodeIdentity, + NymTopology, QueryReqwestRpcNyxdClient, Recipient, }; use wasm_utils::check_promise_result; use wasm_utils::error::PromisableResult; @@ -194,6 +194,7 @@ impl NymNodeTesterBuilder { existing_client.upgrade( packet_router, self.bandwidth_controller.take(), + ClientStatsSender::new(None), gateway_task, ) } else { @@ -209,6 +210,7 @@ impl NymNodeTesterBuilder { Some(gateway_info.shared_key), packet_router, self.bandwidth_controller.take(), + ClientStatsSender::new(None), gateway_task, ) };