From 4e79ef1b122c39470352cb1cceb6c88ecd4ca933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 23 Jul 2024 17:00:37 +0100 Subject: [PATCH] updated all ecash-related parameters - bloomfilter, expiration, sizes, etc. --- Cargo.lock | 5 +- .../cli_helpers/client_show_ticketbooks.rs | 6 +- .../client-core/src/client/base_client/mod.rs | 8 +- .../gateway-client/src/client/config.rs | 135 +++++ .../src/{client.rs => client/mod.rs} | 121 ++--- .../client-libs/gateway-client/src/error.rs | 6 + common/network-defaults/Cargo.toml | 22 +- common/network-defaults/src/constants.rs | 57 +++ common/network-defaults/src/ecash.rs | 43 +- common/network-defaults/src/env_setup.rs | 67 +++ common/network-defaults/src/lib.rs | 476 +----------------- common/network-defaults/src/mainnet.rs | 24 +- common/network-defaults/src/network.rs | 353 +++++++++++++ common/nym_offline_compact_ecash/Cargo.toml | 1 + .../src/constants.rs | 6 +- gateway/src/node/client_handling/bandwidth.rs | 5 +- .../connection_handler/authenticated.rs | 2 +- .../ecash/credential_sender.rs | 4 +- nym-api/src/network_monitor/monitor/sender.rs | 13 +- nym-wallet/Cargo.lock | 5 +- 20 files changed, 768 insertions(+), 591 deletions(-) create mode 100644 common/client-libs/gateway-client/src/client/config.rs rename common/client-libs/gateway-client/src/{client.rs => client/mod.rs} (89%) create mode 100644 common/network-defaults/src/constants.rs create mode 100644 common/network-defaults/src/env_setup.rs create mode 100644 common/network-defaults/src/network.rs diff --git a/Cargo.lock b/Cargo.lock index 3b0583fa59..5fd80849e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4726,6 +4726,7 @@ dependencies = [ "ff", "group", "itertools 0.12.1", + "nym-network-defaults", "nym-pemstore", "rand 0.8.5", "rayon", @@ -5367,14 +5368,10 @@ dependencies = [ name = "nym-network-defaults" version = "0.1.0" dependencies = [ - "cfg-if", "dotenvy", - "hex-literal", "log", - "once_cell", "schemars", "serde", - "thiserror", "url", ] diff --git a/common/client-core/src/cli_helpers/client_show_ticketbooks.rs b/common/client-core/src/cli_helpers/client_show_ticketbooks.rs index 9c6e2e71ef..b988f1d79e 100644 --- a/common/client-core/src/cli_helpers/client_show_ticketbooks.rs +++ b/common/client-core/src/cli_helpers/client_show_ticketbooks.rs @@ -6,7 +6,7 @@ use crate::error::ClientCoreError; use nym_credential_storage::models::BasicTicketbookInformation; use nym_credential_storage::storage::Storage; use nym_ecash_time::ecash_today; -use nym_network_defaults::TICKET_BANDWIDTH_VALUE; +use nym_network_defaults::TicketbookType::MixnetEntry; use serde::{Deserialize, Serialize}; use time::Date; @@ -62,7 +62,9 @@ impl From for AvailableTicketbook { expiration: value.expiration_date, issued_tickets: value.total_tickets, claimed_tickets: value.used_tickets, - ticket_size: TICKET_BANDWIDTH_VALUE, + + // TODO: this will change when 'type' field is introduced; for now doesn't matter what we put there + ticket_size: MixnetEntry.bandwidth_value(), } } } diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index 804f16869c..7d494bce35 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -40,6 +40,7 @@ use nym_bandwidth_controller::BandwidthController; use nym_client_core_gateways_storage::{GatewayDetails, GatewaysDetailsStore}; use nym_credential_storage::storage::Storage as CredentialStorage; use nym_crypto::asymmetric::{encryption, identity}; +use nym_gateway_client::client::config::GatewayClientConfig; use nym_gateway_client::{ AcknowledgementReceiver, GatewayClient, GatewayConfig, MixnetMessageReceiver, PacketRouter, }; @@ -403,6 +404,11 @@ where gateway_listener, ); GatewayClient::new( + GatewayClientConfig::new_default() + .with_disabled_credentials_mode(config.client.disabled_credentials_mode) + .with_response_timeout( + config.debug.gateway_connection.gateway_response_timeout, + ), cfg, managed_keys.identity_keypair(), Some(details.derived_aes128_ctr_blake3_hmac_keys), @@ -410,8 +416,6 @@ where bandwidth_controller, shutdown, ) - .with_disabled_credentials_mode(config.client.disabled_credentials_mode) - .with_response_timeout(config.debug.gateway_connection.gateway_response_timeout) }; gateway_client diff --git a/common/client-libs/gateway-client/src/client/config.rs b/common/client-libs/gateway-client/src/client/config.rs new file mode 100644 index 0000000000..2461779d74 --- /dev/null +++ b/common/client-libs/gateway-client/src/client/config.rs @@ -0,0 +1,135 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::GatewayClientError; +use nym_network_defaults::TicketbookType::MixnetEntry; +use si_scale::helpers::bibytes2; +use std::time::Duration; + +#[derive(Debug, Default, Clone, Copy)] +pub struct GatewayClientConfig { + pub connection: Connection, + pub bandwidth: BandwidthTickets, +} + +impl GatewayClientConfig { + pub fn new_default() -> Self { + Default::default() + } + + #[must_use] + pub fn with_disabled_credentials_mode(mut self, disabled_credentials_mode: bool) -> Self { + self.bandwidth.require_tickets = !disabled_credentials_mode; + self + } + + #[must_use] + pub fn with_reconnection_on_failure(mut self, should_reconnect_on_failure: bool) -> Self { + self.connection.should_reconnect_on_failure = should_reconnect_on_failure; + self + } + + #[must_use] + pub fn with_response_timeout(mut self, response_timeout_duration: Duration) -> Self { + self.connection.response_timeout_duration = response_timeout_duration; + self + } + + #[must_use] + pub fn with_reconnection_attempts(mut self, reconnection_attempts: usize) -> Self { + self.connection.reconnection_attempts = reconnection_attempts; + self + } + + #[must_use] + pub fn with_reconnection_backoff(mut self, backoff: Duration) -> Self { + self.connection.reconnection_backoff = backoff; + self + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Connection { + /// Specifies the timeout for gateway responses + pub response_timeout_duration: Duration, + + /// Specifies whether client should try to reconnect to gateway on connection failure. + pub should_reconnect_on_failure: bool, + + /// Specifies maximum number of attempts client will try to reconnect to gateway on failure + /// before giving up. + pub reconnection_attempts: usize, + + /// Delay between each subsequent reconnection attempt. + pub reconnection_backoff: Duration, +} + +impl Connection { + // Set this to a high value for now, so that we don't risk sporadic timeouts that might cause + // bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the + // bandwidth bridging protocol, we can come back to a smaller timeout value + pub const DEFAULT_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60); + pub const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10; + pub const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5); +} + +impl Default for Connection { + fn default() -> Self { + Connection { + response_timeout_duration: Self::DEFAULT_RESPONSE_TIMEOUT, + should_reconnect_on_failure: true, + reconnection_attempts: Self::DEFAULT_RECONNECTION_ATTEMPTS, + reconnection_backoff: Self::DEFAULT_RECONNECTION_BACKOFF, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct BandwidthTickets { + /// specifies whether this client will be sending bandwidth tickets or will attempt to use 'free' testnet bandwidth instead + pub require_tickets: bool, + + /// specifies threshold (in bytes) under which the client should send another ticket to the gateway + pub remaining_bandwidth_threshold: i64, + + /// specifies threshold (in bytes) under which the client will NOT send any tickets because it got accused of double spending and got its bandwidth revoked + /// if not specified, the client will always send tickets + pub cutoff_remaining_bandwidth_threshold: Option, +} + +impl BandwidthTickets { + // TO BE CHANGED \/ + pub const DEFAULT_REQUIRES_TICKETS: bool = false; + + // 20% of entry ticket value + pub const DEFAULT_REMAINING_BANDWIDTH_THRESHOLD: i64 = + (MixnetEntry.bandwidth_value() / 5) as i64; + + pub const DEFAULT_CUTOFF_REMAINING_BANDWIDTH_THRESHOLD: Option = None; + + pub fn ensure_above_cutoff(&self, available: i64) -> Result<(), GatewayClientError> { + if let Some(cutoff) = self.cutoff_remaining_bandwidth_threshold { + if available < cutoff { + let available_bi2 = bibytes2(available as f64); + let cutoff_bi2 = bibytes2(cutoff as f64); + return Err(GatewayClientError::BandwidthBelowCutoffValue { + available_bi2, + cutoff_bi2, + }); + } + } + + Ok(()) + } +} + +impl Default for BandwidthTickets { + fn default() -> Self { + BandwidthTickets { + require_tickets: Self::DEFAULT_REQUIRES_TICKETS, + remaining_bandwidth_threshold: Self::DEFAULT_REMAINING_BANDWIDTH_THRESHOLD, + cutoff_remaining_bandwidth_threshold: + Self::DEFAULT_CUTOFF_REMAINING_BANDWIDTH_THRESHOLD, + } + } +} diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client/mod.rs similarity index 89% rename from common/client-libs/gateway-client/src/client.rs rename to common/client-libs/gateway-client/src/client/mod.rs index 5ab694faac..e544a610b6 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client/mod.rs @@ -1,6 +1,7 @@ // Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 - +use crate::bandwidth::ClientBandwidth; +use crate::client::config::GatewayClientConfig; use crate::error::GatewayClientError; use crate::packet_router::PacketRouter; pub use crate::packet_router::{ @@ -23,13 +24,11 @@ use nym_gateway_requests::{ BinaryRequest, ClientControlRequest, ServerResponse, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION, }; -use nym_network_defaults::REMAINING_BANDWIDTH_THRESHOLD; use nym_sphinx::forwarding::packet::MixPacket; use nym_task::TaskClient; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; use rand::rngs::OsRng; use std::sync::Arc; -use std::time::Duration; use tungstenite::protocol::Message; use url::Url; @@ -40,7 +39,6 @@ use tokio::time::sleep; #[cfg(not(target_arch = "wasm32"))] use tokio_tungstenite::connect_async; -use crate::bandwidth::ClientBandwidth; #[cfg(not(unix))] use std::os::raw::c_int as RawFd; #[cfg(target_arch = "wasm32")] @@ -48,12 +46,7 @@ use wasm_utils::websocket::JSWebsocket; #[cfg(target_arch = "wasm32")] use wasmtimer::tokio::sleep; -// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause -// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the -// bandwidth bridging protocol, we can come back to a smaller timeout value -const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60); -const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10; -const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5); +pub mod config; pub struct GatewayConfig { pub gateway_identity: identity::PublicKey, @@ -80,8 +73,9 @@ impl GatewayConfig { // TODO: this should be refactored into a state machine that keeps track of its authentication state pub struct GatewayClient { + pub cfg: GatewayClientConfig, + authenticated: bool, - disabled_credentials_mode: bool, bandwidth: ClientBandwidth, gateway_address: String, gateway_identity: identity::PublicKey, @@ -89,18 +83,8 @@ pub struct GatewayClient { shared_key: Option>, connection: SocketState, packet_router: PacketRouter, - response_timeout_duration: Duration, bandwidth_controller: Option>, - // reconnection related variables - /// Specifies whether client should try to reconnect to gateway on connection failure. - should_reconnect_on_failure: bool, - /// Specifies maximum number of attempts client will try to reconnect to gateway on failure - /// before giving up. - reconnection_attempts: usize, - /// Delay between each subsequent reconnection attempt. - reconnection_backoff: Duration, - // currently unused (but populated) negotiated_protocol: Option, @@ -110,7 +94,8 @@ pub struct GatewayClient { impl GatewayClient { pub fn new( - config: GatewayConfig, + cfg: GatewayClientConfig, + gateway_config: GatewayConfig, local_identity: Arc, // TODO: make it mandatory. if you don't want to pass it, use `new_init` shared_key: Option>, @@ -119,55 +104,21 @@ impl GatewayClient { task_client: TaskClient, ) -> Self { GatewayClient { + cfg, authenticated: false, - disabled_credentials_mode: true, bandwidth: ClientBandwidth::new_empty(), - gateway_address: config.gateway_listener, - gateway_identity: config.gateway_identity, + gateway_address: gateway_config.gateway_listener, + gateway_identity: gateway_config.gateway_identity, local_identity, shared_key, connection: SocketState::NotConnected, packet_router, - response_timeout_duration: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, bandwidth_controller, - should_reconnect_on_failure: true, - reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS, - reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF, negotiated_protocol: None, task_client, } } - #[must_use] - pub fn with_disabled_credentials_mode(mut self, disabled_credentials_mode: bool) -> Self { - self.disabled_credentials_mode = disabled_credentials_mode; - self - } - - #[must_use] - pub fn with_reconnection_on_failure(mut self, should_reconnect_on_failure: bool) -> Self { - self.should_reconnect_on_failure = should_reconnect_on_failure; - self - } - - #[must_use] - pub fn with_response_timeout(mut self, response_timeout_duration: Duration) -> Self { - self.response_timeout_duration = response_timeout_duration; - self - } - - #[must_use] - pub fn with_reconnection_attempts(mut self, reconnection_attempts: usize) -> Self { - self.reconnection_attempts = reconnection_attempts; - self - } - - #[must_use] - pub fn with_reconnection_backoff(mut self, backoff: Duration) -> Self { - self.reconnection_backoff = backoff; - self - } - pub fn gateway_identity(&self) -> identity::PublicKey { self.gateway_identity } @@ -257,18 +208,21 @@ impl GatewayClient { info!("Attempting gateway reconnection..."); self.authenticated = false; - for i in 1..self.reconnection_attempts { + for i in 1..self.cfg.connection.reconnection_attempts { info!("reconnection attempt {}...", i); if self.try_reconnect().await.is_ok() { info!("managed to reconnect!"); return Ok(()); } - sleep(self.reconnection_backoff).await; + sleep(self.cfg.connection.reconnection_backoff).await; } // final attempt (done separately to be able to return a proper error) - info!("reconnection attempt {}", self.reconnection_attempts); + info!( + "reconnection attempt {}", + self.cfg.connection.reconnection_attempts + ); match self.try_reconnect().await { Ok(_) => { info!("managed to reconnect!"); @@ -277,7 +231,7 @@ impl GatewayClient { Err(err) => { error!( "failed to reconnect after {} attempts", - self.reconnection_attempts + self.cfg.connection.reconnection_attempts ); Err(err) } @@ -293,7 +247,7 @@ impl GatewayClient { _ => return Err(GatewayClientError::ConnectionInInvalidState), }; - let timeout = sleep(self.response_timeout_duration); + let timeout = sleep(self.cfg.connection.response_timeout_duration); tokio::pin!(timeout); loop { @@ -462,7 +416,7 @@ impl GatewayClient { ws_stream, self.local_identity.as_ref(), self.gateway_identity, - !self.disabled_credentials_mode, + self.cfg.bandwidth.require_tickets, ) .await .map_err(GatewayClientError::RegistrationFailure), @@ -524,7 +478,7 @@ impl GatewayClient { self_address, encrypted_address, iv, - !self.disabled_credentials_mode, + self.cfg.bandwidth.require_tickets, ) .into(); @@ -638,12 +592,12 @@ impl GatewayClient { if self.shared_key.is_none() { return Err(GatewayClientError::NoSharedKeyAvailable); } - if self.bandwidth_controller.is_none() && !self.disabled_credentials_mode { + if self.bandwidth_controller.is_none() && self.cfg.bandwidth.require_tickets { return Err(GatewayClientError::NoBandwidthControllerAvailable); } warn!("Not enough bandwidth. Trying to get more bandwidth, this might take a while"); - if self.disabled_credentials_mode { + if !self.cfg.bandwidth.require_tickets { info!("The client is running in disabled credentials mode - attempting to claim bandwidth without a credential"); return self.try_claim_testnet_bandwidth().await; } @@ -698,7 +652,10 @@ impl GatewayClient { return Err(GatewayClientError::NotAuthenticated); } let bandwidth_remaining = self.bandwidth.remaining(); - if bandwidth_remaining < REMAINING_BANDWIDTH_THRESHOLD { + if bandwidth_remaining < self.cfg.bandwidth.remaining_bandwidth_threshold { + self.cfg + .bandwidth + .ensure_above_cutoff(bandwidth_remaining)?; self.claim_bandwidth().await?; } @@ -721,7 +678,7 @@ impl GatewayClient { .batch_send_websocket_messages_without_response(messages) .await { - if err.is_closed_connection() && self.should_reconnect_on_failure { + if err.is_closed_connection() && self.cfg.connection.should_reconnect_on_failure { self.attempt_reconnection().await } else { Err(err) @@ -736,7 +693,7 @@ impl GatewayClient { msg: Message, ) -> Result<(), GatewayClientError> { if let Err(err) = self.send_websocket_message_without_response(msg).await { - if err.is_closed_connection() && self.should_reconnect_on_failure { + if err.is_closed_connection() && self.cfg.connection.should_reconnect_on_failure { debug!("Going to attempt a reconnection"); self.attempt_reconnection().await } else { @@ -770,7 +727,10 @@ impl GatewayClient { return Err(GatewayClientError::NotAuthenticated); } let bandwidth_remaining = self.bandwidth.remaining(); - if bandwidth_remaining < REMAINING_BANDWIDTH_THRESHOLD { + if bandwidth_remaining < self.cfg.bandwidth.remaining_bandwidth_threshold { + self.cfg + .bandwidth + .ensure_above_cutoff(bandwidth_remaining)?; self.claim_bandwidth().await?; } @@ -869,7 +829,10 @@ impl GatewayClient { } let shared_key = self.perform_initial_authentication().await?; let bandwidth_remaining = self.bandwidth.remaining(); - if bandwidth_remaining < REMAINING_BANDWIDTH_THRESHOLD { + if bandwidth_remaining < self.cfg.bandwidth.remaining_bandwidth_threshold { + self.cfg + .bandwidth + .ensure_above_cutoff(bandwidth_remaining)?; info!("Claiming more bandwidth with existing credentials. Stop the process now if you don't want that to happen."); self.claim_bandwidth().await?; } @@ -905,8 +868,8 @@ impl GatewayClient { let packet_router = PacketRouter::new(ack_tx, mix_tx, task_client.clone()); GatewayClient { + cfg: GatewayClientConfig::default().with_disabled_credentials_mode(true), authenticated: false, - disabled_credentials_mode: true, bandwidth: ClientBandwidth::new_empty(), gateway_address: gateway_listener.to_string(), gateway_identity, @@ -914,11 +877,7 @@ impl GatewayClient { shared_key: None, connection: SocketState::NotConnected, packet_router, - response_timeout_duration: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, bandwidth_controller: None, - should_reconnect_on_failure: false, - reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS, - reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF, negotiated_protocol: None, task_client, } @@ -937,8 +896,8 @@ impl GatewayClient { assert!(self.shared_key.is_some()); GatewayClient { + cfg: self.cfg, authenticated: self.authenticated, - disabled_credentials_mode: self.disabled_credentials_mode, bandwidth: self.bandwidth, gateway_address: self.gateway_address, gateway_identity: self.gateway_identity, @@ -946,11 +905,7 @@ impl GatewayClient { shared_key: self.shared_key, connection: self.connection, packet_router, - response_timeout_duration: self.response_timeout_duration, bandwidth_controller, - should_reconnect_on_failure: self.should_reconnect_on_failure, - reconnection_attempts: self.reconnection_attempts, - reconnection_backoff: self.reconnection_backoff, negotiated_protocol: self.negotiated_protocol, task_client, } diff --git a/common/client-libs/gateway-client/src/error.rs b/common/client-libs/gateway-client/src/error.rs index a135daf561..0e7757a46c 100644 --- a/common/client-libs/gateway-client/src/error.rs +++ b/common/client-libs/gateway-client/src/error.rs @@ -67,6 +67,12 @@ pub enum GatewayClientError { #[error("There are no more bandwidth credentials acquired. Please buy some more if you want to use the mixnet")] NoMoreBandwidthCredentials, + #[error("the current available bandwidth ({available_bi2}) is below the minimum cutoff threshold off {cutoff_bi2}")] + BandwidthBelowCutoffValue { + available_bi2: String, + cutoff_bi2: String, + }, + #[error("Received an unexpected response")] UnexpectedResponse, diff --git a/common/network-defaults/Cargo.toml b/common/network-defaults/Cargo.toml index 562f12559c..03d09cf159 100644 --- a/common/network-defaults/Cargo.toml +++ b/common/network-defaults/Cargo.toml @@ -8,12 +8,16 @@ license.workspace = true repository.workspace = true [dependencies] -cfg-if = { workspace = true } -dotenvy = { workspace = true } -hex-literal = { workspace = true } -log = { workspace = true } -once_cell = { workspace = true } -schemars = { workspace = true, features = ["preserve_order"] } -serde = { workspace = true, features = ["derive"] } -thiserror = { workspace = true } -url = { workspace = true } +dotenvy = { workspace = true, optional = true } +log = { workspace = true, optional = true } +schemars = { workspace = true, features = ["preserve_order"], optional = true } +serde = { workspace = true, features = ["derive"], optional = true } +url = { workspace = true, optional = true } + +# please be extremely careful when adding new dependencies because this crate is imported by the ecash contract, +# so if anything new is added, consider feature-locking it and then just adding it to default feature + +[features] +default = ["env", "network"] +env = ["dotenvy", "log"] +network = ["schemars", "serde", "url"] \ No newline at end of file diff --git a/common/network-defaults/src/constants.rs b/common/network-defaults/src/constants.rs new file mode 100644 index 0000000000..91bb5c7931 --- /dev/null +++ b/common/network-defaults/src/constants.rs @@ -0,0 +1,57 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +// re-export to not break existing imports +pub use deprecated::*; +pub use nyx::*; +pub use wireguard::*; + +// all of those should be obtained via nym-node, et al. crate instead +pub mod deprecated { + pub const DEFAULT_MIX_LISTENING_PORT: u16 = 1789; + + // 'GATEWAY' + pub const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000; + + // 'MIXNODE' + pub const DEFAULT_VERLOC_LISTENING_PORT: u16 = 1790; + pub const DEFAULT_HTTP_API_LISTENING_PORT: u16 = 8000; + + // 'CLIENT' + pub const DEFAULT_WEBSOCKET_LISTENING_PORT: u16 = 1977; + + // 'SOCKS5' CLIENT + pub const DEFAULT_SOCKS5_LISTENING_PORT: u16 = 1080; + + // NYM-API + pub const DEFAULT_NYM_API_PORT: u16 = 8080; + + pub const NYM_API_VERSION: &str = "v1"; + + // NYM-NODE + pub const DEFAULT_NYM_NODE_HTTP_PORT: u16 = 8080; +} + +pub mod nyx { + /// Defaults Cosmos Hub/ATOM path + pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0"; + + // as set by validators in their configs + // (note that the 'amount' postfix is relevant here as the full gas price also includes denom) + pub const GAS_PRICE_AMOUNT: f64 = 0.025; + + // TODO: is there a way to get this from the chain + pub const TOTAL_SUPPLY: u128 = 1_000_000_000_000_000; +} + +pub mod wireguard { + use std::net::{IpAddr, Ipv4Addr}; + + pub const WG_PORT: u16 = 51822; + + // The interface used to route traffic + pub const WG_TUN_BASE_NAME: &str = "nymwg"; + pub const WG_TUN_DEVICE_ADDRESS: &str = "10.1.0.1"; + pub const WG_TUN_DEVICE_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 1, 0, 1)); + pub const WG_TUN_DEVICE_NETMASK: &str = "255.255.255.0"; +} diff --git a/common/network-defaults/src/ecash.rs b/common/network-defaults/src/ecash.rs index 944abdff27..35a49d88f0 100644 --- a/common/network-defaults/src/ecash.rs +++ b/common/network-defaults/src/ecash.rs @@ -1,20 +1,47 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -/// How much bandwidth (in bytes) one ticket can buy -pub const TICKET_BANDWIDTH_VALUE: u64 = 100 * 1024 * 1024; // 100 MB +/// Specifies the maximum validity of the issued ticketbooks. +pub const TICKETBOOK_VALIDITY_DAYS: u64 = 7; -///Tickets to spend per payment -pub const SPEND_TICKETS: u64 = 1; -/// Threshold for claiming more bandwidth: 1 MB -pub const REMAINING_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024; +/// Specifies the number of tickets in each issued ticketbook. +pub const TICKETBOOK_SIZE: u64 = 50; + +#[derive(Default, Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum TicketbookType { + #[default] + MixnetEntry = 0, + MixnetExit = 1, + WireguardEntry = 2, + WireguardExit = 3, +} + +impl TicketbookType { + pub const WIREGUARD_ENTRY_TICKET_SIZE: u64 = 500 * 1024 * 1024; // 500 MB + + // TBD: + pub const WIREGUARD_EXIT_TICKET_SIZE: u64 = Self::WIREGUARD_ENTRY_TICKET_SIZE; + pub const MIXNET_ENTRY_TICKET_SIZE: u64 = Self::WIREGUARD_ENTRY_TICKET_SIZE; + pub const MIXNET_EXIT_TICKET_SIZE: u64 = Self::WIREGUARD_ENTRY_TICKET_SIZE; + + /// How much bandwidth (in bytes) one ticket can grant + pub const fn bandwidth_value(&self) -> u64 { + match self { + TicketbookType::MixnetEntry => Self::MIXNET_ENTRY_TICKET_SIZE, + TicketbookType::MixnetExit => Self::MIXNET_EXIT_TICKET_SIZE, + TicketbookType::WireguardEntry => Self::WIREGUARD_ENTRY_TICKET_SIZE, + TicketbookType::WireguardExit => Self::WIREGUARD_EXIT_TICKET_SIZE, + } + } +} // Constants for bloom filter for double spending detection //Chosen for FP of //Calculator at https://hur.st/bloomfilter/ pub const ECASH_DS_BLOOMFILTER_PARAMS: BloomfilterParameters = BloomfilterParameters { - num_hashes: 13, - bitmap_size: 250_000, + num_hashes: 10, + bitmap_size: 1_500_000_000, sip_keys: [ (12345678910111213141, 1415926535897932384), (7182818284590452353, 3571113171923293137), diff --git a/common/network-defaults/src/env_setup.rs b/common/network-defaults/src/env_setup.rs new file mode 100644 index 0000000000..4ab07259a7 --- /dev/null +++ b/common/network-defaults/src/env_setup.rs @@ -0,0 +1,67 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::var_names; +use crate::var_names::{DEPRECATED_API_VALIDATOR, DEPRECATED_NYMD_VALIDATOR, NYM_API, NYXD}; +use std::path::Path; + +fn fix_deprecated_environmental_variables() { + // if we're using the outdated environmental variables, set the updated ones to preserve compatibility + if let Ok(nyxd) = std::env::var(DEPRECATED_NYMD_VALIDATOR) { + if std::env::var(NYXD).is_err() { + std::env::set_var(NYXD, nyxd) + } + } + if let Ok(nym_apis) = std::env::var(DEPRECATED_API_VALIDATOR) { + if std::env::var(NYM_API).is_err() { + std::env::set_var(NYM_API, nym_apis) + } + } +} + +// Read the variables from the file and log what the corresponding values in the environment are. +fn print_env_vars_with_keys_in_file + Copy>(config_env_file: P) { + let items = dotenvy::from_path_iter(config_env_file) + .expect("Invalid path to environment configuration file"); + for item in items { + let (key, val) = item.expect("Invalid item in environment configuration file"); + log::debug!("{}: {}", key, val); + } +} + +pub fn setup_env>(config_env_file: Option

) { + match std::env::var(var_names::CONFIGURED) { + // if the configuration is not already set in the env vars + Err(std::env::VarError::NotPresent) => { + if let Some(config_env_file) = &config_env_file { + log::debug!( + "Loading environment variables from {:?}", + config_env_file.as_ref() + ); + dotenvy::from_path(config_env_file) + .expect("Invalid path to environment configuration file"); + fix_deprecated_environmental_variables(); + } else { + // if nothing is set, the use mainnet defaults + // if the user has not set `CONFIGURED`, then even if they set any of the env variables, + // overwrite them + log::debug!("Loading mainnet defaults"); + crate::mainnet::export_to_env(); + } + } + Err(_) => { + log::debug!("Environment variables already set. Using them"); + crate::mainnet::export_to_env() + } + _ => { + fix_deprecated_environmental_variables(); + } + } + + // if we haven't explicitly defined any of the constants, fallback to defaults + crate::mainnet::export_to_env_if_not_set(); + + if let Some(config_env_file) = &config_env_file { + print_env_vars_with_keys_in_file(config_env_file); + } +} diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index edbcbdc498..a25caff469 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -1,471 +1,23 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::var_names::{DEPRECATED_API_VALIDATOR, DEPRECATED_NYMD_VALIDATOR, NYM_API, NYXD}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::net::{IpAddr, Ipv4Addr}; -use std::path::Path; -use std::{ - env::{var, VarError}, - ffi::OsStr, - ops::Not, -}; -use url::Url; - +pub mod constants; pub mod ecash; + +#[cfg(all(feature = "env", feature = "network"))] +pub mod env_setup; pub mod mainnet; +#[cfg(feature = "network")] +pub mod network; + +#[cfg(feature = "env")] pub mod var_names; pub use ecash::*; -#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)] -pub struct ChainDetails { - pub bech32_account_prefix: String, - pub mix_denom: DenomDetailsOwned, - pub stake_denom: DenomDetailsOwned, -} - -#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)] -pub struct NymContracts { - pub mixnet_contract_address: Option, - pub vesting_contract_address: Option, - pub ecash_contract_address: Option, - pub group_contract_address: Option, - pub multisig_contract_address: Option, - pub coconut_dkg_contract_address: Option, -} - -// I wanted to use the simpler `NetworkDetails` name, but there's a clash -// with `NetworkDetails` defined in all.rs... -#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)] -pub struct NymNetworkDetails { - pub network_name: String, - pub chain_details: ChainDetails, - pub endpoints: Vec, - pub contracts: NymContracts, - pub explorer_api: Option, -} - -// by default we assume the same defaults as mainnet, i.e. same prefixes and denoms -impl Default for NymNetworkDetails { - fn default() -> Self { - NymNetworkDetails::new_mainnet() - } -} - -impl NymNetworkDetails { - pub fn new_empty() -> Self { - NymNetworkDetails { - network_name: Default::default(), - chain_details: ChainDetails { - bech32_account_prefix: Default::default(), - mix_denom: DenomDetailsOwned { - base: Default::default(), - display: Default::default(), - display_exponent: Default::default(), - }, - stake_denom: DenomDetailsOwned { - base: Default::default(), - display: Default::default(), - display_exponent: Default::default(), - }, - }, - endpoints: Default::default(), - contracts: Default::default(), - explorer_api: Default::default(), - } - } - - pub fn new_from_env() -> Self { - fn get_optional_env>(env: K) -> Option { - match var(env) { - Ok(var) => { - if var.is_empty() { - None - } else { - Some(var) - } - } - Err(VarError::NotPresent) => None, - err => panic!("Unable to set: {:?}", err), - } - } - - NymNetworkDetails::new_empty() - .with_network_name(var(var_names::NETWORK_NAME).expect("network name not set")) - .with_bech32_account_prefix( - var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"), - ) - .with_mix_denom(DenomDetailsOwned { - base: var(var_names::MIX_DENOM).expect("mix denomination base not set"), - display: var(var_names::MIX_DENOM_DISPLAY) - .expect("mix denomination display not set"), - display_exponent: var(var_names::DENOMS_EXPONENT) - .expect("denomination exponent not set") - .parse() - .expect("denomination exponent is not u32"), - }) - .with_stake_denom(DenomDetailsOwned { - base: var(var_names::STAKE_DENOM).expect("stake denomination base not set"), - display: var(var_names::STAKE_DENOM_DISPLAY) - .expect("stake denomination display not set"), - display_exponent: var(var_names::DENOMS_EXPONENT) - .expect("denomination exponent not set") - .parse() - .expect("denomination exponent is not u32"), - }) - .with_additional_validator_endpoint(ValidatorDetails::new( - var(var_names::NYXD).expect("nyxd validator not set"), - Some(var(var_names::NYM_API).expect("nym api not set")), - get_optional_env(var_names::NYXD_WEBSOCKET), - )) - .with_mixnet_contract(get_optional_env(var_names::MIXNET_CONTRACT_ADDRESS)) - .with_vesting_contract(get_optional_env(var_names::VESTING_CONTRACT_ADDRESS)) - .with_ecash_contract(get_optional_env(var_names::ECASH_CONTRACT_ADDRESS)) - .with_group_contract(get_optional_env(var_names::GROUP_CONTRACT_ADDRESS)) - .with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS)) - .with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS)) - .with_explorer_api(get_optional_env(var_names::EXPLORER_API)) - } - - pub fn new_mainnet() -> Self { - fn parse_optional_str(raw: &str) -> Option { - raw.is_empty().not().then(|| raw.into()) - } - - // Consider caching this process (lazy static) - NymNetworkDetails { - network_name: mainnet::NETWORK_NAME.into(), - chain_details: ChainDetails { - bech32_account_prefix: mainnet::BECH32_PREFIX.into(), - mix_denom: mainnet::MIX_DENOM.into(), - stake_denom: mainnet::STAKE_DENOM.into(), - }, - endpoints: mainnet::validators(), - contracts: NymContracts { - mixnet_contract_address: parse_optional_str(mainnet::MIXNET_CONTRACT_ADDRESS), - vesting_contract_address: parse_optional_str(mainnet::VESTING_CONTRACT_ADDRESS), - ecash_contract_address: parse_optional_str(mainnet::ECASH_CONTRACT_ADDRESS), - group_contract_address: parse_optional_str(mainnet::GROUP_CONTRACT_ADDRESS), - multisig_contract_address: parse_optional_str(mainnet::MULTISIG_CONTRACT_ADDRESS), - coconut_dkg_contract_address: parse_optional_str( - mainnet::COCONUT_DKG_CONTRACT_ADDRESS, - ), - }, - explorer_api: parse_optional_str(mainnet::EXPLORER_API), - } - } - - pub fn default_gas_price_amount(&self) -> f64 { - GAS_PRICE_AMOUNT - } - - #[must_use] - pub fn with_network_name(mut self, network_name: String) -> Self { - self.network_name = network_name; - self - } - - #[must_use] - pub fn with_chain_details(mut self, chain_details: ChainDetails) -> Self { - self.chain_details = chain_details; - self - } - - #[must_use] - pub fn with_bech32_account_prefix>(mut self, prefix: S) -> Self { - self.chain_details.bech32_account_prefix = prefix.into(); - self - } - - #[must_use] - pub fn with_mix_denom(mut self, mix_denom: DenomDetailsOwned) -> Self { - self.chain_details.mix_denom = mix_denom; - self - } - - #[must_use] - pub fn with_stake_denom(mut self, stake_denom: DenomDetailsOwned) -> Self { - self.chain_details.stake_denom = stake_denom; - self - } - - #[must_use] - pub fn with_base_mix_denom>(mut self, base_mix_denom: S) -> Self { - self.chain_details.mix_denom = DenomDetailsOwned::base_only(base_mix_denom.into()); - self - } - - #[must_use] - pub fn with_base_stake_denom>(mut self, base_stake_denom: S) -> Self { - self.chain_details.stake_denom = DenomDetailsOwned::base_only(base_stake_denom.into()); - self - } - - #[must_use] - pub fn with_additional_validator_endpoint(mut self, endpoint: ValidatorDetails) -> Self { - self.endpoints.push(endpoint); - self - } - - #[must_use] - pub fn with_validator_endpoint(mut self, endpoint: ValidatorDetails) -> Self { - self.endpoints = vec![endpoint]; - self - } - - #[must_use] - pub fn with_contracts(mut self, contracts: NymContracts) -> Self { - self.contracts = contracts; - self - } - - #[must_use] - pub fn with_mixnet_contract>(mut self, contract: Option) -> Self { - self.contracts.mixnet_contract_address = contract.map(Into::into); - self - } - - #[must_use] - pub fn with_vesting_contract>(mut self, contract: Option) -> Self { - self.contracts.vesting_contract_address = contract.map(Into::into); - self - } - - #[must_use] - pub fn with_ecash_contract>(mut self, contract: Option) -> Self { - self.contracts.ecash_contract_address = contract.map(Into::into); - self - } - - #[must_use] - pub fn with_group_contract>(mut self, contract: Option) -> Self { - self.contracts.group_contract_address = contract.map(Into::into); - self - } - - #[must_use] - pub fn with_multisig_contract>(mut self, contract: Option) -> Self { - self.contracts.multisig_contract_address = contract.map(Into::into); - self - } - - #[must_use] - pub fn with_coconut_dkg_contract>(mut self, contract: Option) -> Self { - self.contracts.coconut_dkg_contract_address = contract.map(Into::into); - self - } - - #[must_use] - pub fn with_explorer_api>(mut self, endpoint: Option) -> Self { - self.explorer_api = endpoint.map(Into::into); - self - } -} - -#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)] -pub struct DenomDetails { - pub base: &'static str, - pub display: &'static str, - // i.e. display_amount * 10^display_exponent = base_amount - pub display_exponent: u32, -} - -impl DenomDetails { - pub const fn new(base: &'static str, display: &'static str, display_exponent: u32) -> Self { - DenomDetails { - base, - display, - display_exponent, - } - } -} - -#[derive(Debug, Serialize, Deserialize, Hash, Clone, PartialEq, Eq, JsonSchema)] -pub struct DenomDetailsOwned { - pub base: String, - pub display: String, - // i.e. display_amount * 10^display_exponent = base_amount - pub display_exponent: u32, -} - -impl From for DenomDetailsOwned { - fn from(details: DenomDetails) -> Self { - DenomDetailsOwned { - base: details.base.to_owned(), - display: details.display.to_owned(), - display_exponent: details.display_exponent, - } - } -} - -impl DenomDetailsOwned { - pub fn base_only(base: String) -> Self { - DenomDetailsOwned { - base: base.clone(), - display: base, - display_exponent: 0, - } - } -} - -#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)] -pub struct ValidatorDetails { - // it is assumed those values are always valid since they're being provided in our defaults file - pub nyxd_url: String, - // - pub websocket_url: Option, - - // Right now api_url is optional as we are not running the api reliably on all validators - // however, later on it should be a mandatory field - pub api_url: Option, - // TODO: I'd argue this one should also have a field like `gas_price` since its a validator-specific setting -} - -impl ValidatorDetails { - pub fn new>(nyxd_url: S, api_url: Option, websocket_url: Option) -> Self { - ValidatorDetails { - nyxd_url: nyxd_url.into(), - websocket_url: websocket_url.map(Into::into), - api_url: api_url.map(Into::into), - } - } - - pub fn new_nyxd_only>(nyxd_url: S) -> Self { - ValidatorDetails { - nyxd_url: nyxd_url.into(), - websocket_url: None, - api_url: None, - } - } - - pub fn nyxd_url(&self) -> Url { - self.nyxd_url - .parse() - .expect("the provided nyxd url is invalid!") - } - - pub fn api_url(&self) -> Option { - self.api_url - .as_ref() - .map(|url| url.parse().expect("the provided api url is invalid!")) - } - - pub fn websocket_url(&self) -> Option { - self.websocket_url - .as_ref() - .map(|url| url.parse().expect("the provided websocket url is invalid!")) - } -} - -fn fix_deprecated_environmental_variables() { - // if we're using the outdated environmental variables, set the updated ones to preserve compatibility - if let Ok(nyxd) = std::env::var(DEPRECATED_NYMD_VALIDATOR) { - if std::env::var(NYXD).is_err() { - std::env::set_var(NYXD, nyxd) - } - } - if let Ok(nym_apis) = std::env::var(DEPRECATED_API_VALIDATOR) { - if std::env::var(NYM_API).is_err() { - std::env::set_var(NYM_API, nym_apis) - } - } -} - -// Read the variables from the file and log what the corresponding values in the environment are. -fn print_env_vars_with_keys_in_file + Copy>(config_env_file: P) { - let items = dotenvy::from_path_iter(config_env_file) - .expect("Invalid path to environment configuration file"); - for item in items { - let (key, val) = item.expect("Invalid item in environment configuration file"); - log::debug!("{}: {}", key, val); - } -} - -pub fn setup_env>(config_env_file: Option

) { - match std::env::var(var_names::CONFIGURED) { - // if the configuration is not already set in the env vars - Err(std::env::VarError::NotPresent) => { - if let Some(config_env_file) = &config_env_file { - log::debug!( - "Loading environment variables from {:?}", - config_env_file.as_ref() - ); - dotenvy::from_path(config_env_file) - .expect("Invalid path to environment configuration file"); - fix_deprecated_environmental_variables(); - } else { - // if nothing is set, the use mainnet defaults - // if the user has not set `CONFIGURED`, then even if they set any of the env variables, - // overwrite them - log::debug!("Loading mainnet defaults"); - crate::mainnet::export_to_env(); - } - } - Err(_) => { - log::debug!("Environment variables already set. Using them"); - crate::mainnet::export_to_env() - } - _ => { - fix_deprecated_environmental_variables(); - } - } - - // if we haven't explicitly defined any of the constants, fallback to defaults - crate::mainnet::export_to_env_if_not_set(); - - if let Some(config_env_file) = &config_env_file { - print_env_vars_with_keys_in_file(config_env_file); - } -} - -/// Defaults Cosmos Hub/ATOM path -pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0"; -// as set by validators in their configs -// (note that the 'amount' postfix is relevant here as the full gas price also includes denom) -pub const GAS_PRICE_AMOUNT: f64 = 0.025; - -pub const DEFAULT_MIX_LISTENING_PORT: u16 = 1789; - -// 'GATEWAY' -pub const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000; - -// 'MIXNODE' -pub const DEFAULT_VERLOC_LISTENING_PORT: u16 = 1790; -pub const DEFAULT_HTTP_API_LISTENING_PORT: u16 = 8000; - -// 'CLIENT' -pub const DEFAULT_WEBSOCKET_LISTENING_PORT: u16 = 1977; - -// 'SOCKS5' CLIENT -pub const DEFAULT_SOCKS5_LISTENING_PORT: u16 = 1080; - -// NYM-API -pub const DEFAULT_NYM_API_PORT: u16 = 8080; - -pub const NYM_API_VERSION: &str = "v1"; - -// NYM-NODE -pub const DEFAULT_NYM_NODE_HTTP_PORT: u16 = 8080; - -// REWARDING - -/// We'll be assuming a few more things, profit margin and cost function. Since we don't have reliable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate interval costs to Nyms. We'll also assume a cost of 40$ per interval(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms -// pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$ -// pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 55_556; // 40$/1hr at 1 Nym == 1$ -// pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 9259; // 40$/1hr/6 at 1 Nym == 1$ - -// TODO: is there a way to get this from the chain -pub const TOTAL_SUPPLY: u128 = 1_000_000_000_000_000; - -pub const DEFAULT_PROFIT_MARGIN: u8 = 10; - -// WIREGUARD -pub const WG_PORT: u16 = 51822; - -// The interface used to route traffic -pub const WG_TUN_BASE_NAME: &str = "nymwg"; -pub const WG_TUN_DEVICE_ADDRESS: &str = "10.1.0.1"; -pub const WG_TUN_DEVICE_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 1, 0, 1)); -pub const WG_TUN_DEVICE_NETMASK: &str = "255.255.255.0"; +// re-export everything to not break existing imports +pub use constants::*; +#[cfg(all(feature = "env", feature = "network"))] +pub use env_setup::*; +#[cfg(feature = "network")] +pub use network::*; diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index 10575c2729..8ed056f1de 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -1,15 +1,16 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::var_names; +#[cfg(feature = "network")] use crate::{DenomDetails, ValidatorDetails}; -use std::str::FromStr; pub const NETWORK_NAME: &str = "mainnet"; pub const BECH32_PREFIX: &str = "n"; +#[cfg(feature = "network")] pub const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6); +#[cfg(feature = "network")] pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6); pub const MIXNET_CONTRACT_ADDRESS: &str = @@ -36,6 +37,7 @@ pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/"; pub const EXIT_POLICY_URL: &str = "https://nymtech.net/.wellknown/network-requester/exit-policy.txt"; +#[cfg(feature = "network")] pub(crate) fn validators() -> Vec { vec![ValidatorDetails::new( NYXD_URL, @@ -44,23 +46,28 @@ pub(crate) fn validators() -> Vec { )] } +#[cfg(feature = "env")] const DEFAULT_SUFFIX: &str = "_MAINNET_DEFAULT"; +#[cfg(all(feature = "env", feature = "network"))] fn set_var_to_default(var: &str, value: &str) { std::env::set_var(var, value); std::env::set_var(format!("{var}{DEFAULT_SUFFIX}"), "1") } +#[cfg(all(feature = "env", feature = "network"))] fn set_var_conditionally_to_default(var: &str, value: &str) { if std::env::var(var).is_err() { set_var_to_default(var, value) } } +#[cfg(feature = "env")] pub fn uses_default(var: &str) -> bool { std::env::var(format!("{var}{DEFAULT_SUFFIX}")).is_ok() } +#[cfg(feature = "env")] pub fn read_var_if_not_default(var: &str) -> Option { if uses_default(var) { None @@ -69,13 +76,19 @@ pub fn read_var_if_not_default(var: &str) -> Option { } } -pub fn read_parsed_var_if_not_default(var: &str) -> Option> { +#[cfg(feature = "env")] +pub fn read_parsed_var_if_not_default( + var: &str, +) -> Option> { read_var_if_not_default(var) .as_deref() - .map(FromStr::from_str) + .map(std::str::FromStr::from_str) } +#[cfg(all(feature = "env", feature = "network"))] pub fn export_to_env() { + use crate::var_names; + set_var_to_default(var_names::CONFIGURED, "true"); set_var_to_default(var_names::NETWORK_NAME, NETWORK_NAME); set_var_to_default(var_names::BECH32_PREFIX, BECH32_PREFIX); @@ -113,7 +126,10 @@ pub fn export_to_env() { set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL); } +#[cfg(all(feature = "env", feature = "network"))] pub fn export_to_env_if_not_set() { + use crate::var_names; + set_var_conditionally_to_default(var_names::CONFIGURED, "true"); set_var_conditionally_to_default(var_names::NETWORK_NAME, NETWORK_NAME); set_var_conditionally_to_default(var_names::BECH32_PREFIX, BECH32_PREFIX); diff --git a/common/network-defaults/src/network.rs b/common/network-defaults/src/network.rs new file mode 100644 index 0000000000..36abde1727 --- /dev/null +++ b/common/network-defaults/src/network.rs @@ -0,0 +1,353 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::{mainnet, GAS_PRICE_AMOUNT}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::ops::Not; +use url::Url; + +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)] +pub struct ChainDetails { + pub bech32_account_prefix: String, + pub mix_denom: DenomDetailsOwned, + pub stake_denom: DenomDetailsOwned, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)] +pub struct NymContracts { + pub mixnet_contract_address: Option, + pub vesting_contract_address: Option, + pub ecash_contract_address: Option, + pub group_contract_address: Option, + pub multisig_contract_address: Option, + pub coconut_dkg_contract_address: Option, +} + +// I wanted to use the simpler `NetworkDetails` name, but there's a clash +// with `NetworkDetails` defined in all.rs... +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)] +pub struct NymNetworkDetails { + pub network_name: String, + pub chain_details: ChainDetails, + pub endpoints: Vec, + pub contracts: NymContracts, + pub explorer_api: Option, +} + +// by default we assume the same defaults as mainnet, i.e. same prefixes and denoms +impl Default for NymNetworkDetails { + fn default() -> Self { + NymNetworkDetails::new_mainnet() + } +} + +impl NymNetworkDetails { + pub fn new_empty() -> Self { + NymNetworkDetails { + network_name: Default::default(), + chain_details: ChainDetails { + bech32_account_prefix: Default::default(), + mix_denom: DenomDetailsOwned { + base: Default::default(), + display: Default::default(), + display_exponent: Default::default(), + }, + stake_denom: DenomDetailsOwned { + base: Default::default(), + display: Default::default(), + display_exponent: Default::default(), + }, + }, + endpoints: Default::default(), + contracts: Default::default(), + explorer_api: Default::default(), + } + } + + #[cfg(feature = "env")] + pub fn new_from_env() -> Self { + use crate::var_names; + use std::env::{var, VarError}; + use std::ffi::OsStr; + + fn get_optional_env>(env: K) -> Option { + match var(env) { + Ok(var) => { + if var.is_empty() { + None + } else { + Some(var) + } + } + Err(VarError::NotPresent) => None, + err => panic!("Unable to set: {:?}", err), + } + } + + NymNetworkDetails::new_empty() + .with_network_name(var(var_names::NETWORK_NAME).expect("network name not set")) + .with_bech32_account_prefix( + var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"), + ) + .with_mix_denom(DenomDetailsOwned { + base: var(var_names::MIX_DENOM).expect("mix denomination base not set"), + display: var(var_names::MIX_DENOM_DISPLAY) + .expect("mix denomination display not set"), + display_exponent: var(var_names::DENOMS_EXPONENT) + .expect("denomination exponent not set") + .parse() + .expect("denomination exponent is not u32"), + }) + .with_stake_denom(DenomDetailsOwned { + base: var(var_names::STAKE_DENOM).expect("stake denomination base not set"), + display: var(var_names::STAKE_DENOM_DISPLAY) + .expect("stake denomination display not set"), + display_exponent: var(var_names::DENOMS_EXPONENT) + .expect("denomination exponent not set") + .parse() + .expect("denomination exponent is not u32"), + }) + .with_additional_validator_endpoint(ValidatorDetails::new( + var(var_names::NYXD).expect("nyxd validator not set"), + Some(var(var_names::NYM_API).expect("nym api not set")), + get_optional_env(var_names::NYXD_WEBSOCKET), + )) + .with_mixnet_contract(get_optional_env(var_names::MIXNET_CONTRACT_ADDRESS)) + .with_vesting_contract(get_optional_env(var_names::VESTING_CONTRACT_ADDRESS)) + .with_ecash_contract(get_optional_env(var_names::ECASH_CONTRACT_ADDRESS)) + .with_group_contract(get_optional_env(var_names::GROUP_CONTRACT_ADDRESS)) + .with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS)) + .with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS)) + .with_explorer_api(get_optional_env(var_names::EXPLORER_API)) + } + + pub fn new_mainnet() -> Self { + fn parse_optional_str(raw: &str) -> Option { + raw.is_empty().not().then(|| raw.into()) + } + + // Consider caching this process (lazy static) + NymNetworkDetails { + network_name: mainnet::NETWORK_NAME.into(), + chain_details: ChainDetails { + bech32_account_prefix: mainnet::BECH32_PREFIX.into(), + mix_denom: mainnet::MIX_DENOM.into(), + stake_denom: mainnet::STAKE_DENOM.into(), + }, + endpoints: mainnet::validators(), + contracts: NymContracts { + mixnet_contract_address: parse_optional_str(mainnet::MIXNET_CONTRACT_ADDRESS), + vesting_contract_address: parse_optional_str(mainnet::VESTING_CONTRACT_ADDRESS), + ecash_contract_address: parse_optional_str(mainnet::ECASH_CONTRACT_ADDRESS), + group_contract_address: parse_optional_str(mainnet::GROUP_CONTRACT_ADDRESS), + multisig_contract_address: parse_optional_str(mainnet::MULTISIG_CONTRACT_ADDRESS), + coconut_dkg_contract_address: parse_optional_str( + mainnet::COCONUT_DKG_CONTRACT_ADDRESS, + ), + }, + explorer_api: parse_optional_str(mainnet::EXPLORER_API), + } + } + + pub fn default_gas_price_amount(&self) -> f64 { + GAS_PRICE_AMOUNT + } + + #[must_use] + pub fn with_network_name(mut self, network_name: String) -> Self { + self.network_name = network_name; + self + } + + #[must_use] + pub fn with_chain_details(mut self, chain_details: ChainDetails) -> Self { + self.chain_details = chain_details; + self + } + + #[must_use] + pub fn with_bech32_account_prefix>(mut self, prefix: S) -> Self { + self.chain_details.bech32_account_prefix = prefix.into(); + self + } + + #[must_use] + pub fn with_mix_denom(mut self, mix_denom: DenomDetailsOwned) -> Self { + self.chain_details.mix_denom = mix_denom; + self + } + + #[must_use] + pub fn with_stake_denom(mut self, stake_denom: DenomDetailsOwned) -> Self { + self.chain_details.stake_denom = stake_denom; + self + } + + #[must_use] + pub fn with_base_mix_denom>(mut self, base_mix_denom: S) -> Self { + self.chain_details.mix_denom = DenomDetailsOwned::base_only(base_mix_denom.into()); + self + } + + #[must_use] + pub fn with_base_stake_denom>(mut self, base_stake_denom: S) -> Self { + self.chain_details.stake_denom = DenomDetailsOwned::base_only(base_stake_denom.into()); + self + } + + #[must_use] + pub fn with_additional_validator_endpoint(mut self, endpoint: ValidatorDetails) -> Self { + self.endpoints.push(endpoint); + self + } + + #[must_use] + pub fn with_validator_endpoint(mut self, endpoint: ValidatorDetails) -> Self { + self.endpoints = vec![endpoint]; + self + } + + #[must_use] + pub fn with_contracts(mut self, contracts: NymContracts) -> Self { + self.contracts = contracts; + self + } + + #[must_use] + pub fn with_mixnet_contract>(mut self, contract: Option) -> Self { + self.contracts.mixnet_contract_address = contract.map(Into::into); + self + } + + #[must_use] + pub fn with_vesting_contract>(mut self, contract: Option) -> Self { + self.contracts.vesting_contract_address = contract.map(Into::into); + self + } + + #[must_use] + pub fn with_ecash_contract>(mut self, contract: Option) -> Self { + self.contracts.ecash_contract_address = contract.map(Into::into); + self + } + + #[must_use] + pub fn with_group_contract>(mut self, contract: Option) -> Self { + self.contracts.group_contract_address = contract.map(Into::into); + self + } + + #[must_use] + pub fn with_multisig_contract>(mut self, contract: Option) -> Self { + self.contracts.multisig_contract_address = contract.map(Into::into); + self + } + + #[must_use] + pub fn with_coconut_dkg_contract>(mut self, contract: Option) -> Self { + self.contracts.coconut_dkg_contract_address = contract.map(Into::into); + self + } + + #[must_use] + pub fn with_explorer_api>(mut self, endpoint: Option) -> Self { + self.explorer_api = endpoint.map(Into::into); + self + } +} + +#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct DenomDetails { + pub base: &'static str, + pub display: &'static str, + // i.e. display_amount * 10^display_exponent = base_amount + pub display_exponent: u32, +} + +impl DenomDetails { + pub const fn new(base: &'static str, display: &'static str, display_exponent: u32) -> Self { + DenomDetails { + base, + display, + display_exponent, + } + } +} + +#[derive(Debug, Serialize, Deserialize, Hash, Clone, PartialEq, Eq, JsonSchema)] +pub struct DenomDetailsOwned { + pub base: String, + pub display: String, + // i.e. display_amount * 10^display_exponent = base_amount + pub display_exponent: u32, +} + +impl From for DenomDetailsOwned { + fn from(details: DenomDetails) -> Self { + DenomDetailsOwned { + base: details.base.to_owned(), + display: details.display.to_owned(), + display_exponent: details.display_exponent, + } + } +} + +impl DenomDetailsOwned { + pub fn base_only(base: String) -> Self { + DenomDetailsOwned { + base: base.clone(), + display: base, + display_exponent: 0, + } + } +} + +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)] +pub struct ValidatorDetails { + // it is assumed those values are always valid since they're being provided in our defaults file + pub nyxd_url: String, + // + pub websocket_url: Option, + + // Right now api_url is optional as we are not running the api reliably on all validators + // however, later on it should be a mandatory field + pub api_url: Option, + // TODO: I'd argue this one should also have a field like `gas_price` since its a validator-specific setting +} + +impl ValidatorDetails { + pub fn new>(nyxd_url: S, api_url: Option, websocket_url: Option) -> Self { + ValidatorDetails { + nyxd_url: nyxd_url.into(), + websocket_url: websocket_url.map(Into::into), + api_url: api_url.map(Into::into), + } + } + + pub fn new_nyxd_only>(nyxd_url: S) -> Self { + ValidatorDetails { + nyxd_url: nyxd_url.into(), + websocket_url: None, + api_url: None, + } + } + + pub fn nyxd_url(&self) -> Url { + self.nyxd_url + .parse() + .expect("the provided nyxd url is invalid!") + } + + pub fn api_url(&self) -> Option { + self.api_url + .as_ref() + .map(|url| url.parse().expect("the provided api url is invalid!")) + } + + pub fn websocket_url(&self) -> Option { + self.websocket_url + .as_ref() + .map(|url| url.parse().expect("the provided websocket url is invalid!")) + } +} diff --git a/common/nym_offline_compact_ecash/Cargo.toml b/common/nym_offline_compact_ecash/Cargo.toml index d296effaca..19bf70cf62 100644 --- a/common/nym_offline_compact_ecash/Cargo.toml +++ b/common/nym_offline_compact_ecash/Cargo.toml @@ -27,6 +27,7 @@ ff = { workspace = true } group = { workspace = true } nym-pemstore = { path = "../pemstore" } +nym-network-defaults = { path = "../network-defaults", default-features = false } [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } diff --git a/common/nym_offline_compact_ecash/src/constants.rs b/common/nym_offline_compact_ecash/src/constants.rs index 1f9b467aa6..0bc5a1259a 100644 --- a/common/nym_offline_compact_ecash/src/constants.rs +++ b/common/nym_offline_compact_ecash/src/constants.rs @@ -2,17 +2,19 @@ // SPDX-License-Identifier: Apache-2.0 use bls12_381::Scalar; +use nym_network_defaults::ecash::TICKETBOOK_VALIDITY_DAYS; +use nym_network_defaults::TICKETBOOK_SIZE; pub const PUBLIC_ATTRIBUTES_LEN: usize = 1; //expiration date pub const PRIVATE_ATTRIBUTES_LEN: usize = 2; //user and wallet secret pub const ATTRIBUTES_LEN: usize = PUBLIC_ATTRIBUTES_LEN + PRIVATE_ATTRIBUTES_LEN; // number of attributes encoded in a single zk-nym credential -pub const CRED_VALIDITY_PERIOD_DAYS: u64 = 30; +pub const CRED_VALIDITY_PERIOD_DAYS: u64 = TICKETBOOK_VALIDITY_DAYS; pub(crate) const SECONDS_PER_DAY: u64 = 86400; /// Total number of tickets in each issued ticket book. -pub const NB_TICKETS: u64 = 100; +pub const NB_TICKETS: u64 = TICKETBOOK_SIZE; pub const TYPE_EXP: Scalar = Scalar::from_raw([ u64::from_le_bytes(*b"ZKNYMEXP"), diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index 3f15451576..6ceb76ad5a 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -1,6 +1,7 @@ // Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use nym_network_defaults::TicketbookType; use std::num::ParseIntError; use thiserror::Error; use time::error::ComponentRange; @@ -41,9 +42,9 @@ impl Bandwidth { Bandwidth { value } } - pub fn ticket_amount() -> Self { + pub fn ticket_amount(typ: TicketbookType) -> Self { Bandwidth { - value: nym_network_defaults::TICKET_BANDWIDTH_VALUE, + value: typ.bandwidth_value(), } } 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 cd68f126d8..1f6a85cfa8 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -427,7 +427,7 @@ where // TODO: double storing? // self.store_spent_credential(serial_number_bs58).await?; - let bandwidth = Bandwidth::ticket_amount(); + let bandwidth = Bandwidth::ticket_amount(Default::default()); self.increase_bandwidth(bandwidth, spend_date).await?; diff --git a/gateway/src/node/client_handling/websocket/connection_handler/ecash/credential_sender.rs b/gateway/src/node/client_handling/websocket/connection_handler/ecash/credential_sender.rs index 66eda62269..37bf1e4963 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/ecash/credential_sender.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/ecash/credential_sender.rs @@ -380,8 +380,8 @@ where async fn revoke_ticket_bandwidth(&self, ticket_id: i64) -> Result<(), EcashTicketError> { warn!("revoking bandwidth associated with ticket {ticket_id} since it failed verification"); - let bytes_to_revoke = - Bandwidth::ticket_amount().value() as f32 * self.config.revocation_bandwidth_penalty; + let bytes_to_revoke = Bandwidth::ticket_amount(Default::default()).value() as f32 + * self.config.revocation_bandwidth_penalty; let to_revoke_bi2 = bibytes2(bytes_to_revoke as f64); info!(to_revoke_bi2); diff --git a/nym-api/src/network_monitor/monitor/sender.rs b/nym-api/src/network_monitor/monitor/sender.rs index d2d99e7462..531bc7be8f 100644 --- a/nym-api/src/network_monitor/monitor/sender.rs +++ b/nym-api/src/network_monitor/monitor/sender.rs @@ -13,9 +13,9 @@ use futures::task::Context; use futures::{Future, Stream}; use log::{debug, info, trace, warn}; use nym_bandwidth_controller::BandwidthController; -use nym_config::defaults::REMAINING_BANDWIDTH_THRESHOLD; use nym_credential_storage::persistent_storage::PersistentStorage; use nym_crypto::asymmetric::identity::{self, PUBLIC_KEY_LENGTH}; +use nym_gateway_client::client::config::GatewayClientConfig; use nym_gateway_client::client::GatewayConfig; use nym_gateway_client::error::GatewayClientError; use nym_gateway_client::{ @@ -205,15 +205,16 @@ impl PacketSender { ); let gateway_client = GatewayClient::new( + GatewayClientConfig::new_default() + .with_disabled_credentials_mode(fresh_gateway_client_data.disabled_credentials_mode) + .with_response_timeout(fresh_gateway_client_data.gateway_response_timeout), config, Arc::clone(&fresh_gateway_client_data.local_identity), None, gateway_packet_router, Some(fresh_gateway_client_data.bandwidth_controller.clone()), task_client, - ) - .with_disabled_credentials_mode(fresh_gateway_client_data.disabled_credentials_mode) - .with_response_timeout(fresh_gateway_client_data.gateway_response_timeout); + ); ( GatewayClientHandle::new(gateway_client), @@ -325,9 +326,9 @@ impl PacketSender { async fn check_remaining_bandwidth( client: &mut GatewayClient, ) -> Result<(), GatewayClientError> { - if client.remaining_bandwidth() < REMAINING_BANDWIDTH_THRESHOLD { + if client.remaining_bandwidth() < client.cfg.bandwidth.remaining_bandwidth_threshold { Err(GatewayClientError::NotEnoughBandwidth( - REMAINING_BANDWIDTH_THRESHOLD, + client.cfg.bandwidth.remaining_bandwidth_threshold, client.remaining_bandwidth(), )) } else { diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index c9d451dcef..1815aaafe0 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -3159,6 +3159,7 @@ dependencies = [ "ff", "group", "itertools 0.12.1", + "nym-network-defaults", "nym-pemstore", "rand 0.8.5", "serde", @@ -3318,14 +3319,10 @@ dependencies = [ name = "nym-network-defaults" version = "0.1.0" dependencies = [ - "cfg-if", "dotenvy", - "hex-literal", "log", - "once_cell", "schemars", "serde", - "thiserror", "url", ]