From 52dff832ee79d6a48d1e5ce7534622fcc7004dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 9 Sep 2024 10:59:48 +0000 Subject: [PATCH] Move cred verification to common crate --- Cargo.lock | 32 +- Cargo.toml | 2 +- common/credential-verification/Cargo.toml | 33 ++ .../src/bandwidth_storage_manager.rs | 148 ++++++++ .../src/client_bandwidth.rs | 57 +++ .../src}/ecash/credential_sender.rs | 26 +- .../src}/ecash/double_spending.rs | 6 +- .../src}/ecash/error.rs | 0 .../src}/ecash/helpers.rs | 0 .../credential-verification/src}/ecash/mod.rs | 15 +- .../src}/ecash/state.rs | 8 +- common/credential-verification/src/error.rs | 57 +++ common/credential-verification/src/lib.rs | 156 ++++++++ common/credentials-interface/src/lib.rs | 21 ++ gateway/Cargo.toml | 7 +- gateway/src/error.rs | 13 +- gateway/src/node/client_handling/bandwidth.rs | 30 -- gateway/src/node/client_handling/mod.rs | 5 - .../client_handling/websocket/common_state.rs | 3 +- .../connection_handler/authenticated.rs | 336 ++---------------- .../websocket/connection_handler/fresh.rs | 6 +- .../websocket/connection_handler/mod.rs | 54 +-- gateway/src/node/mod.rs | 23 +- 23 files changed, 577 insertions(+), 461 deletions(-) create mode 100644 common/credential-verification/Cargo.toml create mode 100644 common/credential-verification/src/bandwidth_storage_manager.rs create mode 100644 common/credential-verification/src/client_bandwidth.rs rename {gateway/src/node/client_handling/websocket/connection_handler => common/credential-verification/src}/ecash/credential_sender.rs (97%) rename {gateway/src/node/client_handling/websocket/connection_handler => common/credential-verification/src}/ecash/double_spending.rs (94%) rename {gateway/src/node/client_handling/websocket/connection_handler => common/credential-verification/src}/ecash/error.rs (100%) rename {gateway/src/node/client_handling/websocket/connection_handler => common/credential-verification/src}/ecash/helpers.rs (100%) rename {gateway/src/node/client_handling/websocket/connection_handler => common/credential-verification/src}/ecash/mod.rs (93%) rename {gateway/src/node/client_handling/websocket/connection_handler => common/credential-verification/src}/ecash/state.rs (97%) create mode 100644 common/credential-verification/src/error.rs create mode 100644 common/credential-verification/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 4efaa347bd..4fcbad548b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4740,6 +4740,31 @@ dependencies = [ "tokio", ] +[[package]] +name = "nym-credential-verification" +version = "0.1.0" +dependencies = [ + "bs58", + "cosmwasm-std", + "cw-utils", + "futures", + "nym-api-requests", + "nym-credentials", + "nym-credentials-interface", + "nym-ecash-contract-common", + "nym-ecash-double-spending", + "nym-gateway-requests", + "nym-gateway-storage", + "nym-task", + "nym-validator-client", + "rand", + "si-scale", + "thiserror", + "time", + "tokio", + "tracing", +] + [[package]] name = "nym-credentials" version = "0.1.0" @@ -4909,12 +4934,9 @@ dependencies = [ "anyhow", "async-trait", "bip39", - "bloomfilter", "bs58", "clap 4.5.16", "colored", - "cosmwasm-std", - "cw-utils", "dashmap", "defguard_wireguard_rs", "dirs 4.0.0", @@ -4926,11 +4948,10 @@ dependencies = [ "nym-authenticator", "nym-bin-common", "nym-config", + "nym-credential-verification", "nym-credentials", "nym-credentials-interface", "nym-crypto", - "nym-ecash-contract-common", - "nym-ecash-double-spending", "nym-gateway-requests", "nym-gateway-storage", "nym-ip-packet-router", @@ -4954,7 +4975,6 @@ dependencies = [ "sqlx", "subtle-encoding", "thiserror", - "time", "tokio", "tokio-stream", "tokio-tungstenite", diff --git a/Cargo.toml b/Cargo.toml index 2be451eb47..76d5b19e82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,7 +127,7 @@ members = [ "wasm/node-tester", "wasm/zknym-lib", "tools/internal/testnet-manager", - "tools/internal/testnet-manager/dkg-bypass-contract", + "tools/internal/testnet-manager/dkg-bypass-contract", "common/credential-verification", ] default-members = [ diff --git a/common/credential-verification/Cargo.toml b/common/credential-verification/Cargo.toml new file mode 100644 index 0000000000..8585ad2cfa --- /dev/null +++ b/common/credential-verification/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "nym-credential-verification" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +readme.workspace = true + +[dependencies] +bs58 = { workspace = true } +cosmwasm-std = { workspace = true } +cw-utils = { workspace = true } +futures = { workspace = true } +rand = { workspace = true } +si-scale = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +time = { workspace = true } +tracing = { workspace = true } + +nym-api-requests = { path = "../../nym-api/nym-api-requests" } +nym-credentials = { path = "../credentials" } +nym-credentials-interface = { path = "../credentials-interface" } +nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" } +nym-ecash-double-spending = { path = "../ecash-double-spending" } +nym-gateway-requests = { path = "../gateway-requests" } +nym-gateway-storage = { path = "../gateway-storage" } +nym-task = { path = "../task" } +nym-validator-client = { path = "../client-libs/validator-client" } diff --git a/common/credential-verification/src/bandwidth_storage_manager.rs b/common/credential-verification/src/bandwidth_storage_manager.rs new file mode 100644 index 0000000000..3a67d43743 --- /dev/null +++ b/common/credential-verification/src/bandwidth_storage_manager.rs @@ -0,0 +1,148 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_credentials::ecash::utils::ecash_today; +use nym_credentials_interface::Bandwidth; +use nym_gateway_requests::ServerResponse; +use nym_gateway_storage::Storage; +use si_scale::helpers::bibytes2; +use time::OffsetDateTime; +use tracing::*; + +use crate::error::*; +use crate::BandwidthFlushingBehaviourConfig; +use crate::ClientBandwidth; + +const FREE_TESTNET_BANDWIDTH_VALUE: Bandwidth = Bandwidth::new_unchecked(64 * 1024 * 1024 * 1024); // 64GB + +#[derive(Clone)] +pub struct BandwidthStorageManager { + pub(crate) storage: S, + pub(crate) client_bandwidth: ClientBandwidth, + pub(crate) client_id: i64, + pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig, + pub(crate) only_coconut_credentials: bool, +} + +impl BandwidthStorageManager { + pub fn new( + storage: S, + client_bandwidth: ClientBandwidth, + client_id: i64, + bandwidth_cfg: BandwidthFlushingBehaviourConfig, + only_coconut_credentials: bool, + ) -> Self { + BandwidthStorageManager { + storage, + client_bandwidth, + client_id, + bandwidth_cfg, + only_coconut_credentials, + } + } + + async fn sync_expiration(&mut self) -> Result<()> { + self.storage + .set_expiration(self.client_id, self.client_bandwidth.bandwidth.expiration) + .await?; + Ok(()) + } + + pub async fn handle_claim_testnet_bandwidth(&mut self) -> Result { + debug!("handling testnet bandwidth request"); + + if self.only_coconut_credentials { + return Err(Error::OnlyCoconutCredentials); + } + + self.increase_bandwidth(FREE_TESTNET_BANDWIDTH_VALUE, ecash_today()) + .await?; + let available_total = self.client_bandwidth.bandwidth.bytes; + + Ok(ServerResponse::Bandwidth { available_total }) + } + + #[instrument(skip_all)] + pub async fn try_use_bandwidth(&mut self, required_bandwidth: i64) -> Result { + if self.client_bandwidth.bandwidth.expired() { + self.expire_bandwidth().await?; + } + let available_bandwidth = self.client_bandwidth.bandwidth.bytes; + + if available_bandwidth < required_bandwidth { + return Err(Error::OutOfBandwidth { + required: required_bandwidth, + available: available_bandwidth, + }); + } + + let available_bi2 = bibytes2(available_bandwidth as f64); + let required_bi2 = bibytes2(required_bandwidth as f64); + debug!(available = available_bi2, required = required_bi2); + + self.consume_bandwidth(required_bandwidth).await?; + Ok(available_bandwidth) + } + + async fn expire_bandwidth(&mut self) -> Result<()> { + self.storage.reset_bandwidth(self.client_id).await?; + self.client_bandwidth.bandwidth = Default::default(); + self.client_bandwidth.update_sync_data(); + Ok(()) + } + + /// Decreases the amount of available bandwidth of the connected client by the specified value. + /// + /// # Arguments + /// + /// * `amount`: amount to decrease the available bandwidth by. + async fn consume_bandwidth(&mut self, amount: i64) -> Result<()> { + self.client_bandwidth.bandwidth.bytes -= amount; + self.client_bandwidth.bytes_delta_since_sync -= amount; + + // since we're going to be operating on a fair use policy anyway, even if we crash and let extra few packets + // through, that's completely fine + if self.client_bandwidth.should_sync(self.bandwidth_cfg) { + self.sync_bandwidth().await?; + } + + Ok(()) + } + + #[instrument(level = "trace", skip_all)] + async fn sync_bandwidth(&mut self) -> Result<()> { + trace!("syncing client bandwidth with the underlying storage"); + let updated = self + .storage + .increase_bandwidth(self.client_id, self.client_bandwidth.bytes_delta_since_sync) + .await?; + + trace!(updated); + + self.client_bandwidth.bandwidth.bytes = updated; + + self.client_bandwidth.update_sync_data(); + Ok(()) + } + + /// Increases the amount of available bandwidth of the connected client by the specified value. + /// + /// # Arguments + /// + /// * `amount`: amount to increase the available bandwidth by. + /// * `expiration` : the expiration date of that bandwidth + pub async fn increase_bandwidth( + &mut self, + bandwidth: Bandwidth, + expiration: OffsetDateTime, + ) -> Result<()> { + self.client_bandwidth.bandwidth.bytes += bandwidth.value() as i64; + self.client_bandwidth.bytes_delta_since_sync += bandwidth.value() as i64; + self.client_bandwidth.bandwidth.expiration = expiration; + + // any increases to bandwidth should get flushed immediately + // (we don't want to accidentally miss somebody claiming a gigabyte voucher) + self.sync_expiration().await?; + self.sync_bandwidth().await + } +} diff --git a/common/credential-verification/src/client_bandwidth.rs b/common/credential-verification/src/client_bandwidth.rs new file mode 100644 index 0000000000..610a198e88 --- /dev/null +++ b/common/credential-verification/src/client_bandwidth.rs @@ -0,0 +1,57 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::time::Duration; + +use nym_credentials_interface::AvailableBandwidth; +use time::OffsetDateTime; + +#[derive(Debug, Clone, Copy)] +pub struct BandwidthFlushingBehaviourConfig { + /// Defines maximum delay between client bandwidth information being flushed to the persistent storage. + pub client_bandwidth_max_flushing_rate: Duration, + + /// Defines a maximum change in client bandwidth before it gets flushed to the persistent storage. + pub client_bandwidth_max_delta_flushing_amount: i64, +} + +#[derive(Debug, Clone, Copy)] +pub struct ClientBandwidth { + pub(crate) bandwidth: AvailableBandwidth, + pub(crate) last_flushed: OffsetDateTime, + + /// the number of bytes the client had during the last sync. + /// it is used to determine whether the current value should be synced with the storage + /// by checking the delta with the known amount + pub(crate) bytes_at_last_sync: i64, + pub(crate) bytes_delta_since_sync: i64, +} + +impl ClientBandwidth { + pub fn new(bandwidth: AvailableBandwidth) -> ClientBandwidth { + ClientBandwidth { + bandwidth, + last_flushed: OffsetDateTime::now_utc(), + bytes_at_last_sync: bandwidth.bytes, + bytes_delta_since_sync: 0, + } + } + + pub(crate) fn should_sync(&self, cfg: BandwidthFlushingBehaviourConfig) -> bool { + if self.bytes_delta_since_sync.abs() >= cfg.client_bandwidth_max_delta_flushing_amount { + return true; + } + + if self.last_flushed + cfg.client_bandwidth_max_flushing_rate < OffsetDateTime::now_utc() { + return true; + } + + false + } + + pub(crate) fn update_sync_data(&mut self) { + self.last_flushed = OffsetDateTime::now_utc(); + self.bytes_at_last_sync = self.bandwidth.bytes; + self.bytes_delta_since_sync = 0; + } +} diff --git a/gateway/src/node/client_handling/websocket/connection_handler/ecash/credential_sender.rs b/common/credential-verification/src/ecash/credential_sender.rs similarity index 97% rename from gateway/src/node/client_handling/websocket/connection_handler/ecash/credential_sender.rs rename to common/credential-verification/src/ecash/credential_sender.rs index 7cc478cc9a..47653cf334 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/ecash/credential_sender.rs +++ b/common/credential-verification/src/ecash/credential_sender.rs @@ -1,17 +1,17 @@ // Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::client_handling::bandwidth::Bandwidth; -use crate::node::client_handling::websocket::connection_handler::ecash::error::EcashTicketError; -use crate::node::client_handling::websocket::connection_handler::ecash::helpers::for_each_api_concurrent; -use crate::node::client_handling::websocket::connection_handler::ecash::state::SharedState; -use crate::GatewayError; +use crate::ecash::error::EcashTicketError; +use crate::ecash::helpers::for_each_api_concurrent; +use crate::ecash::state::SharedState; +use crate::Error; use cosmwasm_std::Fraction; use cw_utils::ThresholdResponse; use futures::channel::mpsc::UnboundedReceiver; use futures::{Stream, StreamExt}; use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY; use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketBody}; +use nym_credentials_interface::Bandwidth; use nym_credentials_interface::{ClientTicket, TicketType}; use nym_gateway_storage::Storage; use nym_validator_client::nym_api::EpochId; @@ -105,25 +105,25 @@ impl PendingRedemptionVote { } } -pub(crate) struct CredentialHandlerConfig { +pub struct CredentialHandlerConfig { /// Specifies the multiplier for revoking a malformed/double-spent ticket /// (if it has to go all the way to the nym-api for verification) /// e.g. if one ticket grants 100Mb and `revocation_bandwidth_penalty` is set to 1.5, /// the client will lose 150Mb - pub(crate) revocation_bandwidth_penalty: f32, + pub revocation_bandwidth_penalty: f32, /// Specifies the interval for attempting to resolve any failed, pending operations, /// such as ticket verification or redemption. - pub(crate) pending_poller: Duration, + pub pending_poller: Duration, - pub(crate) minimum_api_quorum: f32, + pub minimum_api_quorum: f32, /// Specifies the minimum number of tickets this gateway will attempt to redeem. - pub(crate) minimum_redemption_tickets: usize, + pub minimum_redemption_tickets: usize, /// Specifies the maximum time between two subsequent tickets redemptions. /// That's required as nym-apis will purge all ticket information for tickets older than 30 days. - pub(crate) maximum_time_between_redemption: Duration, + pub maximum_time_between_redemption: Duration, } pub(crate) struct CredentialHandler { @@ -260,7 +260,7 @@ where config: CredentialHandlerConfig, ticket_receiver: UnboundedReceiver, shared_state: SharedState, - ) -> Result { + ) -> Result { let multisig_threshold = shared_state .nyxd_client .read() @@ -269,7 +269,7 @@ where .await?; let ThresholdResponse::AbsolutePercentage { percentage, .. } = multisig_threshold else { - return Err(GatewayError::InvalidMultisigThreshold); + return Err(Error::InvalidMultisigThreshold); }; // that's a nasty conversion, but it works : ) diff --git a/gateway/src/node/client_handling/websocket/connection_handler/ecash/double_spending.rs b/common/credential-verification/src/ecash/double_spending.rs similarity index 94% rename from gateway/src/node/client_handling/websocket/connection_handler/ecash/double_spending.rs rename to common/credential-verification/src/ecash/double_spending.rs index a8cebdaf7b..812eea1fc1 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/ecash/double_spending.rs +++ b/common/credential-verification/src/ecash/double_spending.rs @@ -1,10 +1,10 @@ // Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::client_handling::websocket::connection_handler::ecash::error::EcashTicketError; -use crate::node::client_handling::websocket::connection_handler::ecash::state::SharedState; -use crate::node::Storage; +use crate::ecash::error::EcashTicketError; +use crate::ecash::state::SharedState; use nym_ecash_double_spending::DoubleSpendingFilter; +use nym_gateway_storage::Storage; use nym_task::TaskClient; use nym_validator_client::client::NymApiClientExt; use nym_validator_client::EcashApiClient; diff --git a/gateway/src/node/client_handling/websocket/connection_handler/ecash/error.rs b/common/credential-verification/src/ecash/error.rs similarity index 100% rename from gateway/src/node/client_handling/websocket/connection_handler/ecash/error.rs rename to common/credential-verification/src/ecash/error.rs diff --git a/gateway/src/node/client_handling/websocket/connection_handler/ecash/helpers.rs b/common/credential-verification/src/ecash/helpers.rs similarity index 100% rename from gateway/src/node/client_handling/websocket/connection_handler/ecash/helpers.rs rename to common/credential-verification/src/ecash/helpers.rs diff --git a/gateway/src/node/client_handling/websocket/connection_handler/ecash/mod.rs b/common/credential-verification/src/ecash/mod.rs similarity index 93% rename from gateway/src/node/client_handling/websocket/connection_handler/ecash/mod.rs rename to common/credential-verification/src/ecash/mod.rs index 69c3d46800..c024f37733 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/ecash/mod.rs +++ b/common/credential-verification/src/ecash/mod.rs @@ -1,26 +1,25 @@ // Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::client_handling::websocket::connection_handler::ecash::state::SharedState; -use crate::GatewayError; +use crate::Error; use credential_sender::CredentialHandler; +use credential_sender::CredentialHandlerConfig; use double_spending::DoubleSpendingDetector; +use error::EcashTicketError; use futures::channel::mpsc::{self, UnboundedSender}; use nym_credentials::CredentialSpendingData; use nym_credentials_interface::{ClientTicket, CompactEcashError, NymPayInfo, VerificationKeyAuth}; use nym_gateway_storage::Storage; use nym_validator_client::nym_api::EpochId; use nym_validator_client::DirectSigningHttpRpcNyxdClient; +use state::SharedState; use time::OffsetDateTime; use tokio::sync::{Mutex, RwLockReadGuard}; use tracing::error; -use crate::node::client_handling::websocket::connection_handler::ecash::credential_sender::CredentialHandlerConfig; -use crate::node::client_handling::websocket::connection_handler::ecash::error::EcashTicketError; - -pub(crate) mod credential_sender; +pub mod credential_sender; pub(crate) mod double_spending; -pub(crate) mod error; +pub mod error; mod helpers; mod state; @@ -45,7 +44,7 @@ where pk_bytes: [u8; 32], shutdown: nym_task::TaskClient, storage: S, - ) -> Result { + ) -> Result { let shared_state = SharedState::new(nyxd_client, storage).await?; let double_spend_detector = DoubleSpendingDetector::new(shared_state.clone()); diff --git a/gateway/src/node/client_handling/websocket/connection_handler/ecash/state.rs b/common/credential-verification/src/ecash/state.rs similarity index 97% rename from gateway/src/node/client_handling/websocket/connection_handler/ecash/state.rs rename to common/credential-verification/src/ecash/state.rs index 57f39e95fd..7f5a718759 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/ecash/state.rs +++ b/common/credential-verification/src/ecash/state.rs @@ -1,12 +1,12 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::node::client_handling::websocket::connection_handler::ecash::error::EcashTicketError; -use crate::node::Storage; -use crate::GatewayError; +use crate::ecash::error::EcashTicketError; +use crate::Error; use cosmwasm_std::{from_binary, CosmosMsg, WasmMsg}; use nym_credentials_interface::VerificationKeyAuth; use nym_ecash_contract_common::msg::ExecuteMsg; +use nym_gateway_storage::Storage; use nym_validator_client::coconut::all_ecash_api_clients; use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::contract_traits::{ @@ -37,7 +37,7 @@ where pub(crate) async fn new( nyxd_client: DirectSigningHttpRpcNyxdClient, storage: S, - ) -> Result { + ) -> Result { let address = nyxd_client.address(); if nyxd_client.dkg_contract_address().is_none() { diff --git a/common/credential-verification/src/error.rs b/common/credential-verification/src/error.rs new file mode 100644 index 0000000000..0f6b0e27e5 --- /dev/null +++ b/common/credential-verification/src/error.rs @@ -0,0 +1,57 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use thiserror::Error; +use time::Date; + +use crate::ecash::error::EcashTicketError; + +pub type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum Error { + #[error("the provided bandwidth credential has already been spent before at this gateway")] + BandwidthCredentialAlreadySpent, + + #[error(transparent)] + EcashFailure(EcashTicketError), + + #[error( + "the provided credential has an invalid spending date. got {got} but expected {expected}" + )] + InvalidCredentialSpendingDate { got: Date, expected: Date }, + + #[error("the current multisig contract is not using 'AbsolutePercentage' threshold!")] + InvalidMultisigThreshold, + + #[error( + "the received payment contained more than a single ticket. that's currently not supported" + )] + MultipleTickets, + + #[error("Nyxd Error - {0}")] + NyxdError(#[from] nym_validator_client::nyxd::error::NyxdError), + + #[error("This gateway is only accepting coconut credentials for bandwidth")] + OnlyCoconutCredentials, + + #[error("insufficient bandwidth available to process the request. required: {required}B, available: {available}B")] + OutOfBandwidth { required: i64, available: i64 }, + + #[error("Internal gateway storage error")] + StorageError(#[from] nym_gateway_storage::error::StorageError), + + #[error("{0}")] + UnknownTicketType(#[from] nym_credentials_interface::UnknownTicketType), +} + +impl From for Error { + fn from(err: EcashTicketError) -> Self { + // don't expose storage issue details to the user + if let EcashTicketError::InternalStorageFailure { source } = err { + Error::StorageError(source) + } else { + Error::EcashFailure(err) + } + } +} diff --git a/common/credential-verification/src/lib.rs b/common/credential-verification/src/lib.rs new file mode 100644 index 0000000000..bb3a0cab8f --- /dev/null +++ b/common/credential-verification/src/lib.rs @@ -0,0 +1,156 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use bandwidth_storage_manager::BandwidthStorageManager; +use std::sync::Arc; +use time::{Date, OffsetDateTime}; +use tracing::*; + +use nym_credentials::ecash::utils::{ecash_today, EcashTime}; +use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType}; +use nym_gateway_requests::models::CredentialSpendingRequest; +use nym_gateway_storage::Storage; + +pub use client_bandwidth::*; +use ecash::EcashManager; +pub use error::*; + +pub mod bandwidth_storage_manager; +mod client_bandwidth; +pub mod ecash; +pub mod error; + +pub struct CredentialVerifier { + credential: CredentialSpendingRequest, + ecash_verifier: Arc>, + bandwidth_storage_manager: BandwidthStorageManager, +} + +impl CredentialVerifier { + pub fn new( + credential: CredentialSpendingRequest, + ecash_verifier: Arc>, + bandwidth_storage_manager: BandwidthStorageManager, + ) -> Self { + CredentialVerifier { + credential, + ecash_verifier, + bandwidth_storage_manager, + } + } + + fn check_credential_spending_date(&self, today: Date) -> Result<()> { + let proposed = self.credential.data.spend_date; + trace!("checking ticket spending date..."); + + if today != proposed { + trace!("invalid credential spending date. received {proposed}"); + return Err(Error::InvalidCredentialSpendingDate { + got: proposed, + expected: today, + }); + } + Ok(()) + } + + async fn check_bloomfilter(&self, serial_number: &Vec) -> Result<()> { + trace!("checking the bloomfilter..."); + + let spent = self.ecash_verifier.check_double_spend(serial_number).await; + + if spent { + trace!("the credential has already been spent before at some gateway before (bloomfilter failure)"); + return Err(Error::BandwidthCredentialAlreadySpent); + } + Ok(()) + } + + async fn check_local_db_for_double_spending(&self, serial_number: &[u8]) -> Result<()> { + trace!("checking local db for double spending..."); + + let spent = self + .bandwidth_storage_manager + .storage + .contains_ticket(serial_number) + .await?; + if spent { + trace!("the credential has already been spent before at this gateway"); + return Err(Error::BandwidthCredentialAlreadySpent); + } + Ok(()) + } + + async fn cryptographically_verify_ticket(&self) -> Result<()> { + trace!("attempting to perform ticket verification..."); + + let aggregated_verification_key = self + .ecash_verifier + .verification_key(self.credential.data.epoch_id) + .await?; + + self.ecash_verifier + .check_payment(&self.credential.data, &aggregated_verification_key) + .await?; + Ok(()) + } + + async fn store_received_ticket(&self, received_at: OffsetDateTime) -> Result { + trace!("storing received ticket"); + let ticket_id = self + .bandwidth_storage_manager + .storage + .insert_received_ticket( + self.bandwidth_storage_manager.client_id, + received_at, + self.credential.encoded_serial_number(), + self.credential.to_bytes(), + ) + .await?; + Ok(ticket_id) + } + + fn async_verify_ticket(&self, ticket_id: i64) { + let client_ticket = ClientTicket::new(self.credential.data.clone(), ticket_id); + + self.ecash_verifier.async_verify(client_ticket); + } + + pub async fn verify(&mut self) -> Result { + let received_at = OffsetDateTime::now_utc(); + let spend_date = ecash_today(); + + // check if the credential hasn't been spent before + let serial_number = self.credential.data.encoded_serial_number(); + let credential_type = TicketType::try_from_encoded(self.credential.data.payment.t_type)?; + + if self.credential.data.payment.spend_value != 1 { + return Err(Error::MultipleTickets); + } + + self.check_credential_spending_date(spend_date.ecash_date())?; + self.check_bloomfilter(&serial_number).await?; + self.check_local_db_for_double_spending(&serial_number) + .await?; + + // TODO: do we HAVE TO do it? + self.cryptographically_verify_ticket().await?; + + let ticket_id = self.store_received_ticket(received_at).await?; + self.async_verify_ticket(ticket_id); + + // TODO: double storing? + // self.store_spent_credential(serial_number_bs58).await?; + + let bandwidth = Bandwidth::ticket_amount(credential_type.into()); + + self.bandwidth_storage_manager + .increase_bandwidth(bandwidth, spend_date) + .await?; + + Ok(self + .bandwidth_storage_manager + .client_bandwidth + .bandwidth + .bytes) + } +} diff --git a/common/credentials-interface/src/lib.rs b/common/credentials-interface/src/lib.rs index 270cb5f658..b0d8466fe9 100644 --- a/common/credentials-interface/src/lib.rs +++ b/common/credentials-interface/src/lib.rs @@ -319,3 +319,24 @@ impl Default for AvailableBandwidth { } } } + +#[derive(Debug, Copy, Clone)] +pub struct Bandwidth { + value: u64, +} + +impl Bandwidth { + pub const fn new_unchecked(value: u64) -> Bandwidth { + Bandwidth { value } + } + + pub fn ticket_amount(typ: TicketTypeRepr) -> Self { + Bandwidth { + value: typ.bandwidth_value(), + } + } + + pub fn value(&self) -> u64 { + self.value + } +} diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index e5f43bc54d..2a2e7ac51e 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -30,8 +30,6 @@ bip39 = { workspace = true } bs58 = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"], optional = true } colored = { workspace = true } -cosmwasm-std = { workspace = true } -cw-utils = { workspace = true } dashmap = { workspace = true } dirs = { workspace = true } dotenvy = { workspace = true } @@ -64,9 +62,7 @@ tokio-tungstenite = { workspace = true } tokio-util = { workspace = true, features = ["codec"] } tracing = { workspace = true } url = { workspace = true, features = ["serde"] } -time = { workspace = true } zeroize = { workspace = true } -bloomfilter = { workspace = true } # internal @@ -76,9 +72,8 @@ nym-bin-common = { path = "../common/bin-common" } nym-config = { path = "../common/config" } nym-credentials = { path = "../common/credentials" } nym-credentials-interface = { path = "../common/credentials-interface" } +nym-credential-verification = { path = "../common/credential-verification" } nym-crypto = { path = "../common/crypto" } -nym-ecash-contract-common = { path = "../common/cosmwasm-smart-contracts/ecash-contract" } -nym-ecash-double-spending = { path = "../common/ecash-double-spending" } nym-gateway-storage = { path = "../common/gateway-storage" } nym-gateway-requests = { path = "../common/gateway-requests" } nym-mixnet-client = { path = "../common/client-libs/mixnet-client" } diff --git a/gateway/src/error.rs b/gateway/src/error.rs index 157f3f507e..e7786db443 100644 --- a/gateway/src/error.rs +++ b/gateway/src/error.rs @@ -14,7 +14,6 @@ use std::path::PathBuf; use thiserror::Error; pub use crate::node::client_handling::websocket::connection_handler::authenticated::RequestHandlingError; -use crate::node::client_handling::websocket::connection_handler::ecash::error::EcashTicketError; #[derive(Debug, Error)] pub enum GatewayError { @@ -170,12 +169,6 @@ pub enum GatewayError { source: RequestHandlingError, }, - #[error("ecash related failure: {source}")] - EcashFailure { - #[from] - source: EcashTicketError, - }, - #[error("failed to catch an interrupt: {source}")] ShutdownFailure { source: Box, @@ -196,9 +189,6 @@ pub enum GatewayError { source: ipnetwork::IpNetworkError, }, - #[error("the current multisig contract is not using 'AbsolutePercentage' threshold!")] - InvalidMultisigThreshold, - #[cfg(all(feature = "wireguard", target_os = "linux"))] #[error("failed to remove wireguard interface: {0}")] WireguardInterfaceError(#[from] defguard_wireguard_rs::error::WireguardInterfaceError), @@ -211,6 +201,9 @@ pub enum GatewayError { AuthenticatorStartError { source: Box, }, + + #[error("{0}")] + CredentialVefiricationError(#[from] nym_credential_verification::Error), } impl From for GatewayError { diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index ebfb91b0c1..0113be49b3 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -1,10 +1,8 @@ // Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use nym_network_defaults::TicketTypeRepr; use std::num::ParseIntError; use thiserror::Error; -use time::error::ComponentRange; use tracing::error; #[derive(Debug, Error)] @@ -23,32 +21,4 @@ pub enum BandwidthError { #[source] source: ParseIntError, }, - - #[error("failed to parse expiry timestamp into proper datetime: {source}")] - InvalidExpiryDate { - unix_timestamp: i64, - #[source] - source: ComponentRange, - }, -} - -#[derive(Debug, Copy, Clone)] -pub struct Bandwidth { - value: u64, -} - -impl Bandwidth { - pub const fn new_unchecked(value: u64) -> Bandwidth { - Bandwidth { value } - } - - pub fn ticket_amount(typ: TicketTypeRepr) -> Self { - Bandwidth { - value: typ.bandwidth_value(), - } - } - - pub fn value(&self) -> u64 { - self.value - } } diff --git a/gateway/src/node/client_handling/mod.rs b/gateway/src/node/client_handling/mod.rs index b195b8d693..eecb0735db 100644 --- a/gateway/src/node/client_handling/mod.rs +++ b/gateway/src/node/client_handling/mod.rs @@ -1,12 +1,7 @@ // Copyright 2020-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::client_handling::bandwidth::Bandwidth; - pub(crate) mod active_clients; mod bandwidth; pub(crate) mod embedded_clients; pub(crate) mod websocket; - -pub(crate) const FREE_TESTNET_BANDWIDTH_VALUE: Bandwidth = - Bandwidth::new_unchecked(64 * 1024 * 1024 * 1024); // 64GB diff --git a/gateway/src/node/client_handling/websocket/common_state.rs b/gateway/src/node/client_handling/websocket/common_state.rs index 3a2a206368..2120b33422 100644 --- a/gateway/src/node/client_handling/websocket/common_state.rs +++ b/gateway/src/node/client_handling/websocket/common_state.rs @@ -1,8 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::client_handling::websocket::connection_handler::ecash::EcashManager; -use crate::node::client_handling::websocket::connection_handler::BandwidthFlushingBehaviourConfig; +use nym_credential_verification::{ecash::EcashManager, BandwidthFlushingBehaviourConfig}; use nym_crypto::asymmetric::identity; use std::sync::Arc; diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index d0f039fcca..12bbb16b51 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -2,24 +2,22 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::node::client_handling::{ - bandwidth::{Bandwidth, BandwidthError}, + bandwidth::BandwidthError, websocket::{ - connection_handler::{ - ecash::error::EcashTicketError, ClientBandwidth, ClientDetails, FreshHandler, - }, + connection_handler::{ClientDetails, FreshHandler}, message_receiver::{ IsActive, IsActiveRequestReceiver, IsActiveResultSender, MixMessageReceiver, }, }, - FREE_TESTNET_BANDWIDTH_VALUE, }; use futures::{ future::{FusedFuture, OptionFuture}, FutureExt, StreamExt, }; -use nym_credentials::ecash::utils::{ecash_today, EcashTime}; -use nym_credentials_interface::{ClientTicket, CredentialSpendingData, TicketType}; -use nym_gateway_requests::models::CredentialSpendingRequest; +use nym_credential_verification::CredentialVerifier; +use nym_credential_verification::{ + bandwidth_storage_manager::BandwidthStorageManager, ClientBandwidth, +}; use nym_gateway_requests::{ types::{BinaryRequest, ServerResponse}, ClientControlRequest, GatewayRequestsError, SimpleGatewayRequestsError, @@ -29,10 +27,8 @@ use nym_sphinx::forwarding::packet::MixPacket; use nym_task::TaskClient; use nym_validator_client::coconut::EcashApiError; use rand::{CryptoRng, Rng}; -use si_scale::helpers::bibytes2; use std::{process, time::Duration}; use thiserror::Error; -use time::{Date, OffsetDateTime}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError}; use tracing::*; @@ -59,20 +55,6 @@ pub enum RequestHandlingError { #[error("credential has been rejected by the validators")] RejectedProposal, - #[error( - "the provided credential has an invalid spending date. got {got} but expected {expected}" - )] - InvalidCredentialSpendingDate { got: Date, expected: Date }, - - #[error("the provided bandwidth credential has already been spent before at this gateway")] - BandwidthCredentialAlreadySpent, - - #[error("This gateway is only accepting coconut credentials for bandwidth")] - OnlyCoconutCredentials, - - #[error("Nyxd Error - {0}")] - NyxdError(#[from] nym_validator_client::nyxd::error::NyxdError), - #[error("Validator API error - {0}")] APIError(#[from] nym_validator_client::ValidatorClientError), @@ -94,28 +76,19 @@ pub enum RequestHandlingError { #[error("failed to recover bandwidth value: {0}")] BandwidthRecoveryFailure(#[from] BandwidthError), - #[error("insufficient bandwidth available to process the request. required: {required}B, available: {available}B")] - OutOfBandwidth { required: i64, available: i64 }, - - #[error(transparent)] - EcashFailure(EcashTicketError), - - #[error( - "the received payment contained more than a single ticket. that's currently not supported" - )] - MultipleTickets, - #[error("{0}")] - UnknownTicketType(#[from] nym_credentials_interface::UnknownTicketType), + CredentialVerification(#[from] nym_credential_verification::Error), } impl RequestHandlingError { fn into_error_message(self) -> Message { let server_response = match self { - RequestHandlingError::OutOfBandwidth { - required, - available, - } => ServerResponse::TypedError { + RequestHandlingError::CredentialVerification( + nym_credential_verification::Error::OutOfBandwidth { + required, + available, + }, + ) => ServerResponse::TypedError { error: SimpleGatewayRequestsError::OutOfBandwidth { required, available, @@ -127,17 +100,6 @@ impl RequestHandlingError { } } -impl From for RequestHandlingError { - fn from(err: EcashTicketError) -> Self { - // don't expose storage issue details to the user - if let EcashTicketError::InternalStorageFailure { source } = err { - RequestHandlingError::StorageError(source) - } else { - RequestHandlingError::EcashFailure(err) - } - } -} - /// Helper trait that allows converting result of handling client request into a websocket message // Note: I couldn't have implemented a normal "From" trait as both `Message` and `Result` are foreign types trait IntoWSMessage { @@ -155,8 +117,8 @@ impl IntoWSMessage for Result { pub(crate) struct AuthenticatedHandler { inner: FreshHandler, + bandwidth_storage_manager: BandwidthStorageManager, client: ClientDetails, - client_bandwidth: ClientBandwidth, mix_receiver: MixMessageReceiver, // Occasionally the handler is requested to ping the connected client for confirm that it's // active, such as when a duplicate connection is detected. This hashmap stores the oneshot @@ -209,9 +171,15 @@ where })?; Ok(AuthenticatedHandler { + bandwidth_storage_manager: BandwidthStorageManager::new( + fresh.shared_state.storage.clone(), + ClientBandwidth::new(bandwidth.into()), + client.id, + fresh.shared_state.bandwidth_cfg, + fresh.shared_state.only_coconut_credentials, + ), inner: fresh, client, - client_bandwidth: ClientBandwidth::new(bandwidth.into()), mix_receiver, is_active_request_receiver, is_active_ping_pending_reply: None, @@ -225,55 +193,6 @@ where .disconnect(self.client.address) } - async fn expire_bandwidth(&mut self) -> Result<(), RequestHandlingError> { - self.inner.expire_bandwidth(self.client.id).await?; - self.client_bandwidth.bandwidth = Default::default(); - self.client_bandwidth.update_sync_data(); - Ok(()) - } - - /// Increases the amount of available bandwidth of the connected client by the specified value. - /// - /// # Arguments - /// - /// * `amount`: amount to increase the available bandwidth by. - /// * `expiration` : the expiration date of that bandwidth - async fn increase_bandwidth( - &mut self, - bandwidth: Bandwidth, - expiration: OffsetDateTime, - ) -> Result<(), RequestHandlingError> { - self.client_bandwidth.bandwidth.bytes += bandwidth.value() as i64; - self.client_bandwidth.bytes_delta_since_sync += bandwidth.value() as i64; - self.client_bandwidth.bandwidth.expiration = expiration; - - // any increases to bandwidth should get flushed immediately - // (we don't want to accidentally miss somebody claiming a gigabyte voucher) - self.sync_expiration().await?; - self.sync_bandwidth().await - } - - /// Decreases the amount of available bandwidth of the connected client by the specified value. - /// - /// # Arguments - /// - /// * `amount`: amount to decrease the available bandwidth by. - async fn consume_bandwidth(&mut self, amount: i64) -> Result<(), RequestHandlingError> { - self.client_bandwidth.bandwidth.bytes -= amount; - self.client_bandwidth.bytes_delta_since_sync -= amount; - - // since we're going to be operating on a fair use policy anyway, even if we crash and let extra few packets - // through, that's completely fine - if self - .client_bandwidth - .should_sync(self.inner.shared_state.bandwidth_cfg) - { - self.sync_bandwidth().await?; - } - - Ok(()) - } - /// Forwards the received mix packet from the client into the mix network. /// /// # Arguments @@ -286,109 +205,6 @@ where } } - async fn check_local_db_for_double_spending( - &self, - serial_number: &[u8], - ) -> Result<(), RequestHandlingError> { - trace!("checking local db for double spending..."); - - let spent = self - .inner - .shared_state - .storage - .contains_ticket(serial_number) - .await?; - if spent { - trace!("the credential has already been spent before at this gateway"); - return Err(RequestHandlingError::BandwidthCredentialAlreadySpent); - } - Ok(()) - } - - async fn check_bloomfilter(&self, serial_number: &Vec) -> Result<(), RequestHandlingError> { - trace!("checking the bloomfilter..."); - - let spent = self - .inner - .shared_state - .ecash_verifier - .check_double_spend(serial_number) - .await; - - if spent { - trace!("the credential has already been spent before at some gateway before (bloomfilter failure)"); - return Err(RequestHandlingError::BandwidthCredentialAlreadySpent); - } - Ok(()) - } - - fn check_credential_spending_date( - &self, - proposed: Date, - today: Date, - ) -> Result<(), RequestHandlingError> { - trace!("checking ticket spending date..."); - - if today != proposed { - trace!("invalid credential spending date. received {proposed}"); - return Err(RequestHandlingError::InvalidCredentialSpendingDate { - got: proposed, - expected: today, - }); - } - Ok(()) - } - - async fn cryptographically_verify_ticket( - &self, - credential: &CredentialSpendingRequest, - ) -> Result<(), RequestHandlingError> { - trace!("attempting to perform ticket verification..."); - - let aggregated_verification_key = self - .inner - .shared_state - .ecash_verifier - .verification_key(credential.data.epoch_id) - .await?; - - self.inner - .shared_state - .ecash_verifier - .check_payment(&credential.data, &aggregated_verification_key) - .await?; - Ok(()) - } - - fn async_verify_ticket(&self, ticket: CredentialSpendingData, ticket_id: i64) { - let client_ticket = ClientTicket::new(ticket, ticket_id); - - self.inner - .shared_state - .ecash_verifier - .async_verify(client_ticket); - } - - async fn store_received_ticket( - &self, - ticket_data: &CredentialSpendingRequest, - received_at: OffsetDateTime, - ) -> Result { - trace!("storing received ticket"); - let ticket_id = self - .inner - .shared_state - .storage - .insert_received_ticket( - self.client.id, - received_at, - ticket_data.encoded_serial_number(), - ticket_data.to_bytes(), - ) - .await?; - Ok(ticket_id) - } - /// Tries to handle the received bandwidth request by checking correctness of the received data /// and if successful, increases client's bandwidth by an appropriate amount. /// @@ -401,7 +217,6 @@ where enc_credential: Vec, iv: Vec, ) -> Result { - let received_at = OffsetDateTime::now_utc(); // TODO: change it into a span field instead once we move to tracing debug!( "handling e-cash bandwidth request from {}", @@ -413,107 +228,17 @@ where &self.client.shared_keys, iv, )?; - let spend_date = ecash_today(); - - // check if the credential hasn't been spent before - let serial_number = credential.data.encoded_serial_number(); - let credential_type = TicketType::try_from_encoded(credential.data.payment.t_type)?; - - if credential.data.payment.spend_value != 1 { - return Err(RequestHandlingError::MultipleTickets); - } - - self.check_credential_spending_date(credential.data.spend_date, spend_date.ecash_date())?; - self.check_bloomfilter(&serial_number).await?; - self.check_local_db_for_double_spending(&serial_number) - .await?; - - // TODO: do we HAVE TO do it? - self.cryptographically_verify_ticket(&credential).await?; - - let ticket_id = self.store_received_ticket(&credential, received_at).await?; - self.async_verify_ticket(credential.data, ticket_id); - - // TODO: double storing? - // self.store_spent_credential(serial_number_bs58).await?; - - let bandwidth = Bandwidth::ticket_amount(credential_type.into()); - - self.increase_bandwidth(bandwidth, spend_date).await?; - - let available_total = self.client_bandwidth.bandwidth.bytes; - - Ok(ServerResponse::Bandwidth { available_total }) - } - - async fn handle_claim_testnet_bandwidth( - &mut self, - ) -> Result { - debug!("handling testnet bandwidth request"); - - if self.inner.shared_state.only_coconut_credentials { - return Err(RequestHandlingError::OnlyCoconutCredentials); - } + let mut verifier = CredentialVerifier::new( + credential, + self.inner.shared_state.ecash_verifier.clone(), + self.bandwidth_storage_manager.clone(), + ); - self.increase_bandwidth(FREE_TESTNET_BANDWIDTH_VALUE, ecash_today()) - .await?; - let available_total = self.client_bandwidth.bandwidth.bytes; + let available_total = verifier.verify().await?; Ok(ServerResponse::Bandwidth { available_total }) } - async fn sync_expiration(&mut self) -> Result<(), RequestHandlingError> { - self.inner - .shared_state - .storage - .set_expiration(self.client.id, self.client_bandwidth.bandwidth.expiration) - .await?; - Ok(()) - } - - #[instrument(level = "trace", skip_all)] - async fn sync_bandwidth(&mut self) -> Result<(), RequestHandlingError> { - trace!("syncing client bandwidth with the underlying storage"); - let updated = self - .inner - .shared_state - .storage - .increase_bandwidth(self.client.id, self.client_bandwidth.bytes_delta_since_sync) - .await?; - - trace!(updated); - - self.client_bandwidth.bandwidth.bytes = updated; - - self.client_bandwidth.update_sync_data(); - Ok(()) - } - - #[instrument(skip_all)] - async fn try_use_bandwidth( - &mut self, - required_bandwidth: i64, - ) -> Result { - if self.client_bandwidth.bandwidth.expired() { - self.expire_bandwidth().await?; - } - let available_bandwidth = self.client_bandwidth.bandwidth.bytes; - - if available_bandwidth < required_bandwidth { - return Err(RequestHandlingError::OutOfBandwidth { - required: required_bandwidth, - available: available_bandwidth, - }); - } - - let available_bi2 = bibytes2(available_bandwidth as f64); - let required_bi2 = bibytes2(required_bandwidth as f64); - debug!(available = available_bi2, required = required_bi2); - - self.consume_bandwidth(required_bandwidth).await?; - Ok(available_bandwidth) - } - /// Tries to handle request to forward sphinx packet into the network. The request can only succeed /// if the client has enough available bandwidth. /// @@ -529,7 +254,10 @@ where ) -> Result { let required_bandwidth = mix_packet.packet().len() as i64; - let remaining_bandwidth = self.try_use_bandwidth(required_bandwidth).await?; + let remaining_bandwidth = self + .bandwidth_storage_manager + .try_use_bandwidth(required_bandwidth) + .await?; self.forward_packet(mix_packet); Ok(ServerResponse::Send { @@ -589,8 +317,10 @@ where .into_error_message() } ClientControlRequest::ClaimFreeTestnetBandwidth => self + .bandwidth_storage_manager .handle_claim_testnet_bandwidth() .await + .map_err(|e| e.into()) .into_ws_message(), other => RequestHandlingError::IllegalRequest { additional_context: format!( diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index 0986532896..f56f14e0eb 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -566,7 +566,7 @@ where .unwrap_or_default(); let bandwidth_remaining = if available_bandwidth.expired() { - self.expire_bandwidth(client_id).await?; + self.shared_state.storage.reset_bandwidth(client_id).await?; 0 } else { available_bandwidth.bytes @@ -582,10 +582,6 @@ where )) } - pub(crate) async fn expire_bandwidth(&self, client_id: i64) -> Result<(), StorageError> { - self.shared_state.storage.reset_bandwidth(client_id).await - } - /// Attempts to finalize registration of the client by storing the derived shared keys in the /// persistent store as well as creating entry for its bandwidth allocation. /// diff --git a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs index aeebdb02fa..0ffde8feed 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs @@ -2,15 +2,13 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::config::Config; -use nym_credentials_interface::AvailableBandwidth; +use nym_credential_verification::BandwidthFlushingBehaviourConfig; use nym_gateway_requests::registration::handshake::SharedKeys; use nym_gateway_requests::ServerResponse; use nym_gateway_storage::Storage; use nym_sphinx::DestinationAddressBytes; use nym_task::TaskClient; use rand::{CryptoRng, Rng}; -use std::time::Duration; -use time::OffsetDateTime; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tungstenite::WebSocketStream; use tracing::{instrument, trace, warn}; @@ -20,7 +18,6 @@ pub(crate) use self::authenticated::AuthenticatedHandler; pub(crate) use self::fresh::FreshHandler; pub(crate) mod authenticated; -pub(crate) mod ecash; mod fresh; // TODO: note for my future self to consider the following idea: @@ -132,15 +129,6 @@ pub(crate) async fn handle_connection( trace!("The handler is done!"); } -#[derive(Debug, Clone, Copy)] -pub(crate) struct BandwidthFlushingBehaviourConfig { - /// Defines maximum delay between client bandwidth information being flushed to the persistent storage. - pub client_bandwidth_max_flushing_rate: Duration, - - /// Defines a maximum change in client bandwidth before it gets flushed to the persistent storage. - pub client_bandwidth_max_delta_flushing_amount: i64, -} - impl<'a> From<&'a Config> for BandwidthFlushingBehaviourConfig { fn from(value: &'a Config) -> Self { BandwidthFlushingBehaviourConfig { @@ -151,43 +139,3 @@ impl<'a> From<&'a Config> for BandwidthFlushingBehaviourConfig { } } } - -pub(crate) struct ClientBandwidth { - pub(crate) bandwidth: AvailableBandwidth, - pub(crate) last_flushed: OffsetDateTime, - - /// the number of bytes the client had during the last sync. - /// it is used to determine whether the current value should be synced with the storage - /// by checking the delta with the known amount - pub(crate) bytes_at_last_sync: i64, - pub(crate) bytes_delta_since_sync: i64, -} - -impl ClientBandwidth { - pub(crate) fn new(bandwidth: AvailableBandwidth) -> ClientBandwidth { - ClientBandwidth { - bandwidth, - last_flushed: OffsetDateTime::now_utc(), - bytes_at_last_sync: bandwidth.bytes, - bytes_delta_since_sync: 0, - } - } - - pub(crate) fn should_sync(&self, cfg: BandwidthFlushingBehaviourConfig) -> bool { - if self.bytes_delta_since_sync.abs() >= cfg.client_bandwidth_max_delta_flushing_amount { - return true; - } - - if self.last_flushed + cfg.client_bandwidth_max_flushing_rate < OffsetDateTime::now_utc() { - return true; - } - - false - } - - pub(crate) fn update_sync_data(&mut self) { - self.last_flushed = OffsetDateTime::now_utc(); - self.bytes_at_last_sync = self.bandwidth.bytes; - self.bytes_delta_since_sync = 0; - } -} diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index dc4ceec66f..94cdcf7776 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -12,10 +12,12 @@ use crate::http::HttpApiBuilder; use crate::node::client_handling::active_clients::ActiveClientsStore; use crate::node::client_handling::embedded_clients::{LocalEmbeddedClientHandle, MessageRouter}; use crate::node::client_handling::websocket; -use crate::node::client_handling::websocket::connection_handler::ecash::EcashManager; use crate::node::helpers::{initialise_main_storage, load_network_requester_config}; use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; use futures::channel::{mpsc, oneshot}; +use nym_credential_verification::ecash::{ + credential_sender::CredentialHandlerConfig, EcashManager, +}; use nym_crypto::asymmetric::{encryption, identity}; use nym_mixnet_client::forwarder::{MixForwardingSender, PacketForwarder}; use nym_network_defaults::NymNetworkDetails; @@ -35,7 +37,6 @@ pub(crate) mod client_handling; pub(crate) mod helpers; pub(crate) mod mixnet_handling; -use crate::node::client_handling::websocket::connection_handler::ecash::credential_sender::CredentialHandlerConfig; pub use nym_gateway_storage::{PersistentStorage, Storage}; // TODO: should this struct live here? @@ -620,16 +621,14 @@ impl Gateway { .maximum_time_between_redemption, }; - let ecash_manager = { - EcashManager::new( - handler_config, - nyxd_client, - self.identity_keypair.public_key().to_bytes(), - shutdown.fork("EcashVerifier"), - self.storage.clone(), - ) - .await - }?; + let ecash_manager = EcashManager::new( + handler_config, + nyxd_client, + self.identity_keypair.public_key().to_bytes(), + shutdown.fork("EcashVerifier"), + self.storage.clone(), + ) + .await?; let mix_forwarding_channel = self.start_packet_forwarder(shutdown.fork("PacketForwarder"));