Compare commits

..

1 Commits

Author SHA1 Message Date
serinko 1ef33ce797 initialise branch - leaving for later focus now 2025-01-29 12:08:24 +01:00
131 changed files with 1748 additions and 2968 deletions
-80
View File
@@ -4,86 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.2-hu] (2025-02-04)
- Feature/remove double spending bloomfilter ([#5417])
- HU - Downgrade harmless log message from info to debug ([#5405])
- lower default ticket verification quorum to 0.7 ([#5404])
- Downgrade harmless log message from info to debug ([#5403])
- Redirect from mixnode page to nodes page ([#5397])
- chore :update version of chain watcher and validator rewarder ([#5394])
- bugfix: correctly handle ingore epoch roles flag ([#5390])
- bugfix: terminate mixnet socket listener on shutdown ([#5389])
- feat: make client ignore dual mode nodes by default ([#5388])
- Handle ecash network errors differently ([#5378])
- Remove empty ephemeral keys ([#5376])
- fixed sql migration for adding default message timestamp ([#5374])
- Bind to [::] on nym-node for both IP versions ([#5361])
- exposed NymApiClient method for obtaining node performance history ([#5360])
- Client gateway selection ([#5358])
- chore: refresh wasm sdk ([#5353])
- chore: update indexed_db_futures ([#5347])
- build(deps): bump mikefarah/yq from 4.44.6 to 4.45.1 ([#5342])
- updated cosmrs and tendermint-rpc to their most recent versions ([#5339])
- build(deps): bump ts-rs from 10.0.0 to 10.1.0 ([#5338])
- build(deps): bump tempfile from 3.14.0 to 3.15.0 ([#5337])
- build(deps): bump the patch-updates group with 8 updates ([#5336])
- feature: introduce /load endpoint for self-reported quantised NymNode load ([#5326])
- feature: `CancellationToken`-based shutdowns ([#5325])
- Use expect in geodata test to give error message on failure ([#5314])
- feature: periodically remove stale gateway messages ([#5312])
- build(deps): bump the patch-updates group across 1 directory with 35 updates ([#5310])
- Add dependabot assignes for the root cargo ecosystem ([#5297])
- Move tun constants to network defaults ([#5286])
- Include IPINFO_API_TOKEN in nightly CI ([#5285])
- Nyx Chain Watcher ([#5274])
- bugfix: remove unnecessary arguments for nym-api swagger endpoints ([#5272])
- feature: nym topology revamp ([#5271])
- Add windows to CI builds ([#5269])
- http-api-client: deduplicate code ([#5267])
- build(deps): bump http from 1.1.0 to 1.2.0 ([#5228])
- NS API: add mixnet scraper ([#5200])
- build(deps): bump criterion from 0.4.0 to 0.5.1 ([#4911])
[#5417]: https://github.com/nymtech/nym/pull/5417
[#5405]: https://github.com/nymtech/nym/pull/5405
[#5404]: https://github.com/nymtech/nym/pull/5404
[#5403]: https://github.com/nymtech/nym/pull/5403
[#5397]: https://github.com/nymtech/nym/pull/5397
[#5394]: https://github.com/nymtech/nym/pull/5394
[#5390]: https://github.com/nymtech/nym/pull/5390
[#5389]: https://github.com/nymtech/nym/pull/5389
[#5388]: https://github.com/nymtech/nym/pull/5388
[#5378]: https://github.com/nymtech/nym/pull/5378
[#5376]: https://github.com/nymtech/nym/pull/5376
[#5374]: https://github.com/nymtech/nym/pull/5374
[#5361]: https://github.com/nymtech/nym/pull/5361
[#5360]: https://github.com/nymtech/nym/pull/5360
[#5358]: https://github.com/nymtech/nym/pull/5358
[#5353]: https://github.com/nymtech/nym/pull/5353
[#5347]: https://github.com/nymtech/nym/pull/5347
[#5342]: https://github.com/nymtech/nym/pull/5342
[#5339]: https://github.com/nymtech/nym/pull/5339
[#5338]: https://github.com/nymtech/nym/pull/5338
[#5337]: https://github.com/nymtech/nym/pull/5337
[#5336]: https://github.com/nymtech/nym/pull/5336
[#5326]: https://github.com/nymtech/nym/pull/5326
[#5325]: https://github.com/nymtech/nym/pull/5325
[#5314]: https://github.com/nymtech/nym/pull/5314
[#5312]: https://github.com/nymtech/nym/pull/5312
[#5310]: https://github.com/nymtech/nym/pull/5310
[#5297]: https://github.com/nymtech/nym/pull/5297
[#5286]: https://github.com/nymtech/nym/pull/5286
[#5285]: https://github.com/nymtech/nym/pull/5285
[#5274]: https://github.com/nymtech/nym/pull/5274
[#5272]: https://github.com/nymtech/nym/pull/5272
[#5271]: https://github.com/nymtech/nym/pull/5271
[#5269]: https://github.com/nymtech/nym/pull/5269
[#5267]: https://github.com/nymtech/nym/pull/5267
[#5228]: https://github.com/nymtech/nym/pull/5228
[#5200]: https://github.com/nymtech/nym/pull/5200
[#4911]: https://github.com/nymtech/nym/pull/4911
## [2025.1-reeses] (2025-01-15)
- Feture/legacy alert ([#5346])
Generated
+333 -403
View File
File diff suppressed because it is too large Load Diff
+16 -15
View File
@@ -48,6 +48,7 @@ members = [
"common/credentials-interface",
"common/crypto",
"common/dkg",
"common/ecash-double-spending",
"common/ecash-time",
"common/execute",
"common/exit-policy",
@@ -194,7 +195,7 @@ aead = "0.5.2"
anyhow = "1.0.95"
arc-swap = "1.7.1"
argon2 = "0.5.0"
async-trait = "0.1.86"
async-trait = "0.1.85"
axum-client-ip = "0.6.1"
axum = "0.7.5"
axum-extra = "0.9.4"
@@ -215,11 +216,11 @@ chacha20 = "0.9.0"
chacha20poly1305 = "0.10.1"
chrono = "0.4.39"
cipher = "0.4.3"
clap = "4.5.28"
clap = "4.5.26"
clap_complete = "4.5"
clap_complete_fig = "4.5"
colored = "2.0"
comfy-table = "7.1.4"
comfy-table = "7.1.3"
console = "0.15.10"
console-subscriber = "0.1.1"
console_error_panic_hook = "0.1"
@@ -248,12 +249,12 @@ futures = "0.3.31"
futures-util = "0.3"
generic-array = "0.14.7"
getrandom = "0.2.10"
getset = "0.1.4"
getset = "0.1.3"
handlebars = "3.5.5"
headers = "0.4.0"
hex = "0.4.3"
hex-literal = "0.3.3"
hickory-resolver = "0.24.3"
hickory-resolver = "0.24.2"
hkdf = "0.12.3"
hmac = "0.12.1"
http = "1"
@@ -264,7 +265,7 @@ humantime-serde = "1.1.1"
human-repr = "1.1.0"
hyper = "1.4.1"
hyper-util = "0.1"
indicatif = "0.17.11"
indicatif = "0.17.9"
inquire = "0.6.2"
ip_network = "0.4.1"
ipnetwork = "0.20"
@@ -282,7 +283,7 @@ moka = { version = "0.12", features = ["future"] }
nix = "0.27.1"
notify = "5.1.0"
okapi = "0.7.0"
once_cell = "1.20.3"
once_cell = "1.20.2"
opentelemetry = "0.19.0"
opentelemetry-jaeger = "0.18.0"
parking_lot = "0.12.3"
@@ -307,12 +308,12 @@ rocket_cors = "0.6.0"
rocket_okapi = "0.8.0"
safer-ffi = "0.1.13"
schemars = "0.8.21"
semver = "1.0.25"
semver = "1.0.24"
serde = "1.0.217"
serde_bytes = "0.11.15"
serde_derive = "1.0"
serde_json = "1.0.138"
serde_json_path = "0.7.2"
serde_json = "1.0.135"
serde_json_path = "0.7.1"
serde_repr = "0.1"
serde_with = "3.9.0"
serde_yaml = "0.9.25"
@@ -328,15 +329,15 @@ sysinfo = "0.33.0"
tap = "1.0.1"
tar = "0.4.43"
tempfile = "3.15"
thiserror = "2.0"
thiserror = "1.0.64"
time = "0.3.37"
tokio = "1.43"
tokio = "1.39"
tokio-stream = "0.1.17"
tokio-test = "0.4.4"
tokio-tun = "0.11.5"
tokio-tungstenite = { version = "0.20.1" }
tokio-util = "0.7.13"
toml = "0.8.20"
toml = "0.8.19"
tower = "0.4.13"
tower-http = "0.5.2"
tracing = "0.1.41"
@@ -384,10 +385,10 @@ cw4 = { version = "=1.1.2" }
cw-controllers = { version = "=1.1.0" }
# cosmrs-related
bip32 = { version = "0.5.3", default-features = false }
bip32 = { version = "0.5.2", default-features = false }
cosmrs = { version = "0.21.1" }
cosmrs = { version = "0.21.0" }
tendermint = "0.40.0"
tendermint-rpc = "0.40.0"
prost = { version = "0.13", default-features = false }
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.47"
version = "1.1.46"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+2 -6
View File
@@ -56,7 +56,7 @@ pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
.join(DEFAULT_DATA_DIR)
}
#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct Config {
#[serde(flatten)]
pub base: BaseClientConfig,
@@ -94,10 +94,6 @@ impl CliClientConfig for Config {
}
impl Config {
pub fn base(&self) -> BaseClientConfig {
self.base.clone()
}
pub fn new<S: AsRef<str>>(id: S) -> Self {
Config {
base: BaseClientConfig::new(id.as_ref(), env!("CARGO_PKG_VERSION")),
@@ -213,7 +209,7 @@ impl SocketType {
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Socket {
pub socket_type: SocketType,
@@ -107,8 +107,5 @@ enabled = {{ debug.stats_reporting.enabled }}
provider_address = '{{ debug.stats_reporting.provider_address }}'
reporting_interval = '{{ debug.stats_reporting.reporting_interval }}'
[debug.forget_me]
client = {{ debug.forget_me.client }}
stats = {{ debug.forget_me.stats }}
"#;
+3 -8
View File
@@ -20,7 +20,7 @@ pub use nym_sphinx::addressing::clients::Recipient;
pub mod config;
type NativeClientBuilder = BaseClientBuilder<QueryHttpRpcNyxdClient, OnDiskPersistent>;
type NativeClientBuilder<'a> = BaseClientBuilder<'a, QueryHttpRpcNyxdClient, OnDiskPersistent>;
pub struct SocketClient {
/// Client configuration options, including, among other things, packet sending rates,
@@ -32,10 +32,6 @@ pub struct SocketClient {
}
impl SocketClient {
pub fn config(&self) -> Config {
self.config.clone()
}
pub fn new(config: Config, custom_mixnet: Option<PathBuf>) -> Self {
SocketClient {
config,
@@ -112,9 +108,8 @@ impl SocketClient {
let storage = self.initialise_storage().await?;
let user_agent = nym_bin_common::bin_info!().into();
let mut base_client =
BaseClientBuilder::new(self.config().base(), storage, dkg_query_client)
.with_user_agent(user_agent);
let mut base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client)
.with_user_agent(user_agent);
if let Some(custom_mixnet) = &self.custom_mixnet {
base_client = base_client.with_stored_topology(custom_mixnet)?;
-1
View File
@@ -82,7 +82,6 @@ impl From<Init> for OverrideConfig {
nyxd_urls: init_config.common_args.nyxd_urls,
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
stats_reporting_address: init_config.common_args.stats_reporting_address,
forget_me: init_config.common_args.forget_me.into(),
}
}
}
-3
View File
@@ -16,7 +16,6 @@ use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_client::client::Recipient;
use nym_client_core::cli_helpers::CliClient;
use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
use nym_client_core::config::ForgetMe;
use nym_config::OptionalSet;
use std::error::Error;
use std::net::IpAddr;
@@ -107,7 +106,6 @@ pub(crate) struct OverrideConfig {
nyxd_urls: Option<Vec<url::Url>>,
enabled_credentials_mode: Option<bool>,
stats_reporting_address: Option<Recipient>,
forget_me: ForgetMe,
}
pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
@@ -135,7 +133,6 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
args.fastmode,
)
.with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover)
.with_base(BaseClientConfig::with_forget_me, args.forget_me)
.with_optional(Config::with_port, args.port)
.with_optional(Config::with_host, args.host)
.with_optional_custom_env_ext(
-1
View File
@@ -41,7 +41,6 @@ impl From<Run> for OverrideConfig {
nyxd_urls: run_config.common_args.nyxd_urls,
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
stats_reporting_address: run_config.common_args.stats_reporting_address,
forget_me: run_config.common_args.forget_me.into(),
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.47"
version = "1.1.46"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
-1
View File
@@ -93,7 +93,6 @@ impl From<Init> for OverrideConfig {
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
outfox: false,
stats_reporting_address: init_config.common_args.stats_reporting_address,
forget_me: init_config.common_args.forget_me.into(),
}
}
}
+1 -3
View File
@@ -17,7 +17,7 @@ use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_client_core::cli_helpers::CliClient;
use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
use nym_client_core::config::{ForgetMe, GroupBy, TopologyStructure};
use nym_client_core::config::{GroupBy, TopologyStructure};
use nym_config::OptionalSet;
use nym_sphinx::addressing::Recipient;
use nym_sphinx::params::{PacketSize, PacketType};
@@ -113,7 +113,6 @@ pub(crate) struct OverrideConfig {
enabled_credentials_mode: Option<bool>,
outfox: bool,
stats_reporting_address: Option<Recipient>,
forget_me: ForgetMe,
}
pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
@@ -180,7 +179,6 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
BaseClientConfig::with_topology_structure,
topology_structure,
)
.with_base(BaseClientConfig::with_forget_me, args.forget_me)
.with_optional(Config::with_anonymous_replies, args.use_anonymous_replies)
.with_optional(Config::with_port, args.port)
.with_optional(Config::with_ip, args.ip)
-1
View File
@@ -65,7 +65,6 @@ impl From<Run> for OverrideConfig {
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
outfox: run_config.outfox,
stats_reporting_address: run_config.common_args.stats_reporting_address,
forget_me: run_config.common_args.forget_me.into(),
}
}
}
-4
View File
@@ -113,8 +113,4 @@ enabled = {{ core.debug.stats_reporting.enabled }}
provider_address = '{{ core.debug.stats_reporting.provider_address }}'
reporting_interval = '{{ core.debug.stats_reporting.reporting_interval }}'
[core.debug.forget_me]
client = {{ core.debug.forget_me.client }}
stats = {{ core.debug.forget_me.stats }}
"#;
-1
View File
@@ -40,7 +40,6 @@ nym-crypto = { path = "../crypto" }
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
nym-gateway-client = { path = "../client-libs/gateway-client" }
nym-gateway-requests = { path = "../gateway-requests" }
nym-http-api-client = { path = "../http-api-client" }
nym-metrics = { path = "../nym-metrics" }
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
nym-sphinx = { path = "../nymsphinx" }
+2 -79
View File
@@ -145,11 +145,6 @@ impl Config {
self
}
pub fn with_forget_me(mut self, forget_me: ForgetMe) -> Self {
self.debug.forget_me = forget_me;
self
}
// TODO: this should be refactored properly
// as of 12.09.23 the below is true (not sure how this comment will rot in the future)
// medium_toggle:
@@ -522,7 +517,7 @@ impl Default for Acknowledgements {
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default)]
#[serde(default, deny_unknown_fields)]
pub struct Topology {
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
@@ -563,10 +558,6 @@ pub struct Topology {
/// Specifies whether this client should ignore the current epoch role of the target egress node
/// when constructing the final hop packets.
pub ignore_egress_epoch_role: bool,
/// Specifies whether this client should ignore the current epoch role of the ingress node
/// when attempting to establish new connection
pub ignore_ingress_epoch_role: bool,
}
#[allow(clippy::large_enum_variant)]
@@ -604,9 +595,7 @@ impl Default for Topology {
minimum_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
minimum_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
use_extended_topology: false,
ignore_egress_epoch_role: true,
ignore_ingress_epoch_role: true,
ignore_egress_epoch_role: false,
}
}
}
@@ -724,9 +713,6 @@ pub struct DebugConfig {
/// Defines all configuration options related to stats reporting.
pub stats_reporting: StatsReporting,
/// Defines all configuration options related to the forget me flag.
pub forget_me: ForgetMe,
}
impl DebugConfig {
@@ -749,69 +735,6 @@ impl Default for DebugConfig {
topology: Default::default(),
reply_surbs: Default::default(),
stats_reporting: Default::default(),
forget_me: Default::default(),
}
}
}
#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize, Copy)]
pub struct ForgetMe {
client: bool,
stats: bool,
}
impl From<bool> for ForgetMe {
fn from(value: bool) -> Self {
if value {
Self::new_all()
} else {
Self::new_none()
}
}
}
impl ForgetMe {
pub fn new_all() -> Self {
Self {
client: true,
stats: true,
}
}
pub fn new_client() -> Self {
Self {
client: true,
stats: false,
}
}
pub fn new_stats() -> Self {
Self {
client: false,
stats: true,
}
}
pub fn new(client: bool, stats: bool) -> Self {
Self { client, stats }
}
pub fn any(&self) -> bool {
self.client || self.stats
}
pub fn client(&self) -> bool {
self.client
}
pub fn stats(&self) -> bool {
self.stats
}
pub fn new_none() -> Self {
Self {
client: false,
stats: false,
}
}
}
@@ -182,7 +182,7 @@ impl From<ConfigV5> for Config {
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
},
..Default::default()
stats_reporting: Default::default(),
},
}
}
@@ -120,7 +120,6 @@ where
&core.client.nym_api_urls,
user_agent,
core.debug.topology.minimum_gateway_performance,
core.debug.topology.ignore_ingress_epoch_role,
)
.await?
};
@@ -93,10 +93,6 @@ pub struct CommonClientInitArgs {
/// Sets the address to report statistics
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub stats_reporting_address: Option<Recipient>,
/// Sets the forget me flag
#[cfg_attr(feature = "cli", clap(long, hide = true, default_value_t = false))]
pub forget_me: bool,
}
pub struct InitResultsWithConfig<T> {
@@ -179,7 +175,6 @@ where
&core.client.nym_api_urls,
user_agent,
core.debug.topology.minimum_gateway_performance,
core.debug.topology.ignore_ingress_epoch_role,
)
.await?
};
@@ -61,8 +61,4 @@ pub struct CommonClientRunArgs {
/// Sets the address to report statistics
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub stats_reporting_address: Option<Recipient>,
/// Sets the forget me flag
#[cfg_attr(feature = "cli", clap(long, hide = true, default_value_t = false))]
pub forget_me: bool,
}
@@ -1,7 +1,6 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::mix_traffic::ClientRequestSender;
use super::received_buffer::ReceivedBufferMessage;
use super::statistics_control::StatisticsControl;
use crate::client::base_client::storage::helpers::store_client_keys;
@@ -32,11 +31,10 @@ use crate::init::{
setup_gateway,
types::{GatewaySetup, InitialisationResult},
};
use crate::{config, spawn_future};
use crate::{config, spawn_future, ForgetMe};
use futures::channel::mpsc;
use log::*;
use nym_bandwidth_controller::BandwidthController;
use nym_client_core_config_types::ForgetMe;
use nym_client_core_gateways_storage::{GatewayDetails, GatewaysDetailsStore};
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::{encryption, identity};
@@ -177,8 +175,8 @@ impl From<bool> for CredentialsToggle {
}
}
pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
config: Config,
pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
config: &'a Config,
client_store: S,
dkg_query_client: Option<C>,
@@ -192,18 +190,20 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
#[cfg(unix)]
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
forget_me: ForgetMe,
}
impl<C, S> BaseClientBuilder<C, S>
impl<'a, C, S> BaseClientBuilder<'a, C, S>
where
S: MixnetClientStorage + 'static,
C: DkgQueryClient + Send + Sync + 'static,
{
pub fn new(
base_config: Config,
base_config: &'a Config,
client_store: S,
dkg_query_client: Option<C>,
) -> BaseClientBuilder<C, S> {
) -> BaseClientBuilder<'a, C, S> {
BaseClientBuilder {
config: base_config,
client_store,
@@ -216,12 +216,13 @@ where
setup_method: GatewaySetup::MustLoad { gateway_id: None },
#[cfg(unix)]
connection_fd_callback: None,
forget_me: Default::default(),
}
}
#[must_use]
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
self.config.debug.forget_me = *forget_me;
self.forget_me = forget_me.clone();
self
}
@@ -644,12 +645,13 @@ where
fn start_mix_traffic_controller(
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
shutdown: TaskClient,
) -> (BatchMixMessageSender, ClientRequestSender) {
forget_me: ForgetMe,
) -> BatchMixMessageSender {
info!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx, client_tx) =
MixTrafficController::new(gateway_transceiver);
let (mix_traffic_controller, mix_tx) =
MixTrafficController::new(gateway_transceiver, forget_me);
mix_traffic_controller.start_with_shutdown(shutdown);
(mix_tx, client_tx)
mix_tx
}
// TODO: rename it as it implies the data is persistent whilst one can use InMemBackend
@@ -771,7 +773,7 @@ where
);
let stats_reporter = Self::start_statistics_control(
&self.config,
self.config,
self.user_agent.clone(),
generate_client_stats_id(*self_address.identity()),
input_sender.clone(),
@@ -797,7 +799,7 @@ where
let gateway_transceiver = Self::setup_gateway_transceiver(
self.custom_gateway_transceiver,
&self.config,
self.config,
init_res,
bandwidth_controller,
&details_store,
@@ -831,9 +833,10 @@ where
// traffic stream.
// The MixTrafficController then sends the actual traffic
let (message_sender, client_request_sender) = Self::start_mix_traffic_controller(
let message_sender = Self::start_mix_traffic_controller(
gateway_transceiver,
shutdown.fork("mix_traffic_controller"),
self.forget_me,
);
// Channels that the websocket listener can use to signal downstream to the real traffic
@@ -908,8 +911,6 @@ where
},
stats_reporter,
task_handle: shutdown,
client_request_sender,
forget_me: self.config.debug.forget_me,
})
}
}
@@ -921,7 +922,6 @@ pub struct BaseClient {
pub client_output: ClientOutputStatus,
pub client_state: ClientState,
pub stats_reporter: ClientStatsSender,
pub client_request_sender: ClientRequestSender,
pub task_handle: TaskHandle,
pub forget_me: ForgetMe,
}
@@ -4,8 +4,6 @@
// TODO: combine those more closely. Perhaps into a single underlying store.
// Like for persistent, on-disk, storage, what's the point of having 3 different databases?
use rand::rngs::OsRng;
use crate::client::key_manager::persistence::{InMemEphemeralKeys, KeyStore};
use crate::client::replies::reply_storage;
use crate::client::replies::reply_storage::ReplyStorageBackend;
@@ -65,7 +63,7 @@ pub trait MixnetClientStorage {
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore;
}
#[derive(Clone)]
#[derive(Clone, Default)]
pub struct Ephemeral {
key_store: InMemEphemeralKeys,
reply_store: reply_storage::Empty,
@@ -73,14 +71,9 @@ pub struct Ephemeral {
gateway_details_store: InMemGatewaysDetails,
}
impl Default for Ephemeral {
fn default() -> Self {
Ephemeral {
key_store: InMemEphemeralKeys::new(&mut OsRng),
reply_store: Default::default(),
credential_store: Default::default(),
gateway_details_store: Default::default(),
}
impl Ephemeral {
pub fn new() -> Self {
Default::default()
}
}
@@ -3,7 +3,6 @@
use crate::client::key_manager::ClientKeys;
use async_trait::async_trait;
use rand::{CryptoRng, RngCore};
use std::error::Error;
use std::sync::Arc;
use tokio::sync::Mutex;
@@ -196,20 +195,9 @@ impl KeyStore for OnDiskKeys {
}
}
#[derive(Clone)]
#[derive(Clone, Default)]
pub struct InMemEphemeralKeys {
keys: Arc<Mutex<ClientKeys>>,
}
impl InMemEphemeralKeys {
pub fn new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
InMemEphemeralKeys {
keys: Arc::new(Mutex::new(ClientKeys::generate_new(rng))),
}
}
keys: Arc<Mutex<Option<ClientKeys>>>,
}
#[derive(Debug, thiserror::Error)]
@@ -222,11 +210,11 @@ impl KeyStore for InMemEphemeralKeys {
type StorageError = EphemeralKeysError;
async fn load_keys(&self) -> Result<ClientKeys, Self::StorageError> {
Ok(self.keys.lock().await.clone())
self.keys.lock().await.clone().ok_or(EphemeralKeysError)
}
async fn store_keys(&self, keys: &ClientKeys) -> Result<(), Self::StorageError> {
*self.keys.lock().await = keys.clone();
*self.keys.lock().await = Some(keys.clone());
Ok(())
}
}
@@ -2,17 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
use crate::error::ClientCoreError;
use crate::spawn_future;
use crate::{spawn_future, ForgetMe};
use log::*;
use nym_gateway_requests::ClientRequest;
use nym_sphinx::forwarding::packet::MixPacket;
use transceiver::ErasedGatewayError;
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver<Vec<MixPacket>>;
pub type ClientRequestReceiver = tokio::sync::mpsc::Receiver<ClientRequest>;
pub type ClientRequestSender = tokio::sync::mpsc::Sender<ClientRequest>;
pub mod transceiver;
@@ -27,67 +23,52 @@ pub struct MixTrafficController {
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
mix_rx: BatchMixMessageReceiver,
client_rx: ClientRequestReceiver,
// TODO: this is temporary work-around.
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
consecutive_gateway_failure_count: usize,
forget_me: ForgetMe,
}
impl MixTrafficController {
pub fn new<T>(
gateway_transceiver: T,
) -> (
MixTrafficController,
BatchMixMessageSender,
ClientRequestSender,
)
forget_me: ForgetMe,
) -> (MixTrafficController, BatchMixMessageSender)
where
T: GatewayTransceiver + Send + 'static,
{
let (message_sender, message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1);
(
MixTrafficController {
gateway_transceiver: Box::new(gateway_transceiver),
mix_rx: message_receiver,
client_rx: client_receiver,
consecutive_gateway_failure_count: 0,
forget_me,
},
message_sender,
client_sender,
)
}
pub fn new_dynamic(
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
) -> (
MixTrafficController,
BatchMixMessageSender,
ClientRequestSender,
) {
forget_me: ForgetMe,
) -> (MixTrafficController, BatchMixMessageSender) {
let (message_sender, message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1);
(
MixTrafficController {
gateway_transceiver,
mix_rx: message_receiver,
client_rx: client_receiver,
consecutive_gateway_failure_count: 0,
forget_me,
},
message_sender,
client_sender,
)
}
async fn on_messages(
&mut self,
mut mix_packets: Vec<MixPacket>,
) -> Result<(), ErasedGatewayError> {
async fn on_messages(&mut self, mut mix_packets: Vec<MixPacket>) {
debug_assert!(!mix_packets.is_empty());
let result = if mix_packets.len() == 1 {
@@ -99,14 +80,21 @@ impl MixTrafficController {
.await
};
if result.is_err() {
self.consecutive_gateway_failure_count += 1;
} else {
trace!("We *might* have managed to forward sphinx packet(s) to the gateway!");
self.consecutive_gateway_failure_count = 0;
match result {
Err(err) => {
error!("Failed to send sphinx packet(s) to the gateway: {err}");
self.consecutive_gateway_failure_count += 1;
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
// todo: in the future this should initiate a 'graceful' shutdown or try
// to reconnect?
panic!("failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead. Can't do anything about it yet :(")
}
}
Ok(_) => {
trace!("We *might* have managed to forward sphinx packet(s) to the gateway!");
self.consecutive_gateway_failure_count = 0;
}
}
result
}
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
@@ -117,35 +105,13 @@ impl MixTrafficController {
tokio::select! {
mix_packets = self.mix_rx.recv() => match mix_packets {
Some(mix_packets) => {
if let Err(err) = self.on_messages(mix_packets).await {
error!("Failed to send sphinx packet(s) to the gateway: {err}");
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
// Disconnect from the gateway. If we should try to re-connect
// is handled at a higher layer.
error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead");
// Do we need to handle the embedded mixnet client case
// separately?
shutdown.send_we_stopped(Box::new(ClientCoreError::GatewayFailedToForwardMessages));
break;
}
}
self.on_messages(mix_packets).await;
},
None => {
log::trace!("MixTrafficController: Stopping since channel closed");
break;
}
},
client_request = self.client_rx.recv() => match client_request {
Some(client_request) => {
match self.gateway_transceiver.send_client_request(client_request).await {
Ok(_) => (),
Err(e) => error!("Failed to send client request: {}", e),
};
},
None => {
log::trace!("MixTrafficController, client request channel closed");
}
},
_ = shutdown.recv_with_delay() => {
log::trace!("MixTrafficController: Received shutdown");
break;
@@ -154,6 +120,25 @@ impl MixTrafficController {
}
shutdown.recv_timeout().await;
if self.forget_me.any() {
log::info!("Sending forget me request to the gateway");
match self
.gateway_transceiver
.send_client_request(ClientRequest::ForgetMe {
client: self.forget_me.client(),
stats: self.forget_me.stats(),
})
.await
{
Ok(_) => {
log::info!("Successfully sent forget me request to the gateway");
}
Err(err) => {
log::error!("Failed to send forget me request to the gateway: {err}");
}
}
}
log::debug!("MixTrafficController: Exiting");
});
}
@@ -86,9 +86,7 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
&mut self,
message: ClientRequest,
) -> Result<(), GatewayClientError> {
let _ = (**self).send_client_request(message.clone()).await?;
log::debug!("Sent client request: {:?}", message);
Ok(())
(**self).send_client_request(message).await
}
}
@@ -145,7 +143,14 @@ where
&mut self,
message: ClientRequest,
) -> Result<(), GatewayClientError> {
self.gateway_client.send_client_request(message).await
if let Some(shared_key) = self.gateway_client.shared_key() {
self.gateway_client
.send_websocket_message(message.encrypt(&*shared_key)?)
.await?;
Ok(())
} else {
Err(GatewayClientError::ConnectionInInvalidState)
}
}
}
@@ -626,14 +626,9 @@ where
messages: Vec<RealMessage>,
transmission_lane: TransmissionLane,
) {
if let Err(err) = self
.real_message_sender
self.real_message_sender
.send((messages, transmission_lane))
.await
{
error!(
"Failed to forward messages to the real message sender (OutQueueControl): {err}"
);
}
.expect("real message receiver task (OutQueueControl) has died");
}
}
@@ -545,7 +545,7 @@ where
loop {
tokio::select! {
biased;
_ = shutdown.recv() => {
_ = shutdown.recv_with_delay() => {
log::trace!("OutQueueControl: Received shutdown");
break;
}
@@ -123,11 +123,6 @@ impl StatisticsControl {
loop {
tokio::select! {
biased;
_ = task_client.recv() => {
log::trace!("StatisticsControl: Received shutdown");
break;
},
stats_event = self.stats_rx.recv() => match stats_event {
Some(stats_event) => self.stats.handle_event(stats_event),
None => {
@@ -151,8 +146,13 @@ impl StatisticsControl {
_ = local_report_interval.next() => {
self.stats.local_report(&mut task_client);
}
_ = task_client.recv_with_delay() => {
log::trace!("StatisticsControl: Received shutdown");
break;
},
}
}
task_client.recv_timeout().await;
log::debug!("StatisticsControl: Exiting");
}
-10
View File
@@ -36,13 +36,6 @@ pub enum ClientCoreError {
#[error("no gateway with id: {0}")]
NoGatewayWithId(String),
#[error("Invalid URL: {0}")]
InvalidUrl(String),
#[cfg(not(target_arch = "wasm32"))]
#[error("resolution failed: {0}")]
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
#[error("no gateways on network")]
NoGatewaysOnNetwork,
@@ -103,9 +96,6 @@ pub enum ClientCoreError {
#[error("timed out while trying to establish gateway connection")]
GatewayConnectionTimeout,
#[error("failed to forward mix messages to gateway")]
GatewayFailedToForwardMessages,
#[error("no ping measurements for the gateway ({identity}) performed")]
NoGatewayMeasurements { identity: String },
+4 -6
View File
@@ -15,9 +15,6 @@ use std::{sync::Arc, time::Duration};
use tungstenite::Message;
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use crate::init::websockets::connect_async;
use nym_topology::NodeId;
#[cfg(not(target_arch = "wasm32"))]
use tokio::net::TcpStream;
@@ -26,6 +23,8 @@ use tokio::time::sleep;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::Instant;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::connect_async;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
#[cfg(target_arch = "wasm32")]
use wasm_utils::websocket::JSWebsocket;
@@ -92,7 +91,6 @@ pub async fn gateways_for_init<R: Rng>(
nym_apis: &[Url],
user_agent: Option<UserAgent>,
minimum_performance: u8,
ignore_epoch_roles: bool,
) -> Result<Vec<RoutingNode>, ClientCoreError> {
let nym_api = nym_apis
.choose(rng)
@@ -114,7 +112,7 @@ pub async fn gateways_for_init<R: Rng>(
// (we don't want instability)
let valid_gateways = gateways
.iter()
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
.filter(|g| !g.supported_roles.mixnode)
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
.filter_map(|gateway| gateway.try_into().ok())
.collect::<Vec<_>>();
@@ -133,7 +131,7 @@ pub async fn gateways_for_init<R: Rng>(
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
Err(_elapsed) => Err(ClientCoreError::GatewayConnectionTimeout),
Ok(Err(conn_failure)) => Err(conn_failure),
Ok(Err(conn_failure)) => Err(conn_failure.into()),
Ok(Ok((stream, _))) => Ok(stream),
}
}
-2
View File
@@ -26,8 +26,6 @@ use serde::Serialize;
pub mod helpers;
pub mod types;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) mod websockets;
// helpers for error wrapping
-44
View File
@@ -1,44 +0,0 @@
use crate::error::ClientCoreError;
use nym_http_api_client::HickoryDnsResolver;
use tokio::net::TcpStream;
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
use tungstenite::handshake::client::Response;
use url::{Host, Url};
use std::net::SocketAddr;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn connect_async(
endpoint: &str,
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), ClientCoreError> {
let resolver = HickoryDnsResolver::default();
let uri = Url::parse(endpoint).map_err(|_| ClientCoreError::InvalidUrl(endpoint.to_owned()))?;
let port: u16 = uri.port_or_known_default().unwrap_or(443);
let host = uri
.host()
.ok_or(ClientCoreError::InvalidUrl(endpoint.to_owned()))?;
// Get address for tcp connection, if a domain is provided use our preferred resolver rather than
// the default std resolve
let sock_addrs: Vec<SocketAddr> = match host {
Host::Ipv4(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Ipv6(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Domain(domain) => {
// Do a DNS lookup for the domain using our custom DNS resolver
resolver
.resolve_str(domain)
.await?
.into_iter()
.map(|a| SocketAddr::new(a, port))
.collect()
}
};
let stream = TcpStream::connect(&sock_addrs[..]).await?;
tokio_tungstenite::client_async_tls(endpoint, stream)
.await
.map_err(Into::into)
}
+45
View File
@@ -33,3 +33,48 @@ where
{
tokio::spawn(future);
}
#[derive(Clone, Default, Debug)]
pub struct ForgetMe {
client: bool,
stats: bool,
}
impl ForgetMe {
pub fn new_all() -> Self {
Self {
client: true,
stats: true,
}
}
pub fn new_client() -> Self {
Self {
client: true,
stats: false,
}
}
pub fn new_stats() -> Self {
Self {
client: false,
stats: true,
}
}
pub fn new(client: bool, stats: bool) -> Self {
Self { client, stats }
}
pub fn any(&self) -> bool {
self.client || self.stats
}
pub fn client(&self) -> bool {
self.client
}
pub fn stats(&self) -> bool {
self.stats
}
}
@@ -27,7 +27,6 @@ nym-credential-storage = { path = "../../credential-storage" }
nym-credentials-interface = { path = "../../credentials-interface" }
nym-crypto = { path = "../../crypto" }
nym-gateway-requests = { path = "../../gateway-requests" }
nym-http-api-client = { path = "../../http-api-client" }
nym-network-defaults = { path = "../../network-defaults" }
nym-sphinx = { path = "../../nymsphinx" }
nym-statistics-common = { path = "../../statistics" }
@@ -40,6 +40,8 @@ use url::Url;
use std::os::fd::RawFd;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::connect_async;
#[cfg(not(unix))]
use std::os::raw::c_int as RawFd;
@@ -51,11 +53,6 @@ use zeroize::Zeroizing;
pub mod config;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) mod websockets;
#[cfg(not(target_arch = "wasm32"))]
use websockets::connect_async;
pub struct GatewayConfig {
pub gateway_identity: identity::PublicKey,
@@ -204,7 +201,15 @@ impl<C, St> GatewayClient<C, St> {
"Attemting to establish connection to gateway at: {}",
self.gateway_address
);
let (ws_stream, _) = connect_async(&self.gateway_address).await?;
let ws_stream = match connect_async(&self.gateway_address).await {
Ok((ws_stream, _)) => ws_stream,
Err(error) => {
return Err(GatewayClientError::NetworkConnectionFailed {
address: self.gateway_address.clone(),
source: error,
})
}
};
self.connection = SocketState::Available(Box::new(ws_stream));
@@ -266,19 +271,6 @@ impl<C, St> GatewayClient<C, St> {
}
}
pub async fn send_client_request(
&mut self,
message: ClientRequest,
) -> Result<(), GatewayClientError> {
if let Some(shared_key) = self.shared_key() {
let encrypted = message.encrypt(&*shared_key)?;
Box::pin(self.send_websocket_message(encrypted)).await?;
Ok(())
} else {
Err(GatewayClientError::ConnectionInInvalidState)
}
}
async fn read_control_response(&mut self) -> Result<ServerResponse, GatewayClientError> {
// we use the fact that all request responses are Message::Text and only pushed
// sphinx packets are Message::Binary
@@ -1,53 +0,0 @@
use crate::error::GatewayClientError;
use nym_http_api_client::HickoryDnsResolver;
use tokio::net::TcpStream;
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
use tungstenite::handshake::client::Response;
use url::{Host, Url};
use std::net::SocketAddr;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn connect_async(
endpoint: &str,
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
let resolver = HickoryDnsResolver::default();
let uri =
Url::parse(endpoint).map_err(|_| GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
let port: u16 = uri.port_or_known_default().unwrap_or(443);
let host = uri
.host()
.ok_or(GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
// Get address for tcp connection, if a domain is provided use our preferred resolver rather than
// the default std resolve
let sock_addrs: Vec<SocketAddr> = match host {
Host::Ipv4(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Ipv6(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Domain(domain) => {
// Do a DNS lookup for the domain using our custom DNS resolver
resolver
.resolve_str(domain)
.await?
.into_iter()
.map(|a| SocketAddr::new(a, port))
.collect()
}
};
let stream = TcpStream::connect(&sock_addrs[..]).await.map_err(|error| {
GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
source: error.into(),
}
})?;
tokio_tungstenite::client_async_tls(endpoint, stream)
.await
.map_err(|error| GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
source: error,
})
}
@@ -44,11 +44,7 @@ pub enum GatewayClientError {
NetworkConnectionFailed { address: String, source: WsError },
#[error("Invalid URL: {0}")]
InvalidUrl(String),
#[cfg(not(target_arch = "wasm32"))]
#[error("resolution failed: {0}")]
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
InvalidURL(String),
#[error("No shared key was provided or obtained")]
NoSharedKeyAvailable,
@@ -11,7 +11,8 @@ use crate::{
use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, VerifyEcashTicketBody,
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, SpentCredentialsResponse,
VerifyEcashTicketBody,
};
use nym_api_requests::ecash::{
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
@@ -646,6 +647,13 @@ impl NymApiClient {
.await?)
}
#[deprecated]
pub async fn spent_credentials_filter(
&self,
) -> Result<SpentCredentialsResponse, ValidatorClientError> {
Ok(self.nym_api.double_spending_filter_v1().await?)
}
pub async fn partial_expiration_date_signatures(
&self,
expiration_date: Option<Date>,
@@ -65,12 +65,6 @@ pub enum EcashApiError {
#[from]
source: cosmrs::ErrorReport,
},
#[error("nym api error")]
NymApi {
#[from]
source: crate::ValidatorClientError,
},
}
impl TryFrom<ContractVKShare> for EcashApiClient {
@@ -31,7 +31,6 @@ pub use nym_api_requests::{
StakeSaturationResponse, UptimeResponse,
},
nym_nodes::{CachedNodesResponse, SkimmedNode},
NymNetworkDetailsResponse,
};
pub use nym_coconut_dkg_common::types::EpochId;
use nym_contracts_common::IdentityKey;
@@ -850,6 +849,20 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn double_spending_filter_v1(&self) -> Result<SpentCredentialsResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::ECASH_ROUTES,
routes::DOUBLE_SPENDING_FILTER_V1,
],
NO_PARAMS,
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn partial_expiration_date_signatures(
&self,
@@ -1014,15 +1027,6 @@ pub trait NymApiClientExt: ApiClient {
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn get_network_details(&self) -> Result<NymNetworkDetailsResponse, NymAPIError> {
self.get_json(
&[routes::API_VERSION, routes::NETWORK, routes::DETAILS],
NO_PARAMS,
)
.await
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -13,6 +13,8 @@ pub const DETAILED: &str = "detailed";
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
pub const ACTIVE: &str = "active";
pub const REWARDED: &str = "rewarded";
pub const DOUBLE_SPENDING_FILTER_V1: &str = "double-spending-filter-v1";
pub const ECASH_ROUTES: &str = "ecash";
pub use ecash::*;
@@ -65,8 +67,6 @@ pub const STAKE_SATURATION: &str = "stake-saturation";
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
pub const SUBMIT_GATEWAY: &str = "submit-gateway-monitoring-results";
pub const SUBMIT_NODE: &str = "submit-node-monitoring-results";
pub const PERFORMANCE: &str = "performance";
pub const SERVICE_PROVIDERS: &str = "services";
pub const DETAILS: &str = "details";
pub const NETWORK: &str = "network";
@@ -8,81 +8,81 @@ use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum VestingContractError {
#[error("VESTING ({l}): {0}", l = line!())]
#[error("VESTING ({}): {0}", line!())]
Std(#[from] StdError),
#[error("VESTING: {0}")]
OverflowError(#[from] OverflowError),
#[error("VESTING ({l}): Account does not exist - {0}", l = line!())]
#[error("VESTING ({}): Account does not exist - {0}", line!())]
NoAccountForAddress(String),
#[error("VESTING ({l}): Only admin can perform this action, {0} is not admin", l = line!())]
#[error("VESTING ({}): Only admin can perform this action, {0} is not admin", line!())]
NotAdmin(String),
#[error("VESTING ({l}): Balance not found for existing account ({0}), this is a bug", l = line!())]
#[error("VESTING ({}): Balance not found for existing account ({0}), this is a bug", line!())]
NoBalanceForAddress(String),
#[error("VESTING ({l}): Insufficient balance for address {0} -> {1}", l = line!())]
#[error("VESTING ({}): Insufficient balance for address {0} -> {1}", line!())]
InsufficientBalance(String, u128),
#[error("VESTING ({l}): Insufficient spendable balance for address {0} -> {1}", l = line!())]
#[error("VESTING ({}): Insufficient spendable balance for address {0} -> {1}", line!())]
InsufficientSpendable(String, u128),
#[error(
"VESTING ({l}):Only delegation owner can perform delegation actions, {0} is not the delegation owner"
, l = line!())]
"VESTING ({}):Only delegation owner can perform delegation actions, {0} is not the delegation owner"
, line!())]
NotDelegate(String),
#[error("VESTING ({l}): Total vesting amount is inprobably low -> {0}, this is likely an error", l = line!())]
#[error("VESTING ({}): Total vesting amount is inprobably low -> {0}, this is likely an error", line!())]
ImprobableVestingAmount(u128),
#[error("VESTING ({l}): Address {0} has already bonded a node", l = line!())]
#[error("VESTING ({}): Address {0} has already bonded a node", line!())]
AlreadyBonded(String),
#[error("VESTING ({l}): Received empty funds vector", l = line!())]
#[error("VESTING ({}): Received empty funds vector", line!())]
EmptyFunds,
#[error("VESTING ({l}): Received wrong denom: {0}, expected {1}", l = line!())]
#[error("VESTING ({}): Received wrong denom: {0}, expected {1}", line!())]
WrongDenom(String, String),
#[error("VESTING ({l}): Received multiple denoms, expected 1", l = line!())]
#[error("VESTING ({}): Received multiple denoms, expected 1", line!())]
MultipleDenoms,
#[error("VESTING ({l}): No delegations found for account {0}, mix_identity {1}", l = line!())]
#[error("VESTING ({}): No delegations found for account {0}, mix_identity {1}", line!())]
NoSuchDelegation(Addr, NodeId),
#[error("VESTING ({l}): Only mixnet contract can perform this operation, got {0}", l = line!())]
#[error("VESTING ({}): Only mixnet contract can perform this operation, got {0}", line!())]
NotMixnetContract(Addr),
#[error("VESTING ({l}): Calculation underflowed", l = line!())]
#[error("VESTING ({}): Calculation underflowed", line!())]
Underflow,
#[error("VESTING ({l}): No bond found for account {0}", l = line!())]
#[error("VESTING ({}): No bond found for account {0}", line!())]
NoBondFound(String),
#[error("VESTING: Attempted to reduce mixnode bond pledge below zero! The current pledge is {current} and we attempted to reduce it by {decrease_by}.")]
InvalidBondPledgeReduction { current: Coin, decrease_by: Coin },
#[error("VESTING ({l}): Action can only be executed by account owner -> {0}", l = line!())]
#[error("VESTING ({}): Action can only be executed by account owner -> {0}", line!())]
NotOwner(String),
#[error("VESTING ({l}): Invalid address: {0}", l = line!())]
#[error("VESTING ({}): Invalid address: {0}", line!())]
InvalidAddress(String),
#[error("VESTING ({l}): Account already exists: {0}", l = line!())]
#[error("VESTING ({}): Account already exists: {0}", line!())]
AccountAlreadyExists(String),
#[error("VESTING ({l}): Staking account already exists: {0}", l = line!())]
#[error("VESTING ({}): Staking account already exists: {0}", line!())]
StakingAccountAlreadyExists(String),
#[error("VESTING ({l}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", l = line!())]
#[error("VESTING ({}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", line!())]
MinVestingFunds { sent: u128, need: u128 },
#[error("VESTING ({l}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", l = line!())]
#[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())]
LockedPledgeCapReached { current: Uint128, cap: Uint128 },
#[error("VESTING: ({l}: Account owned by {owner} has unpopulated vesting periods!", l = line!())]
#[error("VESTING: ({}: Account owned by {owner} has unpopulated vesting periods!", line!())]
UnpopulatedVestingPeriods { owner: Addr },
#[error("VESTING: Vesting account associated with {0} already exists, only addresses with not existing vesting accounts can be added as staking addresses")]
@@ -26,6 +26,7 @@ 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" }
@@ -13,7 +13,6 @@ 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_validator_client::coconut::EcashApiError;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::contract_traits::{
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
@@ -353,9 +352,7 @@ impl CredentialHandler {
}
Err(err) => {
error!("failed to send ticket {ticket_id} for verification to ecash signer '{client}': {err}. if we don't reach quorum, we'll retry later");
Err(EcashTicketError::ApiFailure(EcashApiError::NymApi {
source: err,
}))
Ok(false)
}
}
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "nym-ecash-double-spending"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
bit-vec = { workspace = true }
bloomfilter = { workspace = true }
nym-network-defaults = { path = "../network-defaults" }
+136
View File
@@ -0,0 +1,136 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bit_vec::BitVec;
use bloomfilter::Bloom;
use nym_network_defaults::{BloomfilterParameters, ECASH_DS_BLOOMFILTER_PARAMS};
pub struct DoubleSpendingFilter {
params: BloomfilterParameters,
inner: Bloom<Vec<u8>>,
}
impl Default for DoubleSpendingFilter {
fn default() -> Self {
DoubleSpendingFilter::new_empty_ecash()
}
}
pub fn bloom_from_params<T>(params: &BloomfilterParameters, bitvec: BitVec) -> Bloom<Vec<T>> {
assert_eq!(params.bitmap_size, bitvec.len() as u64);
Bloom::from_bit_vec(
bitvec,
params.bitmap_size,
params.num_hashes,
params.sip_keys,
)
}
impl DoubleSpendingFilter {
pub fn new_empty(params: BloomfilterParameters) -> Self {
let bitvec = BitVec::from_elem(params.bitmap_size as usize, false);
DoubleSpendingFilter {
inner: bloom_from_params(&params, bitvec),
params,
}
}
pub fn params(&self) -> BloomfilterParameters {
self.params
}
pub fn rebuild(&self) -> DoubleSpendingFilterBuilder {
DoubleSpendingFilterBuilder::new(self.params)
}
pub fn reset(&mut self) {
self.inner.clear()
}
pub fn new_empty_ecash() -> Self {
DoubleSpendingFilter::new_empty(ECASH_DS_BLOOMFILTER_PARAMS)
}
pub fn builder(params: BloomfilterParameters) -> DoubleSpendingFilterBuilder {
DoubleSpendingFilterBuilder::new(params)
}
pub fn from_bytes(params: BloomfilterParameters, bitmap: &[u8]) -> Self {
DoubleSpendingFilter {
inner: bloom_from_params(&params, BitVec::from_bytes(bitmap)),
params,
}
}
pub fn replace_bitvec(&mut self, new: BitVec) {
self.inner = bloom_from_params(&self.params, new)
}
pub fn dump_bitmap(&self) -> Vec<u8> {
self.inner.bitmap()
}
pub fn set(&mut self, b: &Vec<u8>) {
self.inner.set(b);
}
pub fn check(&self, b: &Vec<u8>) -> bool {
self.inner.check(b)
}
}
pub struct DoubleSpendingFilterBuilder {
params: BloomfilterParameters,
bit_vec_builder: Option<BitVecBuilder>,
}
impl DoubleSpendingFilterBuilder {
pub fn new(params: BloomfilterParameters) -> Self {
DoubleSpendingFilterBuilder {
params,
bit_vec_builder: None,
}
}
pub fn add_bytes(&mut self, b: &[u8]) -> bool {
match &mut self.bit_vec_builder {
None => {
self.bit_vec_builder = Some(BitVecBuilder::new(b));
true
}
Some(builder) => builder.add_bytes(b),
}
}
pub fn build(self) -> DoubleSpendingFilter {
match self.bit_vec_builder {
None => DoubleSpendingFilter::new_empty(self.params),
Some(builder) => DoubleSpendingFilter {
inner: bloom_from_params(&self.params, builder.finish()),
params: self.params,
},
}
}
}
pub struct BitVecBuilder(BitVec);
impl BitVecBuilder {
pub fn new(initial_bitmap: &[u8]) -> Self {
BitVecBuilder(BitVec::from_bytes(initial_bitmap))
}
pub fn add_bytes(&mut self, b: &[u8]) -> bool {
let add = BitVec::from_bytes(b);
if self.0.len() != add.len() {
return false;
}
self.0.or(&add);
true
}
pub fn finish(self) -> BitVec {
self.0
}
}
+6 -6
View File
@@ -59,12 +59,12 @@ pub enum GatewayRequestsError {
source: NymNodeRoutingAddressError,
},
#[error("received request had invalid size. (actual: {0}, but expected one of: {a} (ACK), {r} (REGULAR), {e8}, {e16}, {e32} (EXTENDED))",
a = PacketSize::AckPacket.size(),
r = PacketSize::RegularPacket.size(),
e8 = PacketSize::ExtendedPacket8.size(),
e16 = PacketSize::ExtendedPacket16.size(),
e32 = PacketSize::ExtendedPacket32.size())
#[error("received request had invalid size. (actual: {0}, but expected one of: {} (ACK), {} (REGULAR), {}, {}, {} (EXTENDED))",
PacketSize::AckPacket.size(),
PacketSize::RegularPacket.size(),
PacketSize::ExtendedPacket8.size(),
PacketSize::ExtendedPacket16.size(),
PacketSize::ExtendedPacket32.size())
]
RequestOfInvalidSize(usize),
@@ -13,7 +13,7 @@ use std::str::FromStr;
use tungstenite::Message;
// wrapper for all encrypted requests for ease of use
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Serialize, Deserialize, Debug)]
#[non_exhaustive]
pub enum ClientRequest {
UpgradeKey {
@@ -3,22 +3,5 @@
* SPDX-License-Identifier: GPL-3.0-only
*/
ALTER TABLE message_store
RENAME TO message_store_old;
-- add new column with message timestamp.
-- note: we can't simply alter existing table to add it since the default value is non-constant
CREATE TABLE message_store
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
client_address_bs58 TEXT NOT NULL,
content BLOB NOT NULL,
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO message_store(id, client_address_bs58, content)
SELECT id, client_address_bs58, content
FROM message_store_old;
DROP TABLE message_store_old;
ADD COLUMN timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP;
-1
View File
@@ -40,7 +40,6 @@ struct SocketAddrs {
#[derive(Debug, thiserror::Error)]
#[error("hickory-dns resolver error: {hickory_error}")]
/// Error occurring while resolving a hostname into an IP address.
pub struct HickoryDnsError {
#[from]
hickory_error: ResolveError,
+1 -1
View File
@@ -22,7 +22,7 @@ pub use user_agent::UserAgent;
#[cfg(not(target_arch = "wasm32"))]
mod dns;
#[cfg(not(target_arch = "wasm32"))]
pub use dns::{HickoryDnsError, HickoryDnsResolver};
pub use dns::HickoryDnsResolver;
// The timeout is relatively high as we are often making requests over the mixnet, where latency is
// high and chatty protocols take a while to complete.
@@ -30,10 +30,6 @@ pub struct Config {
}
impl Config {
pub fn base(&self) -> BaseClientConfig {
self.base.clone()
}
pub fn new<S: Into<String>>(id: S, version: S, provider_mix_address: S) -> Self {
Config {
base: BaseClientConfig::new(id, version),
+3 -7
View File
@@ -76,10 +76,6 @@ where
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Sync + Send,
<S::KeyStore as KeyStore>::StorageError: Send + Sync,
{
pub fn config(&self) -> Config {
self.config.clone()
}
pub fn new(
config: Config,
storage: S,
@@ -237,7 +233,7 @@ where
};
let mut base_builder =
BaseClientBuilder::new(self.config.base(), self.storage, dkg_query_client)
BaseClientBuilder::new(&self.config.base, self.storage, dkg_query_client)
.with_gateway_setup(self.setup_method)
.with_user_agent(self.user_agent);
@@ -245,7 +241,7 @@ where
base_builder = base_builder.with_stored_topology(custom_mixnet)?;
}
let packet_type = self.config.base().debug.traffic.packet_type;
let packet_type = self.config.base.debug.traffic.packet_type;
let mut started_client = base_builder.start_base().await?;
let self_address = started_client.address;
let client_input = started_client.client_input.register_producer();
@@ -256,7 +252,7 @@ where
Self::start_socks5_listener(
&self.config.socks5,
self.config.base().debug,
self.config.base.debug,
client_input,
client_output,
client_state,
+2 -2
View File
@@ -221,7 +221,7 @@ impl TaskManager {
}
}
pub async fn wait_for_graceful_shutdown(&mut self) {
pub(crate) async fn wait_for_graceful_shutdown(&mut self) {
if let Some(notify_rx) = self.notify_rx.take() {
drop(notify_rx);
}
@@ -315,7 +315,7 @@ impl TaskClient {
const MAX_NAME_LENGTH: usize = 128;
const OVERFLOW_NAME: &'static str = "reached maximum TaskClient children name depth";
const SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(10);
const SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(5);
fn new(
notify: watch::Receiver<()>,
+2 -2
View File
@@ -11,7 +11,7 @@ use std::borrow::Borrow;
use std::collections::{HashMap, HashSet};
use std::fmt::Display;
use std::net::IpAddr;
use tracing::{debug, trace, warn};
use tracing::{debug, warn};
pub use crate::node::{EntryDetails, RoutingNode, SupportedRoles};
pub use error::NymTopologyError;
@@ -293,7 +293,7 @@ impl NymTopology {
let has_exit_gateways = !self.rewarded_set.exit_gateways.is_empty();
let has_entry_gateways = !self.rewarded_set.entry_gateways.is_empty();
trace!(
debug!(
has_layer1 = %has_layer1,
has_layer2 = %has_layer2,
has_layer3 = %has_layer3,
-34
View File
@@ -5,7 +5,6 @@
#![allow(clippy::drop_non_drop)]
use crate::error::WasmCoreError;
use nym_client_core::config::ForgetMe;
use nym_config::helpers::OptionalSet;
use nym_sphinx::params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
@@ -111,8 +110,6 @@ pub struct DebugWasm {
/// Defines all configuration options related to stats reporting.
#[wasm_bindgen(getter_with_clone)]
pub stats_reporting: StatsReportingWasm,
pub forget_me: ForgetMeWasm,
}
impl Default for DebugWasm {
@@ -131,7 +128,6 @@ impl From<DebugWasm> for ConfigDebug {
topology: debug.topology.into(),
reply_surbs: debug.reply_surbs.into(),
stats_reporting: debug.stats_reporting.into(),
forget_me: debug.forget_me.into(),
}
}
}
@@ -146,7 +142,6 @@ impl From<ConfigDebug> for DebugWasm {
topology: debug.topology.into(),
reply_surbs: debug.reply_surbs.into(),
stats_reporting: debug.stats_reporting.into(),
forget_me: ForgetMeWasm::from(debug.forget_me),
}
}
}
@@ -400,10 +395,6 @@ pub struct TopologyWasm {
/// Specifies whether this client should ignore the current epoch role of the target egress node
/// when constructing the final hop packets.
pub ignore_egress_epoch_role: bool,
/// Specifies whether this client should ignore the current epoch role of the ingress node
/// when attempting to establish new connection
pub ignore_ingress_epoch_role: bool,
}
impl Default for TopologyWasm {
@@ -428,7 +419,6 @@ impl From<TopologyWasm> for ConfigTopology {
minimum_gateway_performance: topology.minimum_gateway_performance,
use_extended_topology: topology.use_extended_topology,
ignore_egress_epoch_role: topology.ignore_egress_epoch_role,
ignore_ingress_epoch_role: topology.ignore_ingress_epoch_role,
}
}
}
@@ -446,7 +436,6 @@ impl From<ConfigTopology> for TopologyWasm {
minimum_gateway_performance: topology.minimum_gateway_performance,
use_extended_topology: topology.use_extended_topology,
ignore_egress_epoch_role: topology.ignore_egress_epoch_role,
ignore_ingress_epoch_role: topology.ignore_ingress_epoch_role,
}
}
}
@@ -546,29 +535,6 @@ impl From<ConfigReplySurbs> for ReplySurbsWasm {
}
}
#[wasm_bindgen(inspectable)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, Copy, Default)]
#[serde(deny_unknown_fields)]
pub struct ForgetMeWasm {
pub client: bool,
pub stats: bool,
}
impl From<ForgetMeWasm> for ForgetMe {
fn from(value: ForgetMeWasm) -> Self {
ForgetMe::new(value.client, value.stats)
}
}
impl From<ForgetMe> for ForgetMeWasm {
fn from(value: ForgetMe) -> Self {
Self {
client: value.client(),
stats: value.stats(),
}
}
}
#[wasm_bindgen(inspectable)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
+2 -34
View File
@@ -8,8 +8,8 @@
#![allow(clippy::empty_docs)]
use super::{
AcknowledgementsWasm, CoverTrafficWasm, DebugWasm, ForgetMeWasm, GatewayConnectionWasm,
ReplySurbsWasm, StatsReportingWasm, TopologyWasm, TrafficWasm,
AcknowledgementsWasm, CoverTrafficWasm, DebugWasm, GatewayConnectionWasm, ReplySurbsWasm,
StatsReportingWasm, TopologyWasm, TrafficWasm,
};
use crate::config::ConfigDebug;
use serde::{Deserialize, Serialize};
@@ -47,9 +47,6 @@ pub struct DebugWasmOverride {
/// Defines all configuration options related to stats reporting.
#[tsify(optional)]
pub stats_reporting: Option<StatsReportingWasmOverride>,
#[tsify(optional)]
pub forget_me: Option<ForgetMeWasmOverride>,
}
impl From<DebugWasmOverride> for DebugWasm {
@@ -62,7 +59,6 @@ impl From<DebugWasmOverride> for DebugWasm {
topology: value.topology.map(Into::into).unwrap_or_default(),
reply_surbs: value.reply_surbs.map(Into::into).unwrap_or_default(),
stats_reporting: value.stats_reporting.map(Into::into).unwrap_or_default(),
forget_me: value.forget_me.map(Into::into).unwrap_or_default(),
}
}
}
@@ -285,11 +281,6 @@ pub struct TopologyWasmOverride {
/// when constructing the final hop packets.
#[tsify(optional)]
pub ignore_egress_epoch_role: Option<bool>,
/// Specifies whether this client should ignore the current epoch role of the ingress node
/// when attempting to establish new connection
#[tsify(optional)]
pub ignore_ingress_epoch_role: Option<bool>,
}
impl From<TopologyWasmOverride> for TopologyWasm {
@@ -319,9 +310,6 @@ impl From<TopologyWasmOverride> for TopologyWasm {
ignore_egress_epoch_role: value
.ignore_egress_epoch_role
.unwrap_or(def.ignore_egress_epoch_role),
ignore_ingress_epoch_role: value
.ignore_ingress_epoch_role
.unwrap_or(def.ignore_ingress_epoch_role),
}
}
}
@@ -412,26 +400,6 @@ impl From<ReplySurbsWasmOverride> for ReplySurbsWasm {
}
}
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct ForgetMeWasmOverride {
#[tsify(optional)]
pub client: Option<bool>,
#[tsify(optional)]
pub stats: Option<bool>,
}
impl From<ForgetMeWasmOverride> for ForgetMeWasm {
fn from(value: ForgetMeWasmOverride) -> Self {
ForgetMeWasm {
client: value.client.unwrap_or_default(),
stats: value.stats.unwrap_or_default(),
}
}
}
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
+4 -27
View File
@@ -132,17 +132,9 @@ pub async fn setup_gateway_from_api(
chosen_gateway: Option<IdentityKey>,
nym_apis: &[Url],
minimum_performance: u8,
ignore_epoch_roles: bool,
) -> Result<InitialisationResult, WasmCoreError> {
let mut rng = thread_rng();
let gateways = gateways_for_init(
&mut rng,
nym_apis,
None,
minimum_performance,
ignore_epoch_roles,
)
.await?;
let gateways = gateways_for_init(&mut rng, nym_apis, None, minimum_performance).await?;
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
}
@@ -150,17 +142,9 @@ pub async fn current_gateways_wasm(
nym_apis: &[Url],
user_agent: Option<UserAgent>,
minimum_performance: u8,
ignore_epoch_roles: bool,
) -> Result<Vec<RoutingNode>, ClientCoreError> {
let mut rng = thread_rng();
gateways_for_init(
&mut rng,
nym_apis,
user_agent,
minimum_performance,
ignore_epoch_roles,
)
.await
gateways_for_init(&mut rng, nym_apis, user_agent, minimum_performance).await
}
pub async fn setup_from_topology(
@@ -179,7 +163,6 @@ pub async fn generate_new_client_keys(store: &ClientStorage) -> Result<(), WasmC
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub async fn add_gateway(
preferred_gateway: Option<IdentityKey>,
latency_based_selection: Option<bool>,
@@ -187,7 +170,6 @@ pub async fn add_gateway(
nym_apis: &[Url],
user_agent: UserAgent,
min_performance: u8,
ignore_epoch_roles: bool,
storage: &ClientStorage,
) -> Result<(), WasmCoreError> {
let selection_spec = GatewaySelectionSpecification::new(
@@ -221,13 +203,8 @@ pub async fn add_gateway(
// Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration.
let available_gateways = current_gateways_wasm(
nym_apis,
Some(user_agent),
min_performance,
ignore_epoch_roles,
)
.await?;
let available_gateways =
current_gateways_wasm(nym_apis, Some(user_agent), min_performance).await?;
// since we're registering with a brand new gateway,
// make sure the list of available gateways doesn't overlap the list of known gateways
+39 -59
View File
@@ -179,7 +179,7 @@ dependencies = [
"semver",
"serde",
"serde_json",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -233,7 +233,7 @@ dependencies = [
"schemars",
"serde",
"subtle-encoding",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -267,7 +267,7 @@ dependencies = [
"ed25519-zebra",
"k256 0.13.3",
"rand_core 0.6.4",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -289,7 +289,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -320,7 +320,7 @@ dependencies = [
"serde",
"serde-json-wasm",
"sha2 0.10.8",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -433,7 +433,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.85",
]
[[package]]
@@ -448,7 +448,7 @@ dependencies = [
"cw-utils",
"schemars",
"serde",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -467,7 +467,7 @@ dependencies = [
"prost",
"schemars",
"serde",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -493,7 +493,7 @@ dependencies = [
"schemars",
"semver",
"serde",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -508,7 +508,7 @@ dependencies = [
"schemars",
"semver",
"serde",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -538,7 +538,7 @@ dependencies = [
"schemars",
"semver",
"serde",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -553,7 +553,7 @@ dependencies = [
"cw20",
"schemars",
"serde",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -570,7 +570,7 @@ dependencies = [
"cw3",
"schemars",
"serde",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -622,7 +622,7 @@ dependencies = [
"nym-group-contract-common",
"schemars",
"serde",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -1140,7 +1140,7 @@ dependencies = [
"nym-coconut-bandwidth-contract-common",
"nym-multisig-contract-common",
"serde",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -1170,7 +1170,7 @@ dependencies = [
"nym-contracts-common",
"nym-group-contract-common",
"serde",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -1196,7 +1196,7 @@ dependencies = [
"cw-storage-plus",
"schemars",
"serde",
"thiserror 2.0.11",
"thiserror",
"vergen",
]
@@ -1210,7 +1210,7 @@ dependencies = [
"nym-sphinx-types",
"rand",
"subtle-encoding",
"thiserror 2.0.11",
"thiserror",
"x25519-dalek",
"zeroize",
]
@@ -1240,7 +1240,7 @@ dependencies = [
"semver",
"serde",
"sylvia",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -1254,7 +1254,7 @@ dependencies = [
"cw-utils",
"cw2",
"nym-multisig-contract-common",
"thiserror 2.0.11",
"thiserror",
]
[[package]]
@@ -1289,7 +1289,7 @@ dependencies = [
"rand_chacha",
"semver",
"serde",
"thiserror 1.0.64",
"thiserror",
"time",
]
@@ -1310,7 +1310,7 @@ dependencies = [
"serde",
"serde-json-wasm",
"serde_repr",
"thiserror 2.0.11",
"thiserror",
"time",
]
@@ -1326,7 +1326,7 @@ dependencies = [
"cw4",
"schemars",
"serde",
"thiserror 2.0.11",
"thiserror",
]
[[package]]
@@ -1349,7 +1349,7 @@ name = "nym-sphinx-types"
version = "0.2.0"
dependencies = [
"sphinx-packet",
"thiserror 2.0.11",
"thiserror",
]
[[package]]
@@ -1370,7 +1370,7 @@ dependencies = [
"rand_chacha",
"serde",
"serde_json",
"thiserror 1.0.64",
"thiserror",
]
[[package]]
@@ -1383,7 +1383,7 @@ dependencies = [
"nym-contracts-common",
"nym-mixnet-contract-common",
"serde",
"thiserror 2.0.11",
"thiserror",
]
[[package]]
@@ -1489,9 +1489,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.93"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
@@ -1666,7 +1666,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.98",
"syn 2.0.85",
]
[[package]]
@@ -1699,9 +1699,9 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.25"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
dependencies = [
"serde",
]
@@ -1741,7 +1741,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.85",
]
[[package]]
@@ -1752,7 +1752,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.85",
]
[[package]]
@@ -1775,7 +1775,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.85",
]
[[package]]
@@ -1934,9 +1934,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.98"
version = "2.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
dependencies = [
"proc-macro2",
"quote",
@@ -1949,16 +1949,7 @@ version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
dependencies = [
"thiserror-impl 1.0.64",
]
[[package]]
name = "thiserror"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
dependencies = [
"thiserror-impl 2.0.11",
"thiserror-impl",
]
[[package]]
@@ -1969,18 +1960,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "thiserror-impl"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.85",
]
[[package]]
@@ -2146,5 +2126,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
"syn 2.0.85",
]
@@ -163,4 +163,3 @@ Another command is `version_count` where at least one `nym-node` version is requ
```sh
./node_api_check version_count 1.1.0 1.1.1 1.1.2 1.1.3 --markdown
```
@@ -11,38 +11,22 @@ Tor community created a very helpful table called [*Good Bad ISPs*](https://comm
<div>
<Tabs items={[
<><code>nym-node</code> - dedicated server</>,
<><code>nym-node</code> - VPS</>,
<code>nym-node</code>,
<code>validator</code>,
]} defaultIndex="0">
<MyTab>
#### `nym-node` - dedicated server
If you are about to start a new Nym Node, look for a server with this specification:
#### `nym-node`
Before we conclude the testing with exact results, these are the rough specs:
| **Hardware** | **Minimum Specification** |
| :--- | ---: |
| Type of server | Dedicated or bare metal |
| CPU Cores | 4 |
| Memory | 8-16 GB RAM |
| Storage | 50 GB (preferably 80 GB) |
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
| Bandwidth | Unmetered if possible, or \> 40Tb |
| Port speed | 1Gbps |
| **Hardware** | **Minimum Specification** |
| :--- | ---: |
| CPU Cores | 4 |
| Memory | 8 GB RAM |
| Storage | 80 GB |
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
| Bandwidth | > 1Tb |
| Port speed | 1Gbps |
</MyTab>
<MyTab>
#### `nym-node` - VPS
If you are about to start a new Nym Node, look for a server with this specification:
| **Hardware** | **Minimum Specification** |
| :--- | ---: |
| Type of server | VPS |
| CPU Cores | 4 |
| Memory | 8-16 GB RAM |
| Storage | 50 GB (preferably 80 GB) |
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
| Bandwidth | Unmetered if possible, or \> 40Tb |
| Port speed | 10Gbps |
</MyTab>
<MyTab>
#### Nyx validator
@@ -1,25 +0,0 @@
```
Dear <ISP>:
Thank you for forwarding me the notice you received from <COPYRIGHT_CLAIMANT> regarding <CONTENT>. I would like to assure you that I am not hosting the claimed infringing materials, and I believe that the notice is likely based upon misunderstandings about the law and about some of the software I run. I believe that the Digital Millennium Copyright Act's ("DMCA") safe harbor provisions likely protect you from liability arising from this complaint.
As you know, the DMCA creates four "safe harbors" for service providers (such as ISPs) to protect them from copyright liability for the acts of their users, when the service provider fulfill certain requirements. (See 17 U.S.C. 512). The requirements to meet the DMCA safe harbor provisions vary depending on the type of safe harbor claimed.
You may be familiar with the "notice and takedown" requirements of section 512(c) of the DMCA, which require a service provider respond to expeditiously to remove, or disable access to, the material that is claimed to be infringing or to be the subject of infringing activity. However, we believe that the more appropriate safe harbor provision is under section 512(a), which applies when the service provider merely acts as a conduit. In such case, there are different and less burdensome eligibility requirements, as the D.C. Circuit Court of Appeals held in RIAA v. Verizon (see https://scholar.google.com/scholar_case?case=15815830240179540527) and the Eighth Circuit Court of Appeals confirmed in RIAA v. Charter (see https://scholar.google.com/scholar_case?case=11547531128234336420).
Under DMCA 512(a), service providers like you are typically protected from damages for copyright infringement claims if you also maintain "a policy that provides for termination in appropriate circumstances of subscribers and account holders of the service provider's system or network who are repeat infringers." If you have and implement such a policy, and you otherwise qualify for the safe harbor, you should be free from fear of copyright damages.
In this case, the copyright notice you received was likely triggered by a program I run called Nym. Nym is a network software that helps users to enhance their privacy, security, and safety online.
The program does not host any content. Rather, it is part of a network of nodes on the Internet that simply pass packets among themselves before sending them to their destinations, just as any Internet intermediary does. The difference is that Nym tunnels the connections such that no intermediary can learn both the source and destination of the packets, giving users protection from nefarious snooping on network traffic. The result is that, unlike most other Internet traffic, the final IP address that the recipient receives is not the IP address of the sender. Nym protects users against hazards such as harassment, spam, and identity theft.
Nym aims to improve on technology developed by Panoramix by building a decentralized authentication and payment protocol. It will enable developers to build their own sustainable privacy-enhanced services. Panoramix is an EU-funded Horizon 2020 programme with the goal of protecting communication privacy by building a comprehensive mixnet infrastructure. This project has received funding from the European Unions Horizon 2020 research and innovation programme under the Grant Agreement No 653497, "Privacy and Accountability in Networks via Optimized Randomized Mix-nets (Panoramix)” (For more on Nym, see https://www.nym.com/, For more on Panoramix, see https://panoramix.me/ .) I hope, as an organization committed to protecting the privacy of its customers, you'll agree that this is a valuable technology.
While the Nym node that I run may appear to be the source of material that is alleged to be infringing, I do not host that material. I do not select the material transmitted through the Nym node that I run, and I have no practical means of either identifying the source of such material or preventing its transmission. In addition, I do nothing to encourage or promote the use of the Nym network for copyright infringement or other prohibited activities. For these reasons, I am not an infringer of copyright in any materials that are transmitted through the Nym node that I run, either directly or under a theory of contributory or vicarious liability. In addition, as you are just acting as a conduit, you should continue to be protected under the DMCA 512(a) safe harbor provision without taking any further action.
Thank you for working with me on this matter. As a loyal subscriber, I appreciate your notifying me of this issue and hope that the protections of DMCA 512 put any concerns you may have to rest. If not, please contact me with any further questions.
Very truly yours,
Your customer, <YOUR_NAME/PSEUDONYM>
```
@@ -1,19 +0,0 @@
```
Hi,
I am reaching out to introduce myself! I am about to spin up a machine with you to run what is called a “nym node” - think of it as somewhat similar to a Tor exit node.
You can always recognize a nym node by our domain names: nym-exit
Nym node runners are a decentralized community all over the world. We provide secure internet traffic routing services to ordinary people and businesses via the Nym platform and NymVPN app.
The Nym platform implements strict encryption and security standards, which also means I simply relay traffic and do not know the end-destination nor its content.
The Nym traffic pattern is somewhat unique, as we route traffic using the “sphinx” packet format (again, think onion routing), which makes all traffic look uniform. Id like to ensure this unique traffic pattern doesnt raise any flags or issues with you! See https://nym.com/ for more details.
Feel free to ask any questions.
Many thanks,
<YOUR_NAME/PSEUDONYM>
```
@@ -1 +1 @@
807_251_217
805_903_308
@@ -1 +1 @@
1_025_628
1_022_821
@@ -1 +1 @@
403_625_608
402_951_654
@@ -1,7 +1,7 @@
| **Item** | **Description** | **Amount in NYM** |
|:-------------------|:------------------------------------------------------|--------------------:|
| Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 |
| Mixmining Reserve | Tokens releasing for operators rewards | 192_748_782 |
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 |
| Circulating Supply | Amount of unlocked tokens | 807_251_217 |
| Stake Saturation | Optimal size of node self-bond + delegation | 1_025_628 |
| Mixmining Reserve | Tokens releasing for operators rewards | 194_096_191 |
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 500 |
| Circulating Supply | Amount of unlocked tokens | 805_903_308 |
| Stake Saturation | Optimal size of node self-bond + delegation | 1_022_821 |
@@ -1 +1 @@
Monday, February 3rd 2025, 13:47:19 UTC
Thursday, January 23rd 2025, 10:41:32 UTC
+20 -25
View File
@@ -1,26 +1,21 @@
**ISP**,**Locations**,**Public IPv6**,**Crypto Payments**,**Comments**,**Last Updated**
[Flokinet](https://flokinet.is),"Netherlands, Iceland, Romania,France","Yes, needs a ticket and custom setup","yes, including XMR","Very slow customer support","05/2024"
[BitLaunch](https://bitlaunch.io),"Canada, USA, UK","No","Yes","Expensive. Digial Ocean through BitLanch has IPv6","05/2024"
[Hostinger](https://hostinger.com),"France, Lithuania, India, USA, Brazil","Yes, out of the box","Yes","Crypto payments must be done per each server monthly or annually.","05/2024"
[Linode](https://linode.com),"USA, Canada, Japan, India, Indonesia, Sweden, Netherlands, Germany, Brazil, France, UK, Australia, Italy","Yes out of the box","No, only through [BitLAunch](https://bitlaunch.io)","IPv6 sometimes need to be re-added in Networking tab, no reboot needed","05/2024"
[Cherry Servers](https://www.cherryservers.com),"Lithuania, Netherlands, USA, Singapore","No","Yes","Issued IP doesnt match the location offered by the provider.","05/2024"
[Njalla](https://nja.la),"Sweden","Yes","Yes","Privacy vandguards! The biggest VPS 45 is 3 cores only, but it works better than many “larger” servers on the market.","05/2024"
[HostSailor](https://hostsailor.com),"USA","Yes, based on ticket","Yes","The IPv6 setup needs custom research and is not documented","05/2024"
[Misaka](https://www.misaka.io/),"South Africa","Yes, native support","No","Very Expensive","05/2024"
[IsHosting](https://ishosting.com/en),"Brazil, Netherlands","Yes, based on ticket","Yes","Expensive","05/2024"
[AlexHost](https://alexhost.com),"Moldova, Bulgaria, Sweden, Netherlands","Yes, on by default","Yes","They allow TOR Bridges, Relays. Exit nodes are only allowed on dedicated servers (prices start from 26 EUR)","07/2024"
[iHostArt](https://ihostart.com),"Romania","Yes, on by default","Yes","Super permissive provider. They do allow Tor Exit/Relay/Bridge. Pro-free speech etc. Recently, IPv6 geolocation was set to North Korea, so be aware.","07/2024"
[Incognet](https://incognet.io),"Netherlands and USA","Yes, on by default","Yes","They allow Tor exit nodes but you must adhere to their rules https://incognet.io/tor-exits","07/2024"
[vSys Host](https://vsys.host),"Ukraine, Netherlands, USA","Yes, on by default","Yes","Pretty permissive provider registered in Ukraine. Should allow Relay/Exit nodes but nothing in T&C, so better double check.","07/2024"
[LiteServer](https://liteserver.nl),"Netherlands","Yes, on by default","Yes","Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal","07/2024"
[TerraHost](https://terrahost.no),"Norway","Yes, on by default","Yes","Very reliable Norwegian provider. Only allow exit nodes on Dedicated servers subject to certain caveats (you must open a ticket). Always check T&C https://terrahost.no/avtalebetingelser","07/2024"
[Mevspace](https://mevspace.com),"Poland","Yes, on by default","Yes","Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff","07/2024"
[Hostiko](https://hostiko.com.ua),"Ukraine, Germany","Yes, on by default","Yes","Ukrainian provider. They allow Exit nodes on Germany boxes but limit the bandwidth, you also have to restrict certain ports like 25 and 587. Make sure you open a ticket.","07/2024"
[Hostslick](https://hostslick.com),"Netherlands, Germany","Yes, on by default","Yes","Good amount of bandwidth for the price. Make sure you open the ticket if you want to run Exit node","07/2024"
[RDP](https://rdp.sh),"Netherlands, USA, Poland","Yes, on by default","Yes","German provider. Exit nodes are allowed, policy is here https://rdp.sh/docs/faq/tor ports 25,465,587 must be closed. Make sure you open a ticket before running an exit node.","07/2024"
[Flokinet](https://flokinet.is),"Netherlands, Iceland, Romania,France","Yes, needs a ticket and custom setup","yes, including XMR",Very slow customer support,05/2024
[BitLaunch](https://bitlaunch.io),"Canada, USA, UK",No,Yes,Expensive. Digial Ocean through BitLanch has IPv6,05/2024
[Hostinger](https://hostinger.com),"France, Lithuania, India, USA, Brazil","Yes, out of the box",Yes,Crypto payments must be done per each server monthly or annually.,05/2024
[Linode](https://linode.com),"USA, Canada, Japan, India, Indonesia, Sweden, Netherlands, Germany, Brazil, France, UK, Australia, Italy",Yes out of the box,"No, only through [BitLAunch](https://bitlaunch.io)","IPv6 sometimes need to be re-added in Networking tab, no reboot needed",05/2024
[Cherry Servers](https://www.cherryservers.com),"Lithuania, Netherlands, USA, Singapore",No,Yes,Issued IP doesnt match the location offered by the provider.,05/2024
[Njalla](https://nja.la),Sweden,Yes,Yes,"Privacy vandguards! The biggest VPS 45 is 3 cores only, but it works better than many “larger” servers on the market.",05/2024
[HostSailor](https://hostsailor.com),USA,"Yes, based on ticket",Yes,The IPv6 setup needs custom research and is not documented,05/2024
[Misaka](https://www.misaka.io/),South Africa,"Yes, native support",No,Very Expensive,05/2024
[IsHosting](https://ishosting.com/en),"Brazil, Netherlands","Yes, based on ticket",Yes,Expensive,05/2024
[AlexHost](https://alexhost.com),"Moldova, Bulgaria, Sweden, Netherlands","Yes, on by default",Yes,"They allow TOR Bridges, Relays. Exit nodes are only allowed on dedicated servers (prices start from 26 EUR)",07/2024
[iHostArt](https://ihostart.com),Romania,"Yes, on by default",Yes,"Super permissive provider. They do allow Tor Exit/Relay/Bridge. Pro-free speech etc. Recently, IPv6 geolocation was set to North Korea, so be aware.",07/2024
[Incognet](https://incognet.io),Netherlands and USA,"Yes, on by default",Yes,They allow Tor exit nodes but you must adhere to their rules https://incognet.io/tor-exits,07/2024
[vSys Host](https://vsys.host),"Ukraine, Netherlands, USA","Yes, on by default",Yes,"Pretty permissive provider registered in Ukraine. Should allow Relay/Exit nodes but nothing in T&C, so better double check.",07/2024
[LiteServer](https://liteserver.nl),Netherlands,"Yes, on by default",Yes,Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal,07/2024
[TerraHost](https://terrahost.no),Norway,"Yes, on by default",Yes,Very reliable Norwegian provider. Only allow exit nodes on Dedicated servers subject to certain caveats (you must open a ticket). Always check T&C https://terrahost.no/avtalebetingelser,07/2024
[Mevspace](https://mevspace.com),Poland,"Yes, on by default",Yes,"Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff",07/2024
[Hostiko](https://hostiko.com.ua),"Ukraine, Germany","Yes, on by default",Yes,"Ukrainian provider. They allow Exit nodes on Germany boxes but limit the bandwidth, you also have to restrict certain ports like 25 and 587. Make sure you open a ticket.",07/2024
[Hostslick](https://hostslick.com),"Netherlands, Germany","Yes, on by default",Yes,Good amount of bandwidth for the price. Make sure you open the ticket if you want to run Exit node,07/2024
[RDP](https://rdp.sh),"Netherlands, USA, Poland","Yes, on by default",Yes,"German provider. Exit nodes are allowed, policy is here https://rdp.sh/docs/faq/tor ports 25,465,587 must be closed. Make sure you open a ticket before running an exit node.",07/2024
[PQ.Hosting](https://bill.pq.hosting),"Netherlands, USA, UK, Moldova, France","Yes, for a small fee",Yes,Support claims that they allow everything. So far Nym nodes never had any problems,01/2025
1 **ISP** **Locations** **Public IPv6** **Crypto Payments** **Comments** **Last Updated**
2 [Flokinet](https://flokinet.is) Netherlands, Iceland, Romania,France Yes, needs a ticket and custom setup yes, including XMR Very slow customer support 05/2024
3 [BitLaunch](https://bitlaunch.io) Canada, USA, UK No Yes Expensive. Digial Ocean through BitLanch has IPv6 05/2024
4 [Hostinger](https://hostinger.com) France, Lithuania, India, USA, Brazil Yes, out of the box Yes Crypto payments must be done per each server monthly or annually. 05/2024
5 [Linode](https://linode.com) USA, Canada, Japan, India, Indonesia, Sweden, Netherlands, Germany, Brazil, France, UK, Australia, Italy Yes out of the box No, only through [BitLAunch](https://bitlaunch.io) IPv6 sometimes need to be re-added in Networking tab, no reboot needed 05/2024
6 [Cherry Servers](https://www.cherryservers.com) Lithuania, Netherlands, USA, Singapore No Yes Issued IP doesn’t match the location offered by the provider. 05/2024
7 [Njalla](https://nja.la) Sweden Yes Yes Privacy vandguards! The biggest VPS 45 is 3 cores only, but it works better than many “larger” servers on the market. 05/2024
8 [HostSailor](https://hostsailor.com) USA Yes, based on ticket Yes The IPv6 setup needs custom research and is not documented 05/2024
9 [Misaka](https://www.misaka.io/) South Africa Yes, native support No Very Expensive 05/2024
10 [IsHosting](https://ishosting.com/en) Brazil, Netherlands Yes, based on ticket Yes Expensive 05/2024
11 [AlexHost](https://alexhost.com) Moldova, Bulgaria, Sweden, Netherlands Yes, on by default Yes They allow TOR Bridges, Relays. Exit nodes are only allowed on dedicated servers (prices start from 26 EUR) 07/2024
12 [iHostArt](https://ihostart.com) Romania Yes, on by default Yes Super permissive provider. They do allow Tor Exit/Relay/Bridge. Pro-free speech etc. Recently, IPv6 geolocation was set to North Korea, so be aware. 07/2024
13 [Incognet](https://incognet.io) Netherlands and USA Yes, on by default Yes They allow Tor exit nodes but you must adhere to their rules https://incognet.io/tor-exits 07/2024
14 [vSys Host](https://vsys.host) Ukraine, Netherlands, USA Yes, on by default Yes Pretty permissive provider registered in Ukraine. Should allow Relay/Exit nodes but nothing in T&C, so better double check. 07/2024
15 [LiteServer](https://liteserver.nl) Netherlands Yes, on by default Yes Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal 07/2024
16 [TerraHost](https://terrahost.no) Norway Yes, on by default Yes Very reliable Norwegian provider. Only allow exit nodes on Dedicated servers subject to certain caveats (you must open a ticket). Always check T&C https://terrahost.no/avtalebetingelser 07/2024
17 [Mevspace](https://mevspace.com) Poland Yes, on by default Yes Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff 07/2024
18 [Hostiko](https://hostiko.com.ua) Ukraine, Germany Yes, on by default Yes Ukrainian provider. They allow Exit nodes on Germany boxes but limit the bandwidth, you also have to restrict certain ports like 25 and 587. Make sure you open a ticket. 07/2024
19 [Hostslick](https://hostslick.com) Netherlands, Germany Yes, on by default Yes Good amount of bandwidth for the price. Make sure you open the ticket if you want to run Exit node 07/2024
20 [RDP](https://rdp.sh) Netherlands, USA, Poland Yes, on by default Yes German provider. Exit nodes are allowed, policy is here https://rdp.sh/docs/faq/tor ports 25,465,587 must be closed. Make sure you open a ticket before running an exit node. 07/2024
21 [PQ.Hosting](https://bill.pq.hosting) Netherlands, USA, UK, Moldova, France Yes, for a small fee Yes Support claims that they allow everything. So far Nym nodes never had any problems 01/2025
@@ -4,8 +4,6 @@ The information below is generated with [Redoc](https://redocly.com/docs/redoc)
There is also an overview of other Validator endpoints at [https://cosmos.directory/nyx](https://cosmos.directory/nyx).
<br /><br />
<RedocStandalone
specUrl="https://api.nymtech.net/swagger/swagger.yaml"
options={{
@@ -1,11 +1,9 @@
import { RedocStandalone } from 'redoc';
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://api.sandbox.nymtech.net/swagger/swagger.yaml](https://api.sandbox.nymtech.net/swagger/swagger.yaml).
<br /><br />
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at []() which is also used to generate the Swagger docs deployed at []().
<RedocStandalone
specUrl="https://api.sandbox.nymtech.net/swagger/swagger.yaml"
specUrl=""
options={{
nativeScrollbars: true,
theme: {
@@ -3,8 +3,6 @@ import { RedocStandalone } from 'redoc';
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://explorer.nymtech.net/api/v1/openapi.json](https://explorer.nymtech.net/api/v1/openapi.json) which is also used to generate the Swagger docs deployed at [https://explorer.nymtech.net/api/swagger/index.html](https://explorer.nymtech.net/api/swagger/index.html).
<br /><br />
<RedocStandalone
specUrl="https://sandbox-explorer.nymtech.net/api/v1/openapi.json"
options={{
@@ -2,8 +2,6 @@ import { RedocStandalone } from 'redoc';
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://sandbox-explorer.nymtech.net/api/v1/openapi.json](https://sandbox-explorer.nymtech.net/api/v1/openapi.json) which is also used to generate the Swagger docs deployed at [https://sandbox-explorer.nymtech.net/api/swagger/index.html](https://sandbox-explorer.nymtech.net/api/swagger/index.html).
<br /><br />
<RedocStandalone
specUrl="https://sandbox-explorer.nymtech.net/api/v1/openapi.json"
options={{
@@ -2,8 +2,6 @@ import { RedocStandalone } from 'redoc';
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json](https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://mainnet-node-status-api.nymtech.cc/swagger/](https://mainnet-node-status-api.nymtech.cc/swagger/).
<br /><br />
<RedocStandalone
specUrl="https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json"
options={{
@@ -2,8 +2,6 @@ import { RedocStandalone } from 'redoc';
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://sandbox-node-status-api.nymte.ch/api-docs/openapi.json](https://sandbox-node-status-api.nymte.ch/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://sandbox-node-status-api.nymte.ch/swagger/](https://sandbox-node-status-api.nymte.ch/swagger/).
<br /><br />
<RedocStandalone
specUrl="https://sandbox-node-status-api.nymte.ch/api-docs/openapi.json"
options={{
@@ -1,27 +1,10 @@
import APITable from 'components/api-table.tsx';
import { RedocStandalone } from 'redoc';
import { Callout } from 'nextra/components'
You can find the OpenAPI spec found at [https://validator.nymtech.net/api-docs/openapi.json](https://validator.nymtech.net/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://validator.nymtech.net/api/swagger/index.html](https://validator.nymtech.net/api/swagger/index.html).
<Callout type="info" emoji="️">
You can find the Swagger endpoints of other NymAPI instances at the following endpoints:
<APITable endpoint="https://api.nymtech.net/cosmwasm/wasm/v1/contract/n19604yflqggs9mk2z26mqygq43q2kr3n932egxx630svywd5mpxjsztfpvx/smart/eyJnZXRfY3VycmVudF9kZWFsZXJzIjogeyJsaW1pdCI6IDMwfX0=" />
There is also an overview of endpoints at [https://cosmos.directory/nyx](https://cosmos.directory/nyx).
</Callout>
<br /><br />
<RedocStandalone
specUrl="https://validator.nymtech.net/api-docs/openapi.json"
options={{
nativeScrollbars: true,
theme: {
sidebar: {
backgroundColor: '#273239',
textColor: '#FCFDFE'
}
}
}}
/>
> Interactive Redoc component coming soon
@@ -1,13 +1,9 @@
import { Callout } from 'nextra/components';
import { Tabs } from 'nextra/components';
import { MyTab } from 'components/generic-tabs.tsx';
import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
import { VarInfo } from 'components/variable-info.tsx';
import { AccordionTemplate } from 'components/accordion-template.tsx';
import { Steps } from 'nextra/components';
import AbuseResponse from 'components/operators/templates/dmca_response.md';
import OperatorIntroduction from 'components/operators/templates/provider_introduction.md';
export const TestingSteps = () => (
<div>
@@ -33,11 +29,6 @@ export const TunnelManagerCommands = () => (
</div>
);
export const LoadEndpointInfo = () => (
<div>
Developer notes behind <code>/load</code> endpoint
</div>
);
# Changelog
@@ -47,408 +38,6 @@ This page displays a full list of all the changes during our release cycle from
<VarInfo />
## `v2025.2-hu`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.2-hu)
- [`nym-node`](nodes/nym-node.mdx) version `1.4.0`
```
nym-node
Binary Name: nym-node
Build Timestamp: 2025-02-04T09:35:42.399220545Z
Build Version: 1.4.0
Commit SHA: 4c2bf3642e8eec0d55c7753e14429d73ac2d0e99
Commit Date: 2025-02-04T10:29:48.000000000+01:00
Commit Branch: HEAD
rustc Version: 1.84.1
rustc Channel: stable
cargo Profile: release
```
### Operators Updates & Tools
<Callout type="info">
From `nym-node v1.3.0` operators can technically choose multiple functionalities for their `nym-node` binary (flagged as `--mode`).
**However, the clients are yet to be developed to be able to make a proper selection for multi-mode nodes!**
**WE ASK OPERATORS TO ASSIGN ONLY ONE FUNCTIONALITY TO `--mode` OPTION PER NODE. PLEASE SELECT ONE MODE ARGUMENT OUT OF: `mixnode` or `entry-gateway` or `exit-gateway`!**
</Callout>
- Updated maintenance guides to [backup](nodes/maintenance#backup-a-node), [restore](nodes/maintenance#restoring-a-node) and [move](nodes/maintenance#moving-a-node) a node, containing a new and important commands to backup and restore `clients.sqlite` database.
- [New explanation of `nym-node` functionalities](nodes/nym-node/setup#functionality-mode) describing how Gateways get selected in Mixnet mode and Wireguard mode.
<Callout type="warning">
Wireguard nodes route data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run a node with active wireguard routing, please read our [Community Counsel pages](community-counsel/exit-gateway) containing more information and some legal content.
**Wireguard mode has no exit policy right now - we are working on the implementation.**
</Callout>
#### Legal support
We have been notified that a handful of nodes have been taken down by abuse reports. We created new pages with [legal suggestions](community-counsel/legal) and email [templates](community-counsel/templates). Here are some useful points on legal support:
- Here is the first version of a response template tailored for Nym operators.
<AccordionTemplate name="Email template: responding to DMCA take-down notices">
<AbuseResponse />
</AccordionTemplate>
- We are starting Operators Legal Clinic with Alexis Roussel, every Wednesday, 14:30 UTC (60 min) in our [Operator Legal Forum channel on Matrix](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu). Come and share your findigs and questions with the rest of the operators.
- Join [Operator Legal Forum channel](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu) and share as much as possible (like screen prints, provider, location etc).
- Join [Community legal counsel](https://nym.com/docs/operators/community-counsel) - our collective knowledge hub. Add your findings by opening a [Pull Request](https://nym.com/docs/operators/add-content)
- While we are working on a new list of more friendly providers, consider to move away from these provides as soon as possible:
- Servinga / VPS2day (AS39378)
- Frantech / Ponynet / BuyVM (AS53667)
- OVH SAS / OVHcloud (AS16276)
- Online S.A.S. / Scaleway (AS12876)
- Hetzner Online GmbH (AS24940)
- IONOS SE (AS8560)
- Psychz Networks (AS40676)
- 1337 Services GmbH / RDP.sh (AS210558)
- Backup your nodes to have access to `.nym` directory locally. Follow [node](nodes/maintenance#backup-a-node) and [proxy configuration](nodes/maintenance#backup-proxy-configuration) backup guides to be able to [restore your node](nodes/maintenance#restoring-a-node) later on on another machine, without losing your delegation.
- We would like to ask operators who use reverse proxy and a domain (required for Gateways) to start using a common convention starting with `nym-exit` for their nodes URL. The entire address should have this new format:
```
nym-exit.<CUSTOM>.<DOMAIN>.<TLD>
```
For example:
```
# for squads running multiple nodes a format can look like this:
nym-exit.ch-node1.mysquad.org
# or like this
nym-exit.3-jamaica.mysquad.org
# for operators having one node per location, the format can look like this:
nym-exit.brazil.mysquad.org
# or if operators decide to not have any custom, they can simply have this format:
nym-exit.mysquad.org
```
**The `NYM-EXIT` part in the beginning is what's important.**
- When registering a domain, check [Top Level Domain (TLD)](https://www.techopedia.com/definition/1348/top-level-domain-tld) terms and conditions. For example `.icu` is a no go. Having a wrong TLD may lead to your domain being taken away from you when facing a DMCA report.
- Write a message to your provider and introduce your intention to run a Nym Node on their service
<AccordionTemplate name="Email template: Introduce yourself to your VPS provider">
<OperatorIntroduction />
</AccordionTemplate>
- New page with [templates for VPS provider](community-counsel/templates)
#### Delegation Program
**If you are interested to sign up to delegation program que, message Merve on Element for the time being as we are working on CRM upgrade. We review nodes dynamically and delegate to new ones once a month.** New operators interested to join DP must follow the rules specified [here](https://delegations.explorenym.net/), run one of two latest binary version and have [Terms & Conditions](nodes/nym-node/setup#terms--conditions) accepted.
Note: Due to the token price we allow operators to have Operators cost 1000 NYM, the profit margin maximum remains 20%.
**Delegation and undelegation in January**
Delegated to all these nodes:
<AccordionTemplate name="New delegated nodes">
```
AMnDNd1Xgw7Em9R5vehP7r12ZNWUZ3jmitDHya6gpvGR
DLkKyYcA5feq43rqZdx6nJBNZvQsdX7kb2f1f6ED4cs2
26ZmTxTVBKHZg8MTKwypHkXZVJhDC7QHuv3BdsyRyTuk
5ZWdDN9pQ18vYkYYs5ZERh4P4JLtMiijscZ6FvwSfVxR
3uBgUJR393acoCRysu6SiLsximiwAMM85QFxq9WD8puC
5rXcNe2a44vXisK3uqLHCzpzvEwcnsijDMU7hg4fcYk8
nuMerN7ahqsptK8zDUZhnxMyDqePza8vWDf8k171EEs
86cNnnRxNpGdEtSX9UP1GpT6xrNvNuxFdHNkZcK7pAJg
9ngFADaYpT54F9i8iYFZLYR7SB7hrubsPArwpqe1RXQb
FQBbq1crAkCrjVBnEN85VqgZgGRMLJV65NJk8bPADdw
FUH3E3dghXjC9hdt3jeAhSsxwozsw1yzBXkoaFqkL4ci
5wF5wN7T2UuSsiUcQyL77NwdnXwAXxFttpZ1dR1nu4kR
4pVq4QSCcq4zNCnBNCfyugPjVhVyGDCBDSW5rghg8oZS
8XW1WWN1PuuAYz2TXQ9iWRekMFEJXjdEq4asMyE2qCCN
CxgjcTTAnRJzDxMbqtX73vEbWwLQmVoW8zS8RZEicgfT
7fwyXd4Cmh92d4piEPHmdFm1Wz1QCFQDf6bnDGNT3M6P
```
</AccordionTemplate>
Undelegated from 8 nodes for not meeting the rules (being offline):
<AccordionTemplate name="Undelegated nodes">
```
# Gateways
DLxLKsd3LTnfudSSmHanPaZACsh1x4MGEzfJS4jQibir
DiciBkjHovXzTDE2EFJKPNj3TGw2oQjr7HPSas2YQPiQ
ADjpymCgjFsE5m7YvnZFxLMscg85dCUUesR5g8yN3Mzz
C9dEABjtFRMD1x4ZnbqnsELGJgutT73bPCfYzqBe7sHB
# Mixnodes
2FGgY5zWq6JP1BvpnLPbedWRYYAZELFCT9rMybNhnxpo
4nKkwPSbkbtkw3yrRKQVAVdviNgFgcNskgcSGdT2Sucy
DjY3T7n6VoHGcETMnmtZKmUU7NZP6AhCs8CoRsSSbViC
FAKhiQ8nW5sAWAxks1WB8u1MAWsapToCSE3KmF9LuGRQ
```
</AccordionTemplate>
Undelegated due to high saturation:
```
5omopSZy59UyNPpx9P97vjBnN6PwPw9x5MVUx5kuNaXt
```
#### Service Grant Program v2
Aside from delegating on top of nodes, Nym runs a Service Grant Program (SGP) to support Exit Gateway operators before they will be rewarded by collecting [zk-nym tickets](../network/cryptography/zk-nym) from users subscription. Operators included in SGP are long term active community members with the highest requirements on the technical setup and upgrading pace. We are about to start a second iteration of SGP very soon (SGPv2). The final slots and locations are yet to be concluded. Priority to participate in SGPv2 will be given to the current operators in SGP. Based on the number of slots, we will then determine how many more operators can sign up.
##### Rules of SGPv2
<Callout type="info">
**We will share more info soon in the channels. The rules are not set in stone and could potentially be altered or updated in the future! Do *not* purchase new servers neither migrate your nodes just yet.**
</Callout>
As we finalising last details of *"Project Smoosh"*, where one binary - `nym-node` - can run as an `entry-gateway`, `mixnode` or `exit-gateway` in Mixnet mode as well as `entry-gateway` or `exit-gateway` in Wireguard mode, we plan to step up the game. SGPv2 grants will be higher if operators can meet new requirements.
**Minimum Specs & Requirements**
These are minimum requirements to become a part of SGPv2. We aim to have nodes on dedicated servers, with exceptions for much stronger VPS to meet the needed criteria while sharing server with other users.
<div>
<Tabs items={[
<><code>nym-node</code> - dedicated server</>,
<><code>nym-node</code> - VPS</>,
]} defaultIndex="0">
<MyTab>
#### `nym-node` - dedicated server
| **Hardware** | **Minimum Specification** |
| :--- | ---: |
| Type of server | Dedicated or bare metal |
| CPU Cores | 4 |
| Memory | 8-16 GB RAM |
| Storage | 50 GB (preferably 80 GB) |
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
| Bandwidth | Unmetered if possible, or {" >"} 40Tb |
| Port speed | 1Gbps |
- Exit Gateways only
- [Terms & Conditions](nodes/nym-node/setup#terms--conditions) accepted
- Nodes are [bonded](nodes/nym-node/bonding#migrate-to-nym-node-in-mixnet-smart-contract) as `nym-node` not as a legacy node
- Only `Latest` binary version is accepted to enter the program
- Timely upgrades without direct message notifications: Only `Latest` version and the two preceding are accepted to stay in the program
- Dedicated (or bare metal) machine is to ensure that the numbers above are *dedicated* for `nym-node` operation
- Unmetered bandwidth is to ensure smooth user experience without data allowance limitation - 40Tb is a minimum for locations where unlimited bandwidth is not an option.
- 1Gbps is an expected speed of Nym network in order to meet expectations of NymVPN users
- Squads operating more than 2 nodes are expected to run larger servers and divide them for multiple nodes - We will share a how-to guide soon
- Operators need to run [reverse proxy](nodes/nym-node/configuration) with landing page URL starting with `nym-exit` and [Web Secure Socket](nodes/nym-node/configuration) configuration
- Operators must write their providers upfront that they will run a Nym Exit Gateway on their servers, using [this template](community-counsel/templates#introduction-to-server-provider)
- If a node remains offline for more than 5 days for any reason, including abuse reports, and the operator doesn't resolve it neither doesn't communicate the blockers, they will be removed from the program
</MyTab>
<MyTab>
#### `nym-node` - VPS
| **Hardware** | **Minimum Specification** |
| :--- | ---: |
| Type of server | VPS |
| CPU Cores | 4 |
| Memory | 8-16 GB RAM |
| Storage | 50 GB (preferably 80 GB) |
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
| Bandwidth | Unmetered if possible, or {" >"} 40Tb |
| Port speed | 10Gbps |
- Exit Gateways only
- [Terms & Conditions](nodes/nym-node/setup#terms--conditions) accepted
- Nodes are [bonded](nodes/nym-node/bonding#migrate-to-nym-node-in-mixnet-smart-contract) as `nym-node` not as a legacy node
- Only `Latest` binary version is accepted to enter the program
- Timely upgrades without direct message notifications: Only `Latest` version and the two preceding are accepted to stay in the program
- Unmetered bandwidth is to ensure smooth user experience without data allowance limitation - 40Tb is a minimum for locations where unlimited bandwidth is not an option
- 10Gbps is to ensure that the expected speed of 1Gbps is met even when sharing a server with other users
- Squads operating more than 2 nodes are expected to run larger servers and divide them for multiple nodes - We will share a how-to guide soon
- Operators need to run [reverse proxy](nodes/nym-node/configuration) with landing page URL starting with `nym-exit` and [Web Secure Socket](nodes/nym-node/configuration) configuration
- Operators must write their providers upfront that they will run a Nym Exit Gateway on their servers, using [this template](community-counsel/templates#introduction-to-server-provider)
- If a node remains offline for more than 5 days for any reason, including abuse reports, and the operator doesn't resolve it neither doesn't communicate the blockers, they will be removed from the program
</MyTab>
</Tabs>
</div>
**Operators interested in joining SGPv2 can start by searching for servers that meet the above criteria, where they may eventually migrate their nodes, and then share their findings by submitting a form which will be shared shortly. Do *not* buy a new server, neither migrate a node just yet! We will be first reaching to the current participants of Service Grant Program. Everything will be announced, sending DMs to devrel will not speed up the process!**
### Features
- [build(deps): bump criterion from `0.4.0` to `0.5.1`](https://github.com/nymtech/nym/pull/4911): Bumps [criterion](https://github.com/bheisler/criterion.rs) from `0.4.0` to `0.5.1`.
- [NS API: add mixnet scraper](https://github.com/nymtech/nym/pull/5200)
- [build(deps): bump http from `1.1.0` to `1.2.0`](https://github.com/nymtech/nym/pull/5228): Bumps [http](https://github.com/hyperium/http) from `1.1.0` to `1.2.0`.
- [`http-api-client`: deduplicate code](https://github.com/nymtech/nym/pull/5267): After adding PATCH support in [\#5260](https://github.com/nymtech/nym/pull/5260), it's now time to de-duplicate some of the code
- [Add windows to CI builds](https://github.com/nymtech/nym/pull/5269)
- [nym topology revamp](https://github.com/nymtech/nym/pull/5271): This PR changes the internals of the `NymTopology` to blur the lines between explicit mixnodes and gateways so that what used to be considered a "mixnode" could be a valid egress point of the network. `NymTopology` is no longer divided into `BTreeMap<MixLayer, Vec<mix::Node>>` and `Vec<gateway::Node>`. instead there's information about the current rewarded set (to support future VRF work) and a simple map of `HashMap<NodeId, RoutingNode>`. The new features are mostly controlled via 2 new flags/config values:
- `use_extended_topology` that tells the client to retrieve **all** network nodes rather than the ones that got assigned "active" mixnode role (or support being a gateway)
- `ignore_egress_epoch_role` that tells the client it's fine to construct egress packets to nodes that are **not** assigned entry or exit gateway role
- [Nyx Chain Watcher](https://github.com/nymtech/nym/pull/5274)
- [Include `IPINFO_API_TOKEN` in nightly CI](https://github.com/nymtech/nym/pull/5285)
- [Move tun constants to network defaults](https://github.com/nymtech/nym/pull/5286):
<AccordionTemplate name={<TestingSteps/>}>
1. **Regression Testing**:
- Verified no issues arose when running tests for the affected files.
- Tested TUN behaviour with new nym-nodes in the hu branch.
**Results**:
- **No bugs detected**.
- Tunnels are functioning as expected, with traffic routing and IP generation working seamlessly.
</AccordionTemplate>
- [Add dependabot assignes for the root cargo ecosystem](https://github.com/nymtech/nym/pull/5297)
- [build(deps): bump the patch-updates group across 1 directory with 35 updates](https://github.com/nymtech/nym/pull/5310): Bumps the `patch-updates` group with 33 updates
- [Periodically remove stale gateway messages](https://github.com/nymtech/nym/pull/5312): This PR introduces a simple task that removes gateway messages that haven't been retrieved in (by default) 24h.
<AccordionTemplate name={<TestingSteps/>}>
**Automation Script for Data Cleanup Validation**
Test Objective: Validate that the stale message cleanup mechanism in the database correctly removes records older than the configured threshold (24 hours).
Test Setup:
1. Environment:
* SQLite database
* Bash script: used to insert data.
Steps Performed:
1. Ran the insert_data.sh script to populate the database with test data:
* Recent records inserted successfully.
2. Verified the database content post-insertion: sqlite3 clients.sqlite "SELECT * FROM message_store;"
3. Confirmed that all 20 records (10 recent + 10 stale) were present.
4. Allowed the system to run for 24 hours to trigger the cleanup mechanism.
5. Queried the database again after 24 hours: sqlite3 gateway_storage.db "SELECT * FROM message_store;"
6.
Expected Result:
* All stale records (older than 24 hours) should be removed.
* Recent records should remain in the database.
Actual Result:
* Query after 24 hours showed only the 10 recent records.
* All 10 stale records were successfully removed.
</AccordionTemplate>
- [Use expect in geodata test to give error message on failure](https://github.com/nymtech/nym/pull/5314): Keep hitting this error on CI, from what I think is network hickup. But it's hard to tell form the log since the error is swallowed. Explicitly unwrap the result so we get a more detailed error output.
<AccordionTemplate name={<TestingSteps/>}>
**Quick Code Review**
**Summary**
1. **CI Workflow**: Adjusted paths to optimise build triggers, avoiding unnecessary CI runs while ensuring coverage for key directories
2. **Geolocation Test**: Improved error handling by replacing assertions with `.expect` for clearer debugging in API regression tests
**Conclusion**
Regression testing confirms everything works as intended. **Approved**.
</AccordionTemplate>
- [`CancellationToken`-based shutdowns](https://github.com/nymtech/nym/pull/5325): This PR introduces scaffolding for using `CancellationToken` and `TaskTracker` for our graceful shutdowns rather than the existing `TaskClient` and `TaskManager`.
- [Introduce `/load` endpoint for self-reported quantised Nym Node load](https://github.com/nymtech/nym/pull/5326): This PR introduces a new `/load` endpoint on a `NymNode` to return its current load. It returns the following data:
```rust
pub struct NodeLoad {
pub total: Load,
pub machine: Load,
pub network: Load,
}
```
Where `Load` is quantised into the following buckets / tiers:
```rust
pub enum Load {
Negligible, // 0 - 0.1
Low, // 0.1 - 0.3
Medium, // 0.3 - 0.6
High, // 0.6 - 0.8
VeryHigh, // 0.8 - 0.95
AtCapacity, // >= 0.95
}
```
<AccordionTemplate name={<LoadEndpointInfo/>}>
The actual values for`NodeLoad` are determined as follows:
- For network we approximate current rx/tx rates on all `eth` interfaces and scale them to the range of 0-1Gbps (for this initial iteration we assume the maximum network speed is 1Gbps which would be treated as fully saturated). So for example, if the node is sending at 0.5Gbps, it would get a `Load` of 0.5 and thus value of `Load::Medium`, 0.9Gbps would get `Load` of 0.9 and value of `Load::VeryHigh`, etc. we take the bigger value between rx and tx
- For machine load there's a bit more logic in there:
- Firstly we determine what I call a "base load" of the machine. we do this by taking the average load from the last 5min (via `getloadavg`) and dividing it by the number of CPUs in the machine. For example if the average load of the machine in the last 5min was `1.23` and it has 2 CPUs, then it's `Load` would be `Load::High` (`1.23 / 2 = 0.615`)
- However, whilst CPU utilisation is one of the most important factors, it does not tell the whole story. I tried to also take memory/swap utilisation into consideration (whilst not making it the main factor)
- Thus we calculate two additional auxiliary `Load` values, for memory usage and swap usage, i.e.: `used_memory / total_memory` and `used_swap / total_swap` respectively.
- Then we check whether either of the `MemoryLoad` or `SwapLoad` is bigger than the current base `Load` of the machine we have determined, if so, it's increased by one tier / bucket. For example, say the current machine load is `Load::Low`, but the memory usage is at 90% (`Load::VeryHigh`). that would result in the reported `Load` being bumped up to `Load::Medium` instead. The same logic applies with swap load, **however, only if the total swap > 1GB**. this is to prevent weird edge cases where the machine has hardly any swap.
- Finally, the `.total` `Load` uses the same "tier bumping" behaviour using the `.total` and `.network` loads, i.e. `if network > machine`, then `total = machine + 1`. for example if `machine` `Load` is `Load::Low`, but `network` `Load` is `Load::Medium`, then the `total` `Load` is set to `Load::Medium` instead.
</AccordionTemplate>
- [Bump the `patch-updates` group with 8 updates](https://github.com/nymtech/nym/pull/5336)
- [Bump `tempfile` from `3.14.0` to `3.15.0`](https://github.com/nymtech/nym/pull/5337)
- [Bump `ts-rs` from `10.0.0` to `10.1.0`](https://github.com/nymtech/nym/pull/5338)
- [Updated `cosmrs` and `tendermint-rpc` to their most recent versions](https://github.com/nymtech/nym/pull/5339)
- [Bump mikefarah/yq from `4.44.6` to `4.45.1`](https://github.com/nymtech/nym/pull/5342)
- [Update `indexed_db_futures`](https://github.com/nymtech/nym/pull/5347): Updates the `indexed_db_futures` dependency to stop relying on the fork.
- [Refresh wasm sdk](https://github.com/nymtech/nym/pull/5353): This PR refreshes the wasm clients to make them usable in the current network
- [Client gateway selection](https://github.com/nymtech/nym/pull/5358): Changed how gateway is selected.
- For init the target gateway can't support mixing
- For egress, unless `ignore_epoch_roles` is specified, the gateway can't be currently assigned to a mixing layer. But it can be standby or inactive
- [Exposed `NymApiClient` method for obtaining node performance history](https://github.com/nymtech/nym/pull/5360)
- [Bind to `[::]` on `nym-node` for both IP versions](https://github.com/nymtech/nym/pull/5361)
<AccordionTemplate name={<TestingSteps/>}>
**IPv4 Configuration and Migration Testing**
Testing Steps:
- Initiated and ran a nym-node with version 1.3.1 on an IPv4-only machine, then updated it to the new 'hu', 1.4.0 version.
- Initiated and ran a new nym-node with 'hu', 1.4.0 always on a machine with only IPv4.
- Initiated and ran a new nym-node with 'hu', 1.4.0 on a machine with both IPv4 and IPv6 for regression testing.
Results:
- No functional issues during version updates.
- Logged message on all versions: "no registered client for destination: ff02::2"
Status: Pass
</ AccordionTemplate>
- [Remove empty ephemeral keys](https://github.com/nymtech/nym/pull/5376)
- [Handle ecash network errors differently](https://github.com/nymtech/nym/pull/5378): For errors that could be caused by network problems, we should mark the ticket as still pending, not as bad. This won't fix the underlying problem, but should not assume anymore that the client is the culprit and penalise its bandwidth quota.
- [Make client ignore dual mode nodes by default](https://github.com/nymtech/nym/pull/5388)
<AccordionTemplate name={<TestingSteps/>}>
**Testing Steps:**
- Ensured a client is able to select a node which aside from a gateway, can also act as a mixnode
- Verified 'ignore_epoch_roles' is the default mode
- _note; this is most likely not going to be a permanent solution_
**Results:**
- Clients and topology are behaving correctly
Status: Pass
</ AccordionTemplate>
- [Update version of chain watcher and validator rewarder](https://github.com/nymtech/nym/pull/5394)
### Bugfix
- [Remove unnecessary arguments for nym-api swagger endpoints](https://github.com/nymtech/nym/pull/5272)
- [Fixed sql migration for adding default message timestamp](https://github.com/nymtech/nym/pull/5374)
- [Terminate mixnet socket listener on shutdown](https://github.com/nymtech/nym/pull/5389)
- [Correctly handle ingore epoch roles flag](https://github.com/nymtech/nym/pull/5390)
## `v2025.1-reeses`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.1-reeses)
@@ -1,7 +1,5 @@
{
"exit-gateway": "Exit Gateway",
"legal": "Legal Counsel",
"templates": "Templates",
"jurisdictions": "Jurisdictions",
"isp-list": "ISP List",
"landing-pages": "Landing Pages",
@@ -1,84 +0,0 @@
import { Callout } from 'nextra/components';
import { Tabs } from 'nextra/components';
import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
import { VarInfo } from 'components/variable-info.tsx';
import { AccordionTemplate } from 'components/accordion-template.tsx';
import { Steps } from 'nextra/components';
import AbuseResponse from 'components/operators/templates/dmca_response.md';
import OperatorIntroduction from 'components/operators/templates/provider_introduction.md';
# Community Counsel: General Legal Considerations
**This page contains some tips & tricks to support operators running Exit Gateway (`nym-node` in mode `exit-gateway`) gathered by the operators community. Have a look on the points below to strengthen your legal protection.**
<VarInfo />
## Useful Suggestions
#### Introduce Nym Node to your provider
Write a message to your provider telling them about your intention to run a `nym-node` on their infrastructure. You can use this template for it.
<br/>
<AccordionTemplate name="Email template: Introduce yourself to your VPS provider">
<OperatorIntroduction />
</AccordionTemplate>
#### Join Operators Legal Forum
This [Matrix channel]((https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu)) is the best place to ask questions and share your experience with others. You can share screen prints of abuse reports and ask for support.
#### Join Operators Legal Clinic
Do you have any questions directed for lawyers? Come and chat with Nym COO Alexis Roussel, every Wednesday 14:30 UTC for 60min in our [Operator Legal Forum channel on Matrix](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu).
#### Use a friendly provider
Nym operators community shares their experience with different ISPs on [this page](isp-list). At the same time, consider to move away from these provides:
- Servinga / VPS2day (AS39378)
- Frantech / Ponynet / BuyVM (AS53667)
- OVH SAS / OVHcloud (AS16276)
- Online S.A.S. / Scaleway (AS12876)
- Hetzner Online GmbH (AS24940)
- IONOS SE (AS8560)
- Psychz Networks (AS40676)
- 1337 Services GmbH / RDP.sh (AS210558)
#### Backup your nodes
Your only way to restore your node is when you have an access to `.nym` directory locally. Follow [node](../nodes/maintenance#backup-a-node) and [proxy configuration](../nodes/maintenance#backup-proxy-configuration) backup guides to be able to [restore your node](../nodes/maintenance#restoring-a-node) on another machine later on, without losing your delegation.
#### Use `nym-exit` prefix on your landing page URL
We would like to ask operators to use [reverse proxy](../nodes/nym-node/configuration/proxy-configuration) with a [landing page](landing-pages). When assigning a domain please use a common convention with `nym-exit` in the beginning of the the page URL as this will create a reputation and reference. The entire address should have this new format:
```
nym-exit.<CUSTOM>.<DOMAIN>.<TLD>
```
For example:
```
# for squads running multiple nodes a format can look like this:
nym-exit.ch-node1.mysquad.org
# or like this
nym-exit.3-jamaica.mysquad.org
# for operators having one node per location, the format can look like this:
nym-exit.brazil.mysquad.org
# or if operators decide to not have any custom, they can simply have this format:
nym-exit.mysquad.org
```
**The `NYM-EXIT` part in the beginning is what's important.**
#### Chose the right TLD
When registering a domain, check [Top Level Domain (TLD)](https://www.techopedia.com/definition/1348/top-level-domain-tld) terms and conditions. For example `.icu` is a no go. Having a wrong TLD may lead to your domain being taken away from you when facing a DMCA report.
#### Respond to abuse reports
Make sure to read notifications from your account provider and if you receive an abuse report, respond to it in time. Here is a template explaining the essential legal background of Nym Node Exit Gateway. Don't forget to adjust the variables.
<br/>
<AccordionTemplate name="Email template: responding to DMCA take-down notices">
<AbuseResponse />
</AccordionTemplate>
#### Help us to improve these pages
Add your findings by opening a [Pull Request](add-content).
@@ -1,23 +0,0 @@
import { Callout } from 'nextra/components';
import { Tabs } from 'nextra/components';
import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
import { VarInfo } from 'components/variable-info.tsx';
import { AccordionTemplate } from 'components/accordion-template.tsx';
import { Steps } from 'nextra/components';
import AbuseResponse from 'components/operators/templates/dmca_response.md';
import OperatorIntroduction from 'components/operators/templates/provider_introduction.md';
# Templates for Providers
Communication with your provider is an essential part of legal defense, as more often than not, terminating a server is based on the lack of knowledge about Nym Node and it's legal bases. Use the following templates to introduce your intention to run a Nym Node or as a response to abuse report.
<VarInfo/ >
## Email Templates
### Introduction to server provider
<OperatorIntroduction />
### DMCA abuse report response
<AbuseResponse />
+3 -1
View File
@@ -35,7 +35,9 @@ Accepting T&Cs is done via a flag `--accept-operator-terms-and-conditions` added
This is a summary of all needed steps for node operators to setup and configure a `nym-node` and register it to Nym Network:
1. **Start with [Preliminary Steps](nodes/preliminary-steps.mdx), preparing [VPS](nodes/preliminary-steps/vps-setup.mdx) and [Nym wallet](nodes/preliminary-steps/wallet-preparation)**
1. **Start with [Preliminary Steps](nodes/preliminary-steps.mdx), preparing:**
- [VPS](nodes/preliminary-steps/vps-setup.mdx)
- [Nym wallet](nodes/preliminary-steps/wallet-preparation)
2. **[Setup](nodes/nym-node/setup.mdx) the node**
@@ -126,32 +126,13 @@ mkdir -pv <PATH_TO_TARGET_DIRECTORY>
# mkdir -pv $HOME/backup/my_nym_node/.nym
```
###### 2. Backup `clients.sqlite` database
- Install `sqlite3`
```sh
apt install sqlite3
```
- Open sqlite CLI shell inside `clients.sqlite` database
```sh
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
```
- Create backup called `clients_backup.sqlite`
```sh
.backup .nym/nym-nodes/default-nym-node/data/clients_backup.sqlite
```
- Exit sqlite CLI shell
```sh
.exit
```
###### 3. Copy configuration folder `.nym` from your VPS to your newly created backup directory
###### 2. Copy configuration folder `.nym` from your VPS to your newly created backup directory
```sh
scp -r <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes/<ID> <PATH_TO_TARGET_DIRECTORY>
```
###### 4. Verify the success of the backup
###### 3. Verify the success of the backup
The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the backup target directory.
@@ -246,33 +227,7 @@ The `scp` command should print logs, an operator can see directly whether it was
* Run a command `echo "$(curl -4 https://ifconfig.me)"` to see your public IPv4
* Edit config file located at `~/.nym/nym-nodes/<ID>/config/config.toml` correct IP - it's the field under the header `[host]`, called `public_ips = [<PUBLIC_IPS>,]`
* Add your new location field `location = <LOCATION>`, (formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided).
###### 5. Restore `clients.sqlite` database
- Install `sqlite3`
```sh
apt install sqlite3
```
- Open sqlite CLI shell inside `clients.sqlite` database
```sh
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
```
- Restore your backup called `clients_backup.sqlite`
```sh
.restore .nym/nym-nodes/default-nym-node/data/clients_backup.sqlite
```
- Exit sqlite CLI shell
```sh
.exit
```
- Check integrity
```sh
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite "PRAGMA integrity_check;"
```
The result should return `ok` if the database is intact.
###### 6. Test & validate your setup
* Dry run the node and see if everything works.
* Try to run the node and see if everything works.
* Setup the [systemd](nym-node/configuration.mdx#systemd) automation (don't forget to add the [terms and conditions flag](nym-node/setup.mdx#terms--conditions)) to `ExecStart` command, reload the daemon and run the service.
###### 4. Change the node smart contract info via the wallet interface
@@ -358,71 +313,21 @@ mkdir ~/.nym
#in case no `nym-node` was initialized previously
mkdir ~/.nym/nym-nodes
```
###### 2. Backup `clients.sqlite` database
- Stop your node
```sh
service nym-node stop
```
- Install `sqlite3`
```sh
apt install sqlite3
```
- Open sqlite CLI shell inside `clients.sqlite` database
```sh
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
```
- Create backup called `clients_backup.sqlite`
```sh
.backup .nym/nym-nodes/default-nym-node/data/clients_backup.sqlite
```
- Exit sqlite CLI shell
```sh
.exit
```
###### 3. Move the node data and keys to the new machine
###### 2. Move the node data and keys to the new machine
* Open your **local terminal** (as that one's ssh key is authorized in both of the VPS) and run:
```sh
scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes <TARGET_USER_NAME>@<TARGET_HOST_ADDRESS>:~/.nym/nym-nodes/
```
###### 4. Open new/target VPS terminal and configure the node
###### 3. Open new/target VPS terminal and configure the node
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP - it's the one under the header `[host]`, called `public_ips = [<PUBLIC_IPS>,]` and add your new location (field `location = <LOCATION>`, formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided). You can see your IP by running a command `echo "$(curl -4 https://ifconfig.me)"`.
* Try to run the node and see if everything works.
###### 5. Restore `clients.sqlite` database
- Install `sqlite3`
```sh
apt install sqlite3
```
- Open sqlite CLI shell inside `clients.sqlite` database
```sh
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
```
- Restore your backup called `clients_backup.sqlite`
```sh
.restore .nym/nym-nodes/default-nym-node/data/clients_backup.sqlite
```
- Exit sqlite CLI shell
```sh
.exit
```
- Check integrity
```sh
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite "PRAGMA integrity_check;"
```
The result should return `ok` if the database is intact.
###### 6. Test & validate your setup
* Dry run the node and see if everything works.
* Setup the [systemd](nym-node/configuration.mdx#systemd) automation (don't forget to add the [terms and conditions flag](nym-node/setup.mdx#terms--conditions)) to `ExecStart` command, reload the daemon and run the service.
* If you want to use the exact same service config file, you can also copy it from one VPS to another following the same logic by opening your **local terminal** (as that one's ssh key is authorized in both of the VPS) and running:
* Setup the [systemd](nym-node/configuration.mdx#systemd) automation (don't forget to add the [terms and conditions flag](nym-node/setup.mdx#terms--conditions)) to `ExecStart` command, reload the daemon and run the service. If you want to use the exact same service config file, you can also copy it from one VPS to another following the same logic by opening your **local terminal** (as that one's ssh key is authorized in both of the VPS) and running:
```sh
scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:/etc/systemd/system/nym-node.service <TARGET_USER_NAME>@<TARGET_HOST_ADDRESS>:/etc/systemd/system/nym-node.service
```
@@ -20,12 +20,12 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
```sh
nym-node
Binary Name: nym-node
Build Timestamp: 2025-02-04T09:35:42.399220545Z
Build Version: 1.4.0
Commit SHA: 4c2bf3642e8eec0d55c7753e14429d73ac2d0e99
Commit Date: 2025-02-04T10:29:48.000000000+01:00
Build Timestamp: 2025-01-16T11:54:17.079662337Z
Build Version: 1.3.1
Commit SHA: 5ab164d229f85bd2dd27ec6e38292c281df2f678
Commit Date: 2025-01-16T12:51:53.000000000+01:00
Commit Branch: HEAD
rustc Version: 1.84.1
rustc Version: 1.84.0
rustc Channel: stable
cargo Profile: release
```
@@ -39,35 +39,21 @@ cargo Profile: release
<Callout type="info">
From `nym-node v1.3.0` operators can choose multiple functionalities for their `nym-node` binary (flagged as `--mode`).
**However, the clients are yet to be developed to be able to make a proper selection for multi-mode nodes and therefore we ask operators to assign only one functionality to `--mode` option at a time. Please chose out of: `mixnode` or `entry-gateway` or `exit-gateway`. Chosing multiple at once will make your node non-routable!**
**However, the clients are yet to be developed to be able to make a proper selection for multi-mode nodes and therefore we ask operators to sign only one functionality to `--mode` option at a time. Please chose out of: `mixnode` or `entry-gateway` or `exit-gateway`.**
We are developing a design where operators can enable multiple modes, and let the Nym API to position the node according the network's needs in the beginning of each epoch.
</Callout>
### Mixnet Routing
Nym Node has three functionalities in the network: `entry-gateway`, `mixnode` and `exit-gateway`.
***Mixnet mode (5-hop) is the full anonymising option of NymVPN. Read more about the Mixnet architecture [here](http://localhost:3000/docs/network/architecture)***
- **Entry Gateway (`--mode entry-gateway`)**: A the node to which local client connects. It checks the bandwidth allowance, using [zk-nyms](../../../network/cryptography/zk-nym) and either sends [Sphinx packets](../../../network/cryptography/sphinx) through the mixnet or directly to Exit Gateway in case of dVPN (2-hop) routing. This node also recieves replies and sends them back to users local client.
Nym Node has three functionalities in the Mixnet: `entry-gateway`, `mixnode` and `exit-gateway`. These are selected with a flag `--mode <MODE>` alongside `nym-node` command `run` .
- **Entry Gateway (`--mode entry-gateway`)**: A node to which clients connect. It checks the bandwidth allowance, using [zk-nyms](../../../network/cryptography/zk-nym) and either sends [Sphinx packets](../../../network/cryptography/sphinx) through the mixnet or directly to Exit Gateway in case of dVPN (2-hop) routing. This node also receives replies and sends them back to users local client.
- **Mixnode (`--mode mixnode`)**: Nodes organized in three layers, randomly selected every epoch (60 minutes), mixing Sphinx packets, adding a slight latency to defend users agains time correlation attacks and sending them further to the next layer or to the Exit Gateway
- **Mixnode (`--mode mixnode`)**: Nodes organized in three layers, randmoly selected every epoch (60 minutes), mixing Sphinx packets, adding a slight latency to defend users agains time correlation attacks and sending them further to the next layer or to the Exit Gateway
- **Exit Gateway (`--mode exit-gateway`)**: The final node in the mixnet. It puts all packets together and using inbuilt Network requester and IP packet router, it sends traffic to the open internet. This node also recieves replies and sends them back to the user client.
<Callout type="warning">
Exit Gateway is the only mode routing data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run an Exit Gateway, please read our [Community Counsel pages](../../community-counsel/exit-gateway) containing more information and some legal content.
</Callout>
### dVPN Routing
***dVPN routing (2-hop) is the Fast option of NymVPN. It runs a wireguard tunnel inside another wireguard tunnel. dVPN uses 2 Gateway layers (entry and exit) and no Mixnode layers. If an operator announces and [correctly configure](configuration#connectivity-test-and-configuration) their node to route wireguard, the node can be chosen as entry or exit by any client at any time.***
To activate wireguard routing, operators need to add `--wireguard-enabled true` alongside `nym-node` command `run`.
<Callout type="warning">
Wireguard nodes route data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run a node with active wireguard routing, please read our [Community Counsel pages](../../community-counsel/exit-gateway) containing more information and some legal content.
**Wireguard mode has no exit policy right now - we are working on the implementation.**
Exit Gateway is the only node routing data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run an Exit Gateway, please read our [Community Counsel pages](../../community-counsel/exit-gateway) containing more information and some legal content.
</Callout>
Everything essential for each mode exists on `nym-node` by default. For instance, if you run a Mixnode, you'll find that a NR (Network Requester) and IPR (IP Packet Router) addresses exist, but they will be ignored in `mixnode` mode.
@@ -146,7 +146,7 @@ Besides these values, the API is also looking whether the node is bonded in Mixn
**The node score is calculated with this formula:**
<Callout type="info" emoji="📌">
> **active_set_selection_probability = total_stake \* (( config_score \* node_performance ) ^ 20 )**
> **active_set_selection_probability = total_stake \* ( config_score \* node_performance ) ^ 20 )**
</Callout>
Note that the score helps prioritize some nodes over others. If all available nodes have the same score, then the selection is done uniformly at random. By raising the config and performance components to 20, values of these parameters that are below one incur a heavy penalization for the nodes selection chances.
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "1.1.45"
version = "1.1.44"
edition = "2021"
license.workspace = true
@@ -1,62 +1,62 @@
"use client";
'use client'
import * as React from "react";
import { Alert, AlertTitle, Box, CircularProgress, Grid } from "@mui/material";
import { useParams } from "next/navigation";
import { ColumnsType, DetailTable } from "@/app/components/DetailTable";
import { ComponentError } from "@/app/components/ComponentError";
import { ContentCard } from "@/app/components/ContentCard";
import { UptimeChart } from "@/app/components/UptimeChart";
import { NymNodeContextProvider, useNymNodeContext } from "@/app/context/node";
import { useMainContext } from "@/app/context/main";
import { Title } from "@/app/components/Title";
import Paper from "@mui/material/Paper";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow";
import { humanReadableCurrencyToString } from "@/app/utils/currency";
import { DeclaredRole } from "@/app/network-components/nodes/DeclaredRole";
import * as React from 'react'
import { Alert, AlertTitle, Box, CircularProgress, Grid } from '@mui/material'
import { useParams } from 'next/navigation'
import { ColumnsType, DetailTable } from '@/app/components/DetailTable'
import { ComponentError } from '@/app/components/ComponentError'
import { ContentCard } from '@/app/components/ContentCard'
import { UptimeChart } from '@/app/components/UptimeChart'
import {
NodeDelegationsTable,
VestingDelegationWarning,
} from "@/app/network-components/nodes/[id]/NodeDelegationsTable";
NymNodeContextProvider,
useNymNodeContext,
} from '@/app/context/node'
import { useMainContext } from '@/app/context/main'
import { Title } from '@/app/components/Title'
import Paper from "@mui/material/Paper";
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableRow from '@mui/material/TableRow';
import {humanReadableCurrencyToString} from "@/app/utils/currency";
import {DeclaredRole} from "@/app/network-components/nodes/DeclaredRole";
import {NodeDelegationsTable, VestingDelegationWarning} from "@/app/network-components/nodes/[id]/NodeDelegationsTable";
const columns: ColumnsType[] = [
{
field: "identity_key",
title: "Identity Key",
headerAlign: "left",
field: 'identity_key',
title: 'Identity Key',
headerAlign: 'left',
width: 230,
},
{
field: "bond",
title: "Bond",
headerAlign: "left",
field: 'bond',
title: 'Bond',
headerAlign: 'left',
},
{
field: "host",
title: "IP",
headerAlign: "left",
field: 'host',
title: 'IP',
headerAlign: 'left',
width: 99,
},
{
field: "location",
title: "Location",
headerAlign: "left",
field: 'location',
title: 'Location',
headerAlign: 'left',
},
{
field: "owner",
title: "Owner",
headerAlign: "left",
field: 'owner',
title: 'Owner',
headerAlign: 'left',
},
{
field: "version",
title: "Version",
headerAlign: "left",
field: 'version',
title: 'Version',
headerAlign: 'left',
},
];
]
interface NodeEnrichedRowType {
node_id: number;
@@ -71,33 +71,28 @@ interface NodeEnrichedRowType {
function nodeEnrichedToGridRow(node: any): NodeEnrichedRowType {
return {
node_id: node.node_id,
owner: node.bond_information?.owner || "",
identity_key: node.bond_information?.node?.identity_key || "",
location: node.location?.country_name || "",
owner: node.bond_information?.owner || '',
identity_key: node.bond_information?.node?.identity_key || '',
location: node.location?.country_name || '',
bond: node.bond_information?.original_pledge.amount || 0, // TODO: format
host: node.bond_information?.node?.host || "",
version: node.description?.build_information?.build_version || "",
host: node.bond_information?.node?.host || '',
version: node.description?.build_information?.build_version || '',
};
}
/**
* Shows nym node details
*/
const PageNymNodeDetailsWithState = ({
selectedNymNode,
}: {
selectedNymNode?: any;
selectedNymNode?: any
}) => {
const { uptimeHistory } = useNymNodeContext();
const enrichedData = React.useMemo(
() => (selectedNymNode ? [nodeEnrichedToGridRow(selectedNymNode)] : []),
[]
);
const { uptimeHistory } = useNymNodeContext()
const enrichedData = React.useMemo(() => selectedNymNode ? [nodeEnrichedToGridRow(selectedNymNode)] : [], []);
const hasVestingContractDelegations = React.useMemo(
() => selectedNymNode?.delegations?.filter((d: any) => d.proxy)?.length,
[selectedNymNode]
);
const hasVestingContractDelegations = React.useMemo(() => selectedNymNode?.delegations?.filter((d: any) => d.proxy)?.length, [selectedNymNode]);
return (
<Box component="main">
@@ -114,128 +109,81 @@ const PageNymNodeDetailsWithState = ({
</Grid>
<Grid container mt={2} spacing={2}>
{selectedNymNode.rewarding_details && (
<Grid item xs={12} md={4}>
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell colSpan={2}>Delegations and Rewards</TableCell>
</TableRow>
<TableRow>
<TableCell
component="th"
scope="row"
sx={{ color: "inherit" }}
>
<strong>Operator</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString({
amount:
selectedNymNode.rewarding_details.operator.split(
"."
)[0],
denom: "unym",
})}
</TableCell>
</TableRow>
<TableRow>
<TableCell
component="th"
scope="row"
sx={{ color: "inherit" }}
>
<strong>
{hasVestingContractDelegations ? (
<VestingDelegationWarning plural={true}>
Delegates&nbsp;(
{
selectedNymNode.rewarding_details
.unique_delegations
}{" "}
delegates)
</VestingDelegationWarning>
) : (
<>
Delegates&nbsp;(
{
selectedNymNode.rewarding_details
.unique_delegations
}{" "}
delegates)
</>
)}
</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString({
amount:
selectedNymNode.rewarding_details.delegates.split(
"."
)[0],
denom: "unym",
})}
</TableCell>
</TableRow>
<TableRow>
<TableCell
component="th"
scope="row"
sx={{ color: "inherit" }}
>
<strong>Profit margin</strong>
</TableCell>
<TableCell align="right">
{selectedNymNode.rewarding_details.cost_params
.profit_margin_percent * 100}
%
</TableCell>
</TableRow>
<TableRow>
<TableCell
component="th"
scope="row"
sx={{ color: "inherit" }}
>
<strong>Operator costs</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString(
selectedNymNode.rewarding_details.cost_params
.interval_operating_cost
)}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>
)}
{selectedNymNode.rewarding_details &&
<Grid item xs={12} md={4}>
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell colSpan={2}>
Delegations and Rewards
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
<strong>Operator</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString({ amount : selectedNymNode.rewarding_details.operator.split('.')[0], denom: "unym" })}
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
<strong>
{hasVestingContractDelegations ?
<VestingDelegationWarning plural={true}>
Delegates&nbsp;({selectedNymNode.rewarding_details.unique_delegations} delegates)
</VestingDelegationWarning> :
<>Delegates&nbsp;({selectedNymNode.rewarding_details.unique_delegations} delegates)</>
}
</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString({ amount : selectedNymNode.rewarding_details.delegates.split('.')[0], denom: "unym" })}
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
<strong>Profit margin</strong>
</TableCell>
<TableCell align="right">
{selectedNymNode.rewarding_details.cost_params.profit_margin_percent * 100}%
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
<strong>Operator costs</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString(selectedNymNode.rewarding_details.cost_params.interval_operating_cost)}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>}
{selectedNymNode.description?.declared_role && (
<Grid item xs={12} md={4}>
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell colSpan={2}>Node roles</TableCell>
</TableRow>
<TableRow>
<TableCell>Self declared roles</TableCell>
<TableCell>
<DeclaredRole
declared_role={
selectedNymNode.description?.declared_role
}
/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>
)}
{selectedNymNode.description?.declared_role && <Grid item xs={12} md={4}>
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell colSpan={2}>
Node roles
</TableCell>
</TableRow>
<TableRow>
<TableCell>
Self declared roles
</TableCell>
<TableCell>
<DeclaredRole declared_role={selectedNymNode.description?.declared_role}/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>}
</Grid>
<Grid container spacing={2} mt={2}>
@@ -257,41 +205,42 @@ const PageNymNodeDetailsWithState = ({
</Grid>
<Box mt={2}>
<NodeDelegationsTable node={selectedNymNode} />
<NodeDelegationsTable node={selectedNymNode}/>
</Box>
</Box>
);
};
)
}
/**
* Guard component to handle loading and not found states
*/
const PageNymNodeDetailGuard = () => {
const [selectedNode, setSelectedNode] = React.useState<any>();
const [selectedNode, setSelectedNode] = React.useState<any>()
const [isLoading, setLoading] = React.useState<boolean>(true);
const [error, setError] = React.useState<string>();
const { fetchNodeById } = useMainContext();
const { id } = useParams();
const { fetchNodeById } = useMainContext()
const { id } = useParams()
React.useEffect(() => {
setSelectedNode(undefined);
setLoading(true);
(async () => {
if (typeof id === "string") {
if(typeof(id) === "string") {
try {
const res = await fetchNodeById(Number.parseInt(id));
setSelectedNode(res);
} catch (e: any) {
} catch(e: any) {
setError(e.message);
} finally {
}
finally {
setLoading(false);
}
}
})();
}, [id, fetchNodeById]);
}, [id])
if (isLoading) {
return <CircularProgress />;
return <CircularProgress />
}
// loaded, but not found
@@ -299,38 +248,31 @@ const PageNymNodeDetailGuard = () => {
return (
<Alert severity="warning">
<AlertTitle>Nym node not found</AlertTitle>
Sorry, we could not find a node with id <code>{id || ""}</code>
Sorry, we could not find a node with id <code>{id || ''}</code>
</Alert>
);
)
}
if (!selectedNode) {
return (
<Alert severity="warning">
<AlertTitle>Legacy Node</AlertTitle>
Unable to load details for the selected Nym node.
</Alert>
);
}
return <PageNymNodeDetailsWithState selectedNymNode={selectedNode} />;
};
return <PageNymNodeDetailsWithState selectedNymNode={selectedNode} />
}
/**
* Wrapper component that adds the node content based on the `id` in the address URL
*/
const PageNymNodeDetail = () => {
const { id } = useParams();
const { id } = useParams()
if (!id || typeof id !== "string") {
return <Alert severity="error">Oh no! Could not find that node</Alert>;
if (!id || typeof id !== 'string') {
return (
<Alert severity="error">Oh no! Could not find that node</Alert>
)
}
return (
<NymNodeContextProvider nymNodeId={id}>
<PageNymNodeDetailGuard />
</NymNodeContextProvider>
);
};
)
}
export default PageNymNodeDetail;
export default PageNymNodeDetail
+1 -11
View File
@@ -1,14 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
async redirects() {
return [
{
source: '/network-components/mixnode/:id', // Match the old URL
destination: '/network-components/nodes/:id', // Redirect to the new URL
permanent: true,
},
];
},
};
const nextConfig = {};
export default nextConfig;
+1 -2
View File
@@ -28,8 +28,7 @@
"../assets/*"
]
},
"moduleResolution": "node",
"target": "ES2017"
"moduleResolution": "node"
},
"include": [
"next-env.d.ts",
@@ -7,7 +7,7 @@ use tracing::error;
#[derive(Debug, Error)]
pub enum BandwidthError {
#[error("Provided bandwidth credential asks for more bandwidth than it is supported to add at once (credential value: {0}, supported: {s}). Try to split it before attempting again", s = i64::MAX)]
#[error("Provided bandwidth credential asks for more bandwidth than it is supported to add at once (credential value: {0}, supported: {}). Try to split it before attempting again", i64::MAX)]
UnsupportedBandwidthValue(u64),
#[error("failed to parse the bandwidth voucher value: {source}")]
+3 -1
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.49"
version = "1.1.48"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
@@ -16,6 +16,7 @@ async-trait = { workspace = true }
bs58 = { workspace = true }
bip39 = { workspace = true }
bincode.workspace = true
bloomfilter = { workspace = true }
cfg-if = { workspace = true }
clap = { workspace = true, features = ["cargo", "derive", "env"] }
console-subscriber = { workspace = true, optional = true } # validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable"
@@ -83,6 +84,7 @@ tracing = { workspace = true }
#ephemera = { path = "../ephemera" }
nym-bandwidth-controller = { path = "../common/bandwidth-controller" }
nym-ecash-contract-common = { path = "../common/cosmwasm-smart-contracts/ecash-contract" }
nym-ecash-double-spending = { path = "../common/ecash-double-spending" }
nym-ecash-time = { path = "../common/ecash-time", features = ["expiration"] }
nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" }
nym-compact-ecash = { path = "../common/nym_offline_compact_ecash" }
@@ -1,7 +0,0 @@
/*
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: GPL-3.0-only
*/
DROP TABLE partial_bloomfilter;
DROP TABLE bloomfilter_parameters;
-1
View File
@@ -28,7 +28,6 @@ nym-serde-helpers = { path = "../../common/serde-helpers", features = ["bs58", "
nym-credentials-interface = { path = "../../common/credentials-interface" }
nym-crypto = { path = "../../common/crypto", features = ["serde", "asymmetric"] }
nym-config = { path = "../../common/config" }
nym-ecash-time = { path = "../../common/ecash-time" }
nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", features = ["naive_float"] }
-6
View File
@@ -12,12 +12,6 @@ pub mod models;
pub mod nym_nodes;
pub mod pagination;
// The response type we fetch from the network details endpoint.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NymNetworkDetailsResponse {
pub network: nym_config::defaults::NymNetworkDetails,
}
pub trait Deprecatable {
fn deprecate(self) -> Deprecated<Self>
where
+21 -9
View File
@@ -6,7 +6,6 @@ use crate::ecash::state::EcashState;
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
use crate::support::http::state::AppState;
use axum::extract::State;
use axum::http::StatusCode;
use axum::{Json, Router};
use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
use nym_api_requests::ecash::models::{
@@ -90,6 +89,12 @@ async fn verify_ticket(
});
}
// check the bloomfilter for obvious double-spending so that we wouldn't need to waste time on crypto verification
// TODO: when blacklisting is implemented, this should get removed
if state.check_bloomfilter(sn).await {
return reject_ticket(EcashTicketVerificationRejection::ReplayedTicket);
}
// actual double spend detection with storage
if let Some(previous_payment) = state.get_ticket_data_by_serial_number(sn).await? {
match nym_compact_ecash::identify::identify(
@@ -122,17 +127,27 @@ async fn verify_ticket(
return reject_ticket(EcashTicketVerificationRejection::InvalidTicket);
}
// store credential and check whether it wasn't already there (due to a parallel request)
let was_inserted = state
.store_verified_ticket(credential_data, gateway_cosmos_addr)
// finally get EXCLUSIVE lock on the bloomfilter, check if for the final time and insert the SN
let was_present = state
.update_bloomfilter(sn, spend_date, today_ecash)
.await?;
if !was_inserted {
if was_present {
return reject_ticket(EcashTicketVerificationRejection::ReplayedTicket);
}
//store credential
state
.store_verified_ticket(credential_data, gateway_cosmos_addr)
.await?;
Ok(Json(EcashTicketVerificationResponse { verified: Ok(()) }))
}
// // for particular SN returns what gateway has submitted it and whether it has been verified correctly
// async fn credential_status() -> ! {
// todo!()
// }
#[utoipa::path(
tag = "Ecash",
post,
@@ -225,8 +240,5 @@ async fn batch_redeem_tickets(
)]
#[deprecated]
async fn double_spending_filter_v1() -> AxumResult<Json<SpentCredentialsResponse>> {
AxumResult::Err(AxumErrorResponse::new(
"permanently restricted",
StatusCode::GONE,
))
AxumResult::Err(AxumErrorResponse::internal_msg("permanently restricted"))
}
+2
View File
@@ -0,0 +1,2 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
-89
View File
@@ -1,89 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::ecash::storage::EcashStorageExt;
use crate::node_status_api::models::NymApiStorageError;
use crate::support::config::Config;
use crate::support::storage::NymApiStorage;
use nym_ecash_time::ecash_today_date;
use nym_task::TaskClient;
use std::time::Duration;
use time::Date;
use tokio::task::JoinHandle;
use tracing::{debug, error, trace};
/// Task responsible for clearing out the database from stale ecash data,
/// such as verified tickets or issued partial ticketbooks.
pub struct EcashBackgroundStateCleaner {
run_interval: Duration,
issued_ticketbooks_retention_period_days: u32,
verified_tickets_retention_period_days: u32,
storage: NymApiStorage,
task_client: TaskClient,
}
impl EcashBackgroundStateCleaner {
pub fn new(global_config: &Config, storage: NymApiStorage, task_client: TaskClient) -> Self {
EcashBackgroundStateCleaner {
run_interval: global_config.ecash_signer.debug.stale_data_cleaner_interval,
issued_ticketbooks_retention_period_days: global_config
.ecash_signer
.debug
.issued_ticketbooks_retention_period_days,
verified_tickets_retention_period_days: global_config
.ecash_signer
.debug
.verified_tickets_retention_period_days,
storage,
task_client,
}
}
fn ticketbook_retention_cutoff(&self) -> Date {
ecash_today_date()
- time::Duration::days(self.issued_ticketbooks_retention_period_days as i64)
}
fn verified_tickets_retention_cutoff(&self) -> Date {
ecash_today_date()
- time::Duration::days(self.verified_tickets_retention_period_days as i64)
}
async fn clean_stale_data(&self) -> Result<(), NymApiStorageError> {
// 1. remove old verified tickets
self.storage
.remove_expired_verified_tickets(self.verified_tickets_retention_cutoff())
.await?;
// 2. remove old issued partial ticketbooks
self.storage
.remove_old_issued_ticketbooks(self.ticketbook_retention_cutoff())
.await?;
Ok(())
}
async fn run(&mut self) {
let mut ticker = tokio::time::interval(self.run_interval);
loop {
tokio::select! {
_ = self.task_client.recv() => {
trace!("EcashBackgroundStateCleaner: Received shutdown");
break;
}
_ = ticker.tick() => {
if let Err(err) = self.clean_stale_data().await {
error!("failed to clear out stale data: {err}")
}
}
}
}
debug!("EcashBackgroundStateCleaner: exiting");
}
pub(crate) fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move { self.run().await })
}
}
+93 -1
View File
@@ -2,14 +2,106 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::ecash::error::EcashError;
use crate::ecash::state::local::TicketDoubleSpendingFilter;
use crate::ecash::storage::EcashStorageExt;
use crate::support::storage::NymApiStorage;
use futures::{stream, StreamExt};
use nym_compact_ecash::constants;
use nym_config::defaults::BloomfilterParameters;
use nym_dkg::Threshold;
use nym_ecash_double_spending::{DoubleSpendingFilter, DoubleSpendingFilterBuilder};
use nym_ecash_time::{cred_exp_date, ecash_today};
use nym_validator_client::EcashApiClient;
use std::future::Future;
use time::ext::NumericalDuration;
use time::Date;
use tokio::sync::Mutex;
use tracing::warn;
use tracing::{debug, error, info, warn};
// attempt to completely rebuild the bloomfilter data for given day
async fn try_rebuild_today_bloomfilter(
today: Date,
params: BloomfilterParameters,
storage: &NymApiStorage,
) -> Result<DoubleSpendingFilter, EcashError> {
info!("rebuilding bloomfilter for {today}");
let tickets = storage.get_all_spent_tickets_on(today).await?;
debug!(
"there are {} tickets to insert into the filter",
tickets.len()
);
let mut filter = DoubleSpendingFilter::new_empty(params);
for ticket in tickets {
filter.set(&ticket.serial_number)
}
Ok(filter)
}
pub(crate) async fn prepare_partial_bloomfilter_builder(
storage: &NymApiStorage,
params: BloomfilterParameters,
params_id: i64,
start: Date,
days: i64,
) -> Result<DoubleSpendingFilterBuilder, EcashError> {
info!(
"attempting to rebuild partial bloomfilter starting at {start} which includes {days} days"
);
let mut filter_builder = DoubleSpendingFilter::builder(params);
for i in 0..days {
let date = start - i.days();
let Some(bitmap) = storage
.try_load_partial_bloomfilter_bitmap(date, params_id)
.await?
else {
warn!("missing double spending bloomfilter bitmap for {date} (if this API hasn't been running for at least {days} day(s) since 'ecash'-based zk-nyms were introduced this is expected)");
continue;
};
if !filter_builder.add_bytes(&bitmap) {
error!(
"failed to add bitmap from {date} to the global bloomfilter. it may be malformed!"
);
}
}
Ok(filter_builder)
}
pub(super) async fn try_rebuild_bloomfilter(
storage: &NymApiStorage,
) -> Result<TicketDoubleSpendingFilter, EcashError> {
info!("attempting to rebuild the double spending bloomfilter...");
let today = ecash_today().date();
let (params_id, params) = storage.get_double_spending_filter_params().await?;
info!("will use the following parameters: {params:?}");
// we're never going to have persisted data for 'today'. we need to rebuild it from scratch
let today_filter = try_rebuild_today_bloomfilter(today, params, storage).await?;
info!("attempting to rebuild the global filter");
let mut global_filter = prepare_partial_bloomfilter_builder(
storage,
params,
params_id,
today.previous_day().unwrap(),
constants::CRED_VALIDITY_PERIOD_DAYS as i64 - 1,
)
.await?;
if !global_filter.add_bytes(&today_filter.dump_bitmap()) {
error!("failed to add bitmap from {today} to the global bloomfilter. it may be malformed!");
}
Ok(TicketDoubleSpendingFilter::new(
today,
params_id,
global_filter.build(),
today_filter,
))
}
pub(crate) fn ensure_sane_expiration_date(expiration_date: Date) -> Result<(), EcashError> {
let today = ecash_today();
+70
View File
@@ -9,7 +9,9 @@ use crate::ecash::helpers::{
use crate::ecash::keys::KeyPair;
use crate::ecash::storage::models::IssuedHash;
use nym_api_requests::ecash::models::{CommitedDeposit, DepositId};
use nym_config::defaults::BloomfilterParameters;
use nym_crypto::asymmetric::identity;
use nym_ecash_double_spending::DoubleSpendingFilter;
use nym_ticketbooks_merkle::{
IssuedTicketbook, IssuedTicketbooksFullMerkleProof, IssuedTicketbooksMerkleTree, MerkleLeaf,
};
@@ -19,6 +21,69 @@ use time::Date;
use tokio::sync::RwLock;
use tracing::error;
pub(crate) struct TicketDoubleSpendingFilter {
built_on: Date,
params_id: i64,
today_filter: DoubleSpendingFilter,
global_filter: DoubleSpendingFilter,
}
impl TicketDoubleSpendingFilter {
pub(crate) fn new(
built_on: Date,
params_id: i64,
global_filter: DoubleSpendingFilter,
today_filter: DoubleSpendingFilter,
) -> TicketDoubleSpendingFilter {
TicketDoubleSpendingFilter {
built_on,
params_id,
today_filter,
global_filter,
}
}
pub(crate) fn built_on(&self) -> Date {
self.built_on
}
pub(crate) fn params(&self) -> BloomfilterParameters {
self.today_filter.params()
}
pub(crate) fn params_id(&self) -> i64 {
self.params_id
}
pub(crate) fn check(&self, sn: &Vec<u8>) -> bool {
self.global_filter.check(sn)
}
/// Returns boolean to indicate if the entry was already present
pub(crate) fn insert_both(&mut self, sn: &Vec<u8>) -> bool {
self.today_filter.set(sn);
self.insert_global_only(sn)
}
/// Returns boolean to indicate if the entry was already present
pub(crate) fn insert_global_only(&mut self, sn: &Vec<u8>) -> bool {
let existed = self.global_filter.check(sn);
self.global_filter.set(sn);
existed
}
pub(crate) fn export_today_bitmap(&self) -> Vec<u8> {
self.today_filter.dump_bitmap()
}
pub(crate) fn advance_day(&mut self, date: Date, new_global: DoubleSpendingFilter) {
self.built_on = date;
self.global_filter = new_global;
self.today_filter.reset();
}
}
#[derive(Default)]
pub(crate) struct DailyMerkleTree {
pub(crate) merkle_tree: IssuedTicketbooksMerkleTree,
@@ -142,6 +207,9 @@ pub(crate) struct LocalEcashState {
pub(crate) partial_expiration_date_signatures:
CachedImmutableItems<Date, IssuedExpirationDateSignatures>,
// the actual, up to date, bloomfilter
pub(crate) double_spending_filter: Arc<RwLock<TicketDoubleSpendingFilter>>,
// merkle trees for ticketbooks issued for particular expiration dates
pub(crate) issued_merkle_trees: Arc<RwLock<HashMap<Date, DailyMerkleTree>>>,
}
@@ -150,6 +218,7 @@ impl LocalEcashState {
pub(crate) fn new(
ecash_keypair: KeyPair,
identity_keypair: identity::KeyPair,
double_spending_filter: TicketDoubleSpendingFilter,
explicitly_disabled: bool,
) -> Self {
LocalEcashState {
@@ -159,6 +228,7 @@ impl LocalEcashState {
active_signer: Default::default(),
partial_coin_index_signatures: Default::default(),
partial_expiration_date_signatures: Default::default(),
double_spending_filter: Arc::new(RwLock::new(double_spending_filter)),
issued_merkle_trees: Arc::new(Default::default()),
}
}
+172 -51
View File
@@ -8,9 +8,11 @@ use crate::ecash::error::{EcashError, RedemptionError, Result};
use crate::ecash::helpers::{IssuedCoinIndicesSignatures, IssuedExpirationDateSignatures};
use crate::ecash::keys::KeyPair;
use crate::ecash::state::auxiliary::AuxiliaryEcashState;
use crate::ecash::state::cleaner::EcashBackgroundStateCleaner;
use crate::ecash::state::global::GlobalEcachState;
use crate::ecash::state::helpers::{ensure_sane_expiration_date, query_all_threshold_apis};
use crate::ecash::state::helpers::{
ensure_sane_expiration_date, prepare_partial_bloomfilter_builder, query_all_threshold_apis,
try_rebuild_bloomfilter,
};
use crate::ecash::state::local::{DailyMerkleTree, LocalEcashState};
use crate::ecash::storage::models::{SerialNumberWrapper, TicketProvider};
use crate::ecash::storage::EcashStorageExt;
@@ -31,30 +33,31 @@ use nym_compact_ecash::scheme::expiration_date_signatures::{
aggregate_annotated_expiration_signatures, ExpirationDateSignatureShare,
};
use nym_compact_ecash::{
scheme::expiration_date_signatures::sign_expiration_date, BlindedSignature, Bytable,
constants, scheme::expiration_date_signatures::sign_expiration_date, BlindedSignature, Bytable,
SecretKeyAuth, VerificationKeyAuth,
};
use nym_config::defaults::BloomfilterParameters;
use nym_credentials::ecash::utils::EcashTime;
use nym_credentials::{aggregate_verification_keys, CredentialSpendingData};
use nym_crypto::asymmetric::identity;
use nym_ecash_contract_common::deposit::{Deposit, DepositId};
use nym_ecash_contract_common::msg::ExecuteMsg;
use nym_ecash_contract_common::redeem_credential::BATCH_REDEMPTION_PROPOSAL_TITLE;
use nym_ecash_time::{ecash_default_expiration_date, ecash_today_date};
use nym_task::TaskClient;
use nym_ecash_double_spending::DoubleSpendingFilter;
use nym_ecash_time::{cred_exp_date, ecash_today_date};
use nym_ticketbooks_merkle::{IssuedTicketbook, IssuedTicketbooksFullMerkleProof, MerkleLeaf};
use nym_validator_client::nyxd::AccountId;
use nym_validator_client::EcashApiClient;
use rand::{thread_rng, RngCore};
use std::collections::HashMap;
use std::ops::Deref;
use time::ext::NumericalDuration;
use time::{Date, OffsetDateTime};
use tokio::sync::{RwLockReadGuard, RwLockWriteGuard};
use tokio::task::JoinHandle;
use tracing::{debug, error, info, warn};
pub(crate) mod auxiliary;
mod cleaner;
pub(crate) mod bloom;
pub(crate) mod global;
mod helpers;
pub(crate) mod local;
@@ -81,24 +84,10 @@ impl EcashStateConfig {
}
}
#[derive(Default)]
pub(crate) enum BackgroundCleanerState {
WaitingStartup(EcashBackgroundStateCleaner),
Running {
_handle: JoinHandle<()>,
},
// an ephemeral state so that we could swap between the other two
#[default]
Invalid,
}
pub struct EcashState {
// additional global config parameters
pub(crate) config: EcashStateConfig,
pub(crate) background_cleaner_state: BackgroundCleanerState,
// state global to the system, like aggregated keys, addresses, etc.
pub(crate) global: GlobalEcachState,
@@ -110,8 +99,7 @@ pub struct EcashState {
}
impl EcashState {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new<C, D>(
pub(crate) async fn new<C, D>(
global_config: &Config,
contract_address: AccountId,
client: C,
@@ -119,38 +107,24 @@ impl EcashState {
key_pair: KeyPair,
comm_channel: D,
storage: NymApiStorage,
task_client: TaskClient,
) -> Self
) -> Result<Self>
where
C: LocalClient + Send + Sync + 'static,
D: APICommunicationChannel + Send + Sync + 'static,
{
Self {
let double_spending_filter = try_rebuild_bloomfilter(&storage).await?;
Ok(Self {
config: EcashStateConfig::new(global_config),
background_cleaner_state: BackgroundCleanerState::WaitingStartup(
EcashBackgroundStateCleaner::new(global_config, storage.clone(), task_client),
),
global: GlobalEcachState::new(contract_address),
local: LocalEcashState::new(
key_pair,
identity_keypair,
double_spending_filter,
!global_config.ecash_signer.enabled,
),
aux: AuxiliaryEcashState::new(client, comm_channel, storage),
}
}
pub(crate) fn spawn_background_cleaner(&mut self) {
match std::mem::take(&mut self.background_cleaner_state) {
BackgroundCleanerState::WaitingStartup(cleaner) => {
self.background_cleaner_state = BackgroundCleanerState::Running {
_handle: cleaner.start(),
}
}
// whilst we normally don't want to panic, this one would only occur at startup,
// if some logical invariants got broken (which have to be fixed in code anyway)
_ => panic!("attempted to spawn background cleaner more than once"),
}
})
}
/// Ensures that this nym-api is one of ecash signers for the current epoch
@@ -792,6 +766,12 @@ impl EcashState {
// remove the in-memory merkle tree
map.remove(&date);
}
// remove data from the storage
self.aux
.storage
.remove_old_issued_ticketbooks(cutoff)
.await?;
}
Ok(())
@@ -827,12 +807,6 @@ impl EcashState {
return Err(EcashError::ExpirationDateTooEarly);
}
if challenge.expiration_date > ecash_default_expiration_date() {
// we wouldn't have issued any credentials for that expiration date so no point
// in attempting to construct an ultimately empty response
return Err(EcashError::ExpirationDateTooLate);
}
let merkle_proof = self
.get_merkle_proof(challenge.expiration_date, &challenge.deposits)
.await?;
@@ -889,17 +863,18 @@ impl EcashState {
})
}
/// Returns a boolean to indicate whether the ticket has actually been inserted
pub async fn store_verified_ticket(
&self,
ticket_data: &CredentialSpendingData,
gateway_addr: &AccountId,
) -> Result<bool> {
) -> Result<()> {
self.aux
.storage
.store_verified_ticket(ticket_data, gateway_addr)
.await
.map_err(Into::into)
// TODO UNIMPLEMENTED: we should probably also be removing old tickets here
}
pub async fn get_ticket_provider(
@@ -946,4 +921,150 @@ impl EcashState {
.await
.map_err(Into::into)
}
pub async fn check_bloomfilter(&self, serial_number: &Vec<u8>) -> bool {
self.local
.double_spending_filter
.read()
.await
.check(serial_number)
}
async fn update_archived_partial_bloomfilter(
&self,
date: Date,
params_id: i64,
params: BloomfilterParameters,
sn: &Vec<u8>,
) -> Result<(), EcashError> {
let mut filter = match self
.aux
.storage
.try_load_partial_bloomfilter_bitmap(date, params_id)
.await?
{
Some(bitmap) => DoubleSpendingFilter::from_bytes(params, &bitmap),
None => {
warn!("no existing partial bloomfilter for {date}");
DoubleSpendingFilter::new_empty(params)
}
};
filter.set(sn);
let updated_bitmap = filter.dump_bitmap();
self.aux
.storage
.update_archived_partial_bloomfilter(date, &updated_bitmap)
.await?;
Ok(())
}
/// Attempt to insert the provided serial number into the bloomfilter.
/// Furthermore, attempt to rotate the filter if we have advanced into a next day.
pub async fn update_bloomfilter(
&self,
serial_number: &Vec<u8>,
spending_date: Date,
today: Date,
) -> Result<bool, EcashError> {
let mut guard = self.local.double_spending_filter.write().await;
let filter_date = guard.built_on();
let yesterday = today.previous_day().unwrap();
let params_id = guard.params_id();
let params = guard.params();
// if the filter is up-to-date, we just insert the entry and call it a day
if filter_date == today {
if spending_date == today {
return Ok(guard.insert_both(serial_number));
}
// sanity check because this should NEVER happen,
// but when it inevitably does, we don't want to crash
if spending_date != yesterday {
error!("attempted to insert a ticket with spending date of {spending_date} while it's {today} today!!");
}
// this shouldn't be happening too often, so it's fine to interact with the storage
warn!("updating archived partial bloomfilter for {spending_date}. those logs have to be closely controlled to make sure they're not too frequent");
self.update_archived_partial_bloomfilter(
spending_date,
params_id,
params,
serial_number,
)
.await?;
return Ok(guard.insert_global_only(serial_number));
}
info!("we need to advance our bloomfilter");
let previous_bitmap = guard.export_today_bitmap();
// archive the BF for today's date
self.aux
.storage
.insert_partial_bloomfilter(filter_date, params_id, &previous_bitmap)
.await?;
let new_global_filter = if filter_date == yesterday {
// normal case when we update filter daily
let two_days_ago = yesterday.previous_day().unwrap();
let mut filter_builder = prepare_partial_bloomfilter_builder(
&self.aux.storage,
params,
params_id,
two_days_ago,
constants::CRED_VALIDITY_PERIOD_DAYS as i64 - 2,
)
.await?;
// add the bitmap from 'old today', i.e. yesterday
// (we have it on hand so no point in retrieving it from storage)
filter_builder.add_bytes(&previous_bitmap);
filter_builder.build()
} else {
// initial deployment case when we don't even get tickets daily
prepare_partial_bloomfilter_builder(
&self.aux.storage,
params,
params_id,
yesterday,
constants::CRED_VALIDITY_PERIOD_DAYS as i64 - 1,
)
.await?
.build()
};
guard.advance_day(today, new_global_filter);
// drop guard so other tasks could read the filter already whilst we clean-up the storage
let res = if spending_date == today {
Ok(guard.insert_both(serial_number))
} else {
Ok(guard.insert_global_only(serial_number))
};
drop(guard);
let cutoff = cred_exp_date().ecash_date();
// sanity check:
assert_eq!(
cutoff,
today + (constants::CRED_VALIDITY_PERIOD_DAYS as i64 - 1).days()
);
// remove the data we no longer need to hold, i.e. partial bloomfilters beyond max credential validity
// and the ticket data for those
self.aux
.storage
.remove_old_partial_bloomfilters(cutoff)
.await?;
self.aux
.storage
.remove_expired_verified_tickets(cutoff)
.await?;
res
}
}

Some files were not shown because too many files have changed in this diff Show More