Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3e19b4563 | |||
| 54aef7c242 | |||
| 6c45c9f0b0 | |||
| b5afae0916 | |||
| 988eca857f | |||
| 3c05db2874 | |||
| a8e268f84a | |||
| ac22533ecd | |||
| bdc0b875a4 | |||
| d7b67c1408 | |||
| 606e29ebb0 | |||
| 21e3c1538d | |||
| 0fc7cc657d | |||
| 23a7f01c05 | |||
| 3a21cfa1ab | |||
| 1d2e6d916c | |||
| 4c2bf3642e | |||
| 70e2e32385 | |||
| 68a192daa3 | |||
| d6aacae14e | |||
| 6f00023d09 | |||
| 982ec56874 | |||
| 5dcc1ed6dc | |||
| d62bc0a10b | |||
| d1fb926a2a | |||
| dea69acd49 | |||
| ada2d2247a | |||
| 0159d7c27a | |||
| 882003c08c | |||
| b71a491872 | |||
| 8f48ae08c4 | |||
| 31b9623407 | |||
| 6d90ffdd2c | |||
| 28997c7f97 | |||
| 9550934d1f | |||
| a6c586a33b | |||
| 7c85c1a271 | |||
| 92c8d1b73f | |||
| 554e9ca490 | |||
| 6e6675f7bf | |||
| a7f7ebfbae | |||
| 1aec8be85e | |||
| 4b474dd8ff | |||
| b163dba2d4 | |||
| a0daabab03 | |||
| b0a5b60945 | |||
| aa83501ed0 | |||
| 5f06414a12 | |||
| 656838811a | |||
| 7b8458630a | |||
| cf2ab08b4d | |||
| 2466112829 | |||
| e5306908e4 | |||
| b628a5f814 | |||
| 62045d76b3 | |||
| f8317f5a03 | |||
| c3ec970a37 | |||
| 5a573bc278 | |||
| 3d200db722 | |||
| e4139713cb |
+116
@@ -4,6 +4,122 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.3-ruta] (2025-02-10)
|
||||
|
||||
- Push down forget me to client configs ([#5431])
|
||||
- Fix statistics shutdown ([#5426])
|
||||
- Make wait_for_graceful_shutdown to be pub ([#5424])
|
||||
- Upgrade to thiserror 2.0 ([#5414])
|
||||
- build(deps): bump the patch-updates group across 1 directory with 9 updates ([#5406])
|
||||
- Relocate a validator api function ([#5401])
|
||||
- Send shutdown instead of panic when reaching max fail ([#5398])
|
||||
- Change Explorer URL to new smooshed nodes ([#5396])
|
||||
- reduce log severity for checking topology validity ([#5395])
|
||||
- MixnetClient can send ClientRequests ([#5381])
|
||||
- Fix missing path triggers for CI ([#5380])
|
||||
- Uncouple storage reference for bandwidth client ([#5372])
|
||||
- build(deps): bump tokio from 1.40.0 to 1.43.0 ([#5370])
|
||||
- DNS resolver configuration for internal HTTP client lookups ([#5355])
|
||||
- Update README.md ([#5328])
|
||||
- Update README.md ([#5327])
|
||||
|
||||
[#5431]: https://github.com/nymtech/nym/pull/5431
|
||||
[#5426]: https://github.com/nymtech/nym/pull/5426
|
||||
[#5424]: https://github.com/nymtech/nym/pull/5424
|
||||
[#5414]: https://github.com/nymtech/nym/pull/5414
|
||||
[#5406]: https://github.com/nymtech/nym/pull/5406
|
||||
[#5401]: https://github.com/nymtech/nym/pull/5401
|
||||
[#5398]: https://github.com/nymtech/nym/pull/5398
|
||||
[#5396]: https://github.com/nymtech/nym/pull/5396
|
||||
[#5395]: https://github.com/nymtech/nym/pull/5395
|
||||
[#5381]: https://github.com/nymtech/nym/pull/5381
|
||||
[#5380]: https://github.com/nymtech/nym/pull/5380
|
||||
[#5372]: https://github.com/nymtech/nym/pull/5372
|
||||
[#5370]: https://github.com/nymtech/nym/pull/5370
|
||||
[#5355]: https://github.com/nymtech/nym/pull/5355
|
||||
[#5328]: https://github.com/nymtech/nym/pull/5328
|
||||
[#5327]: https://github.com/nymtech/nym/pull/5327
|
||||
|
||||
## [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
+162
-175
File diff suppressed because it is too large
Load Diff
+8
-9
@@ -48,7 +48,6 @@ members = [
|
||||
"common/credentials-interface",
|
||||
"common/crypto",
|
||||
"common/dkg",
|
||||
"common/ecash-double-spending",
|
||||
"common/ecash-time",
|
||||
"common/execute",
|
||||
"common/exit-policy",
|
||||
@@ -216,7 +215,7 @@ chacha20 = "0.9.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
chrono = "0.4.39"
|
||||
cipher = "0.4.3"
|
||||
clap = "4.5.26"
|
||||
clap = "4.5.27"
|
||||
clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.0"
|
||||
@@ -249,7 +248,7 @@ futures = "0.3.31"
|
||||
futures-util = "0.3"
|
||||
generic-array = "0.14.7"
|
||||
getrandom = "0.2.10"
|
||||
getset = "0.1.3"
|
||||
getset = "0.1.4"
|
||||
handlebars = "3.5.5"
|
||||
headers = "0.4.0"
|
||||
hex = "0.4.3"
|
||||
@@ -265,7 +264,7 @@ humantime-serde = "1.1.1"
|
||||
human-repr = "1.1.0"
|
||||
hyper = "1.4.1"
|
||||
hyper-util = "0.1"
|
||||
indicatif = "0.17.9"
|
||||
indicatif = "0.17.11"
|
||||
inquire = "0.6.2"
|
||||
ip_network = "0.4.1"
|
||||
ipnetwork = "0.20"
|
||||
@@ -308,11 +307,11 @@ rocket_cors = "0.6.0"
|
||||
rocket_okapi = "0.8.0"
|
||||
safer-ffi = "0.1.13"
|
||||
schemars = "0.8.21"
|
||||
semver = "1.0.24"
|
||||
semver = "1.0.25"
|
||||
serde = "1.0.217"
|
||||
serde_bytes = "0.11.15"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0.135"
|
||||
serde_json = "1.0.138"
|
||||
serde_json_path = "0.7.1"
|
||||
serde_repr = "0.1"
|
||||
serde_with = "3.9.0"
|
||||
@@ -329,9 +328,9 @@ sysinfo = "0.33.0"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.43"
|
||||
tempfile = "3.15"
|
||||
thiserror = "1.0.64"
|
||||
thiserror = "2.0"
|
||||
time = "0.3.37"
|
||||
tokio = "1.39"
|
||||
tokio = "1.43"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-test = "0.4.4"
|
||||
tokio-tun = "0.11.5"
|
||||
@@ -385,7 +384,7 @@ cw4 = { version = "=1.1.2" }
|
||||
cw-controllers = { version = "=1.1.0" }
|
||||
|
||||
# cosmrs-related
|
||||
bip32 = { version = "0.5.2", default-features = false }
|
||||
bip32 = { version = "0.5.3", default-features = false }
|
||||
|
||||
|
||||
cosmrs = { version = "0.21.0" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.46"
|
||||
version = "1.1.48"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -56,7 +56,7 @@ pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
.join(DEFAULT_DATA_DIR)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
|
||||
pub struct Config {
|
||||
#[serde(flatten)]
|
||||
pub base: BaseClientConfig,
|
||||
@@ -94,6 +94,10 @@ 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")),
|
||||
@@ -209,7 +213,7 @@ impl SocketType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Socket {
|
||||
pub socket_type: SocketType,
|
||||
|
||||
@@ -107,5 +107,8 @@ 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 }}
|
||||
|
||||
"#;
|
||||
|
||||
@@ -20,7 +20,7 @@ pub use nym_sphinx::addressing::clients::Recipient;
|
||||
|
||||
pub mod config;
|
||||
|
||||
type NativeClientBuilder<'a> = BaseClientBuilder<'a, QueryHttpRpcNyxdClient, OnDiskPersistent>;
|
||||
type NativeClientBuilder = BaseClientBuilder<QueryHttpRpcNyxdClient, OnDiskPersistent>;
|
||||
|
||||
pub struct SocketClient {
|
||||
/// Client configuration options, including, among other things, packet sending rates,
|
||||
@@ -32,6 +32,10 @@ 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,
|
||||
@@ -108,8 +112,9 @@ 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)?;
|
||||
|
||||
@@ -82,6 +82,7 @@ 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ 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;
|
||||
@@ -106,6 +107,7 @@ 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>> {
|
||||
@@ -133,6 +135,7 @@ 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(
|
||||
|
||||
@@ -41,6 +41,7 @@ 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,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.46"
|
||||
version = "1.1.48"
|
||||
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"
|
||||
|
||||
@@ -93,6 +93,7 @@ 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::{GroupBy, TopologyStructure};
|
||||
use nym_client_core::config::{ForgetMe, GroupBy, TopologyStructure};
|
||||
use nym_config::OptionalSet;
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
@@ -113,6 +113,7 @@ 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>> {
|
||||
@@ -179,6 +180,7 @@ 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)
|
||||
|
||||
@@ -65,6 +65,7 @@ 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,4 +113,8 @@ 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 }}
|
||||
|
||||
"#;
|
||||
|
||||
@@ -40,6 +40,7 @@ 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" }
|
||||
|
||||
@@ -145,6 +145,11 @@ 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:
|
||||
@@ -517,7 +522,7 @@ impl Default for Acknowledgements {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
#[serde(default)]
|
||||
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.
|
||||
@@ -558,6 +563,10 @@ 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)]
|
||||
@@ -595,7 +604,9 @@ 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: false,
|
||||
|
||||
ignore_egress_epoch_role: true,
|
||||
ignore_ingress_epoch_role: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -713,6 +724,9 @@ 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 {
|
||||
@@ -735,6 +749,69 @@ 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,
|
||||
},
|
||||
stats_reporting: Default::default(),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ where
|
||||
&core.client.nym_api_urls,
|
||||
user_agent,
|
||||
core.debug.topology.minimum_gateway_performance,
|
||||
core.debug.topology.ignore_ingress_epoch_role,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
@@ -93,6 +93,10 @@ 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> {
|
||||
@@ -175,6 +179,7 @@ where
|
||||
&core.client.nym_api_urls,
|
||||
user_agent,
|
||||
core.debug.topology.minimum_gateway_performance,
|
||||
core.debug.topology.ignore_ingress_epoch_role,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
@@ -61,4 +61,8 @@ 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,6 +1,7 @@
|
||||
// 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;
|
||||
@@ -31,10 +32,11 @@ use crate::init::{
|
||||
setup_gateway,
|
||||
types::{GatewaySetup, InitialisationResult},
|
||||
};
|
||||
use crate::{config, spawn_future, ForgetMe};
|
||||
use crate::{config, spawn_future};
|
||||
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};
|
||||
@@ -175,8 +177,8 @@ impl From<bool> for CredentialsToggle {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
|
||||
config: &'a Config,
|
||||
pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||
config: Config,
|
||||
client_store: S,
|
||||
dkg_query_client: Option<C>,
|
||||
|
||||
@@ -190,20 +192,18 @@ pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
|
||||
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
|
||||
forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
impl<'a, C, S> BaseClientBuilder<'a, C, S>
|
||||
impl<C, S> BaseClientBuilder<C, S>
|
||||
where
|
||||
S: MixnetClientStorage + 'static,
|
||||
C: DkgQueryClient + Send + Sync + 'static,
|
||||
{
|
||||
pub fn new(
|
||||
base_config: &'a Config,
|
||||
base_config: Config,
|
||||
client_store: S,
|
||||
dkg_query_client: Option<C>,
|
||||
) -> BaseClientBuilder<'a, C, S> {
|
||||
) -> BaseClientBuilder<C, S> {
|
||||
BaseClientBuilder {
|
||||
config: base_config,
|
||||
client_store,
|
||||
@@ -216,13 +216,12 @@ 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.forget_me = forget_me.clone();
|
||||
self.config.debug.forget_me = *forget_me;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -645,13 +644,12 @@ where
|
||||
fn start_mix_traffic_controller(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
shutdown: TaskClient,
|
||||
forget_me: ForgetMe,
|
||||
) -> BatchMixMessageSender {
|
||||
) -> (BatchMixMessageSender, ClientRequestSender) {
|
||||
info!("Starting mix traffic controller...");
|
||||
let (mix_traffic_controller, mix_tx) =
|
||||
MixTrafficController::new(gateway_transceiver, forget_me);
|
||||
let (mix_traffic_controller, mix_tx, client_tx) =
|
||||
MixTrafficController::new(gateway_transceiver);
|
||||
mix_traffic_controller.start_with_shutdown(shutdown);
|
||||
mix_tx
|
||||
(mix_tx, client_tx)
|
||||
}
|
||||
|
||||
// TODO: rename it as it implies the data is persistent whilst one can use InMemBackend
|
||||
@@ -773,7 +771,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(),
|
||||
@@ -799,7 +797,7 @@ where
|
||||
|
||||
let gateway_transceiver = Self::setup_gateway_transceiver(
|
||||
self.custom_gateway_transceiver,
|
||||
self.config,
|
||||
&self.config,
|
||||
init_res,
|
||||
bandwidth_controller,
|
||||
&details_store,
|
||||
@@ -833,10 +831,9 @@ where
|
||||
// traffic stream.
|
||||
// The MixTrafficController then sends the actual traffic
|
||||
|
||||
let message_sender = Self::start_mix_traffic_controller(
|
||||
let (message_sender, client_request_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
|
||||
@@ -911,6 +908,8 @@ where
|
||||
},
|
||||
stats_reporter,
|
||||
task_handle: shutdown,
|
||||
client_request_sender,
|
||||
forget_me: self.config.debug.forget_me,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -922,6 +921,7 @@ 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,6 +4,8 @@
|
||||
// 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;
|
||||
@@ -63,7 +65,7 @@ pub trait MixnetClientStorage {
|
||||
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore;
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Clone)]
|
||||
pub struct Ephemeral {
|
||||
key_store: InMemEphemeralKeys,
|
||||
reply_store: reply_storage::Empty,
|
||||
@@ -71,9 +73,14 @@ pub struct Ephemeral {
|
||||
gateway_details_store: InMemGatewaysDetails,
|
||||
}
|
||||
|
||||
impl Ephemeral {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
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;
|
||||
@@ -195,9 +196,20 @@ impl KeyStore for OnDiskKeys {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Clone)]
|
||||
pub struct InMemEphemeralKeys {
|
||||
keys: Arc<Mutex<Option<ClientKeys>>>,
|
||||
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))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -210,11 +222,11 @@ impl KeyStore for InMemEphemeralKeys {
|
||||
type StorageError = EphemeralKeysError;
|
||||
|
||||
async fn load_keys(&self) -> Result<ClientKeys, Self::StorageError> {
|
||||
self.keys.lock().await.clone().ok_or(EphemeralKeysError)
|
||||
Ok(self.keys.lock().await.clone())
|
||||
}
|
||||
|
||||
async fn store_keys(&self, keys: &ClientKeys) -> Result<(), Self::StorageError> {
|
||||
*self.keys.lock().await = Some(keys.clone());
|
||||
*self.keys.lock().await = keys.clone();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
|
||||
use crate::{spawn_future, ForgetMe};
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::spawn_future;
|
||||
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;
|
||||
|
||||
@@ -23,52 +27,67 @@ 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,
|
||||
forget_me: ForgetMe,
|
||||
) -> (MixTrafficController, BatchMixMessageSender)
|
||||
) -> (
|
||||
MixTrafficController,
|
||||
BatchMixMessageSender,
|
||||
ClientRequestSender,
|
||||
)
|
||||
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>,
|
||||
forget_me: ForgetMe,
|
||||
) -> (MixTrafficController, BatchMixMessageSender) {
|
||||
) -> (
|
||||
MixTrafficController,
|
||||
BatchMixMessageSender,
|
||||
ClientRequestSender,
|
||||
) {
|
||||
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>) {
|
||||
async fn on_messages(
|
||||
&mut self,
|
||||
mut mix_packets: Vec<MixPacket>,
|
||||
) -> Result<(), ErasedGatewayError> {
|
||||
debug_assert!(!mix_packets.is_empty());
|
||||
|
||||
let result = if mix_packets.len() == 1 {
|
||||
@@ -80,21 +99,14 @@ impl MixTrafficController {
|
||||
.await
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
|
||||
@@ -105,13 +117,35 @@ impl MixTrafficController {
|
||||
tokio::select! {
|
||||
mix_packets = self.mix_rx.recv() => match mix_packets {
|
||||
Some(mix_packets) => {
|
||||
self.on_messages(mix_packets).await;
|
||||
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;
|
||||
}
|
||||
}
|
||||
},
|
||||
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;
|
||||
@@ -120,25 +154,6 @@ 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,7 +86,9 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
(**self).send_client_request(message).await
|
||||
let _ = (**self).send_client_request(message.clone()).await?;
|
||||
log::debug!("Sent client request: {:?}", message);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,14 +145,7 @@ where
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
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)
|
||||
}
|
||||
self.gateway_client.send_client_request(message).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -626,9 +626,14 @@ where
|
||||
messages: Vec<RealMessage>,
|
||||
transmission_lane: TransmissionLane,
|
||||
) {
|
||||
self.real_message_sender
|
||||
if let Err(err) = self
|
||||
.real_message_sender
|
||||
.send((messages, transmission_lane))
|
||||
.await
|
||||
.expect("real message receiver task (OutQueueControl) has died");
|
||||
{
|
||||
error!(
|
||||
"Failed to forward messages to the real message sender (OutQueueControl): {err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,7 +545,7 @@ where
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("OutQueueControl: Received shutdown");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -123,6 +123,11 @@ 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 => {
|
||||
@@ -146,13 +151,8 @@ 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");
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,13 @@ 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,
|
||||
|
||||
@@ -96,6 +103,9 @@ 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 },
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ 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;
|
||||
@@ -23,8 +26,6 @@ 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;
|
||||
@@ -91,6 +92,7 @@ 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)
|
||||
@@ -112,7 +114,7 @@ pub async fn gateways_for_init<R: Rng>(
|
||||
// (we don't want instability)
|
||||
let valid_gateways = gateways
|
||||
.iter()
|
||||
.filter(|g| !g.supported_roles.mixnode)
|
||||
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
|
||||
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
|
||||
.filter_map(|gateway| gateway.try_into().ok())
|
||||
.collect::<Vec<_>>();
|
||||
@@ -131,7 +133,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.into()),
|
||||
Ok(Err(conn_failure)) => Err(conn_failure),
|
||||
Ok(Ok((stream, _))) => Ok(stream),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ use serde::Serialize;
|
||||
|
||||
pub mod helpers;
|
||||
pub mod types;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) mod websockets;
|
||||
|
||||
// helpers for error wrapping
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
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)
|
||||
}
|
||||
@@ -33,48 +33,3 @@ 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,6 +27,7 @@ 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,8 +40,6 @@ 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;
|
||||
@@ -53,6 +51,11 @@ 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,
|
||||
|
||||
@@ -201,15 +204,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
"Attemting to establish connection to gateway at: {}",
|
||||
self.gateway_address
|
||||
);
|
||||
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,
|
||||
})
|
||||
}
|
||||
};
|
||||
let (ws_stream, _) = connect_async(&self.gateway_address).await?;
|
||||
|
||||
self.connection = SocketState::Available(Box::new(ws_stream));
|
||||
|
||||
@@ -271,6 +266,19 @@ 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
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
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,7 +44,11 @@ pub enum GatewayClientError {
|
||||
NetworkConnectionFailed { address: String, source: WsError },
|
||||
|
||||
#[error("Invalid URL: {0}")]
|
||||
InvalidURL(String),
|
||||
InvalidUrl(String),
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("resolution failed: {0}")]
|
||||
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
|
||||
|
||||
#[error("No shared key was provided or obtained")]
|
||||
NoSharedKeyAvailable,
|
||||
|
||||
@@ -11,8 +11,7 @@ use crate::{
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
|
||||
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, SpentCredentialsResponse,
|
||||
VerifyEcashTicketBody,
|
||||
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, VerifyEcashTicketBody,
|
||||
};
|
||||
use nym_api_requests::ecash::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
|
||||
@@ -647,13 +646,6 @@ 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,6 +65,12 @@ pub enum EcashApiError {
|
||||
#[from]
|
||||
source: cosmrs::ErrorReport,
|
||||
},
|
||||
|
||||
#[error("nym api error")]
|
||||
NymApi {
|
||||
#[from]
|
||||
source: crate::ValidatorClientError,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<ContractVKShare> for EcashApiClient {
|
||||
|
||||
@@ -31,6 +31,7 @@ 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;
|
||||
@@ -849,20 +850,6 @@ 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,
|
||||
@@ -1027,6 +1014,15 @@ 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,8 +13,6 @@ 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::*;
|
||||
@@ -67,6 +65,8 @@ 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 ({}): {0}", line!())]
|
||||
#[error("VESTING ({l}): {0}", l = line!())]
|
||||
Std(#[from] StdError),
|
||||
|
||||
#[error("VESTING: {0}")]
|
||||
OverflowError(#[from] OverflowError),
|
||||
|
||||
#[error("VESTING ({}): Account does not exist - {0}", line!())]
|
||||
#[error("VESTING ({l}): Account does not exist - {0}", l = line!())]
|
||||
NoAccountForAddress(String),
|
||||
|
||||
#[error("VESTING ({}): Only admin can perform this action, {0} is not admin", line!())]
|
||||
#[error("VESTING ({l}): Only admin can perform this action, {0} is not admin", l = line!())]
|
||||
NotAdmin(String),
|
||||
|
||||
#[error("VESTING ({}): Balance not found for existing account ({0}), this is a bug", line!())]
|
||||
#[error("VESTING ({l}): Balance not found for existing account ({0}), this is a bug", l = line!())]
|
||||
NoBalanceForAddress(String),
|
||||
|
||||
#[error("VESTING ({}): Insufficient balance for address {0} -> {1}", line!())]
|
||||
#[error("VESTING ({l}): Insufficient balance for address {0} -> {1}", l = line!())]
|
||||
InsufficientBalance(String, u128),
|
||||
|
||||
#[error("VESTING ({}): Insufficient spendable balance for address {0} -> {1}", line!())]
|
||||
#[error("VESTING ({l}): Insufficient spendable balance for address {0} -> {1}", l = line!())]
|
||||
InsufficientSpendable(String, u128),
|
||||
|
||||
#[error(
|
||||
"VESTING ({}):Only delegation owner can perform delegation actions, {0} is not the delegation owner"
|
||||
, line!())]
|
||||
"VESTING ({l}):Only delegation owner can perform delegation actions, {0} is not the delegation owner"
|
||||
, l = line!())]
|
||||
NotDelegate(String),
|
||||
|
||||
#[error("VESTING ({}): Total vesting amount is inprobably low -> {0}, this is likely an error", line!())]
|
||||
#[error("VESTING ({l}): Total vesting amount is inprobably low -> {0}, this is likely an error", l = line!())]
|
||||
ImprobableVestingAmount(u128),
|
||||
|
||||
#[error("VESTING ({}): Address {0} has already bonded a node", line!())]
|
||||
#[error("VESTING ({l}): Address {0} has already bonded a node", l = line!())]
|
||||
AlreadyBonded(String),
|
||||
|
||||
#[error("VESTING ({}): Received empty funds vector", line!())]
|
||||
#[error("VESTING ({l}): Received empty funds vector", l = line!())]
|
||||
EmptyFunds,
|
||||
|
||||
#[error("VESTING ({}): Received wrong denom: {0}, expected {1}", line!())]
|
||||
#[error("VESTING ({l}): Received wrong denom: {0}, expected {1}", l = line!())]
|
||||
WrongDenom(String, String),
|
||||
|
||||
#[error("VESTING ({}): Received multiple denoms, expected 1", line!())]
|
||||
#[error("VESTING ({l}): Received multiple denoms, expected 1", l = line!())]
|
||||
MultipleDenoms,
|
||||
|
||||
#[error("VESTING ({}): No delegations found for account {0}, mix_identity {1}", line!())]
|
||||
#[error("VESTING ({l}): No delegations found for account {0}, mix_identity {1}", l = line!())]
|
||||
NoSuchDelegation(Addr, NodeId),
|
||||
|
||||
#[error("VESTING ({}): Only mixnet contract can perform this operation, got {0}", line!())]
|
||||
#[error("VESTING ({l}): Only mixnet contract can perform this operation, got {0}", l = line!())]
|
||||
NotMixnetContract(Addr),
|
||||
|
||||
#[error("VESTING ({}): Calculation underflowed", line!())]
|
||||
#[error("VESTING ({l}): Calculation underflowed", l = line!())]
|
||||
Underflow,
|
||||
|
||||
#[error("VESTING ({}): No bond found for account {0}", line!())]
|
||||
#[error("VESTING ({l}): No bond found for account {0}", l = 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 ({}): Action can only be executed by account owner -> {0}", line!())]
|
||||
#[error("VESTING ({l}): Action can only be executed by account owner -> {0}", l = line!())]
|
||||
NotOwner(String),
|
||||
|
||||
#[error("VESTING ({}): Invalid address: {0}", line!())]
|
||||
#[error("VESTING ({l}): Invalid address: {0}", l = line!())]
|
||||
InvalidAddress(String),
|
||||
|
||||
#[error("VESTING ({}): Account already exists: {0}", line!())]
|
||||
#[error("VESTING ({l}): Account already exists: {0}", l = line!())]
|
||||
AccountAlreadyExists(String),
|
||||
|
||||
#[error("VESTING ({}): Staking account already exists: {0}", line!())]
|
||||
#[error("VESTING ({l}): Staking account already exists: {0}", l = line!())]
|
||||
StakingAccountAlreadyExists(String),
|
||||
|
||||
#[error("VESTING ({}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", line!())]
|
||||
#[error("VESTING ({l}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", l = line!())]
|
||||
MinVestingFunds { sent: u128, need: u128 },
|
||||
|
||||
#[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())]
|
||||
#[error("VESTING ({l}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", l = line!())]
|
||||
LockedPledgeCapReached { current: Uint128, cap: Uint128 },
|
||||
|
||||
#[error("VESTING: ({}: Account owned by {owner} has unpopulated vesting periods!", line!())]
|
||||
#[error("VESTING: ({l}: Account owned by {owner} has unpopulated vesting periods!", l = 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,7 +26,6 @@ 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,6 +13,7 @@ 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,
|
||||
@@ -352,7 +353,9 @@ 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");
|
||||
Ok(false)
|
||||
Err(EcashTicketError::ApiFailure(EcashApiError::NymApi {
|
||||
source: err,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
[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" }
|
||||
@@ -1,136 +0,0 @@
|
||||
// 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(¶ms, 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(¶ms, 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
|
||||
}
|
||||
}
|
||||
@@ -59,12 +59,12 @@ pub enum GatewayRequestsError {
|
||||
source: NymNodeRoutingAddressError,
|
||||
},
|
||||
|
||||
#[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())
|
||||
#[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())
|
||||
]
|
||||
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)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum ClientRequest {
|
||||
UpgradeKey {
|
||||
|
||||
@@ -3,5 +3,22 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
|
||||
ALTER TABLE message_store
|
||||
ADD COLUMN timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
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;
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ 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,
|
||||
|
||||
@@ -22,7 +22,7 @@ pub use user_agent::UserAgent;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod dns;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use dns::HickoryDnsResolver;
|
||||
pub use dns::{HickoryDnsError, 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,6 +30,10 @@ 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),
|
||||
|
||||
@@ -76,6 +76,10 @@ 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,
|
||||
@@ -233,7 +237,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);
|
||||
|
||||
@@ -241,7 +245,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();
|
||||
@@ -252,7 +256,7 @@ where
|
||||
|
||||
Self::start_socks5_listener(
|
||||
&self.config.socks5,
|
||||
self.config.base.debug,
|
||||
self.config.base().debug,
|
||||
client_input,
|
||||
client_output,
|
||||
client_state,
|
||||
|
||||
@@ -221,7 +221,7 @@ impl TaskManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn wait_for_graceful_shutdown(&mut self) {
|
||||
pub 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(5);
|
||||
const SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(10);
|
||||
|
||||
fn new(
|
||||
notify: watch::Receiver<()>,
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::borrow::Borrow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Display;
|
||||
use std::net::IpAddr;
|
||||
use tracing::{debug, warn};
|
||||
use tracing::{debug, trace, 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();
|
||||
|
||||
debug!(
|
||||
trace!(
|
||||
has_layer1 = %has_layer1,
|
||||
has_layer2 = %has_layer2,
|
||||
has_layer3 = %has_layer3,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#![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};
|
||||
@@ -110,6 +111,8 @@ 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 {
|
||||
@@ -128,6 +131,7 @@ 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,6 +146,7 @@ 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,6 +400,10 @@ 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 {
|
||||
@@ -419,6 +428,7 @@ 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,6 +446,7 @@ 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -535,6 +546,29 @@ 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)]
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
#![allow(clippy::empty_docs)]
|
||||
|
||||
use super::{
|
||||
AcknowledgementsWasm, CoverTrafficWasm, DebugWasm, GatewayConnectionWasm, ReplySurbsWasm,
|
||||
StatsReportingWasm, TopologyWasm, TrafficWasm,
|
||||
AcknowledgementsWasm, CoverTrafficWasm, DebugWasm, ForgetMeWasm, GatewayConnectionWasm,
|
||||
ReplySurbsWasm, StatsReportingWasm, TopologyWasm, TrafficWasm,
|
||||
};
|
||||
use crate::config::ConfigDebug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -47,6 +47,9 @@ 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 {
|
||||
@@ -59,6 +62,7 @@ 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,6 +285,11 @@ 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 {
|
||||
@@ -310,6 +319,9 @@ 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,6 +412,26 @@ 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")]
|
||||
|
||||
@@ -132,9 +132,17 @@ 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).await?;
|
||||
let gateways = gateways_for_init(
|
||||
&mut rng,
|
||||
nym_apis,
|
||||
None,
|
||||
minimum_performance,
|
||||
ignore_epoch_roles,
|
||||
)
|
||||
.await?;
|
||||
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
|
||||
}
|
||||
|
||||
@@ -142,9 +150,17 @@ 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).await
|
||||
gateways_for_init(
|
||||
&mut rng,
|
||||
nym_apis,
|
||||
user_agent,
|
||||
minimum_performance,
|
||||
ignore_epoch_roles,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn setup_from_topology(
|
||||
@@ -163,6 +179,7 @@ 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>,
|
||||
@@ -170,6 +187,7 @@ 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(
|
||||
@@ -203,8 +221,13 @@ 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).await?;
|
||||
let available_gateways = current_gateways_wasm(
|
||||
nym_apis,
|
||||
Some(user_agent),
|
||||
min_performance,
|
||||
ignore_epoch_roles,
|
||||
)
|
||||
.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
|
||||
|
||||
Generated
+59
-39
@@ -179,7 +179,7 @@ dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -233,7 +233,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"subtle-encoding",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -267,7 +267,7 @@ dependencies = [
|
||||
"ed25519-zebra",
|
||||
"k256 0.13.3",
|
||||
"rand_core 0.6.4",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -289,7 +289,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -320,7 +320,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
"sha2 0.10.8",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -433,7 +433,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.85",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -448,7 +448,7 @@ dependencies = [
|
||||
"cw-utils",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -467,7 +467,7 @@ dependencies = [
|
||||
"prost",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -493,7 +493,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"semver",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -508,7 +508,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"semver",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -538,7 +538,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"semver",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -553,7 +553,7 @@ dependencies = [
|
||||
"cw20",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -570,7 +570,7 @@ dependencies = [
|
||||
"cw3",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -622,7 +622,7 @@ dependencies = [
|
||||
"nym-group-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1140,7 +1140,7 @@ dependencies = [
|
||||
"nym-coconut-bandwidth-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1170,7 +1170,7 @@ dependencies = [
|
||||
"nym-contracts-common",
|
||||
"nym-group-contract-common",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1196,7 +1196,7 @@ dependencies = [
|
||||
"cw-storage-plus",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 2.0.11",
|
||||
"vergen",
|
||||
]
|
||||
|
||||
@@ -1210,7 +1210,7 @@ dependencies = [
|
||||
"nym-sphinx-types",
|
||||
"rand",
|
||||
"subtle-encoding",
|
||||
"thiserror",
|
||||
"thiserror 2.0.11",
|
||||
"x25519-dalek",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -1240,7 +1240,7 @@ dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"sylvia",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1254,7 +1254,7 @@ dependencies = [
|
||||
"cw-utils",
|
||||
"cw2",
|
||||
"nym-multisig-contract-common",
|
||||
"thiserror",
|
||||
"thiserror 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1289,7 +1289,7 @@ dependencies = [
|
||||
"rand_chacha",
|
||||
"semver",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
"time",
|
||||
]
|
||||
|
||||
@@ -1310,7 +1310,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
"thiserror 2.0.11",
|
||||
"time",
|
||||
]
|
||||
|
||||
@@ -1326,7 +1326,7 @@ dependencies = [
|
||||
"cw4",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1349,7 +1349,7 @@ name = "nym-sphinx-types"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"sphinx-packet",
|
||||
"thiserror",
|
||||
"thiserror 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1370,7 +1370,7 @@ dependencies = [
|
||||
"rand_chacha",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1383,7 +1383,7 @@ dependencies = [
|
||||
"nym-contracts-common",
|
||||
"nym-mixnet-contract-common",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1489,9 +1489,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.89"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1666,7 +1666,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.85",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1699,9 +1699,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.24"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
|
||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -1741,7 +1741,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.85",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1752,7 +1752,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.85",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1775,7 +1775,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.85",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1934,9 +1934,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.85"
|
||||
version = "2.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
|
||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1949,7 +1949,16 @@ version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1960,7 +1969,18 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.85",
|
||||
"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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2126,5 +2146,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.85",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import { Tabs } from 'nextra/components';
|
||||
import { Callout } from 'nextra/components';
|
||||
import { VarInfo } from 'components/variable-info.tsx';
|
||||
import { Steps } from 'nextra/components';
|
||||
import { MyTab } from 'components/generic-tabs.tsx';
|
||||
import NodeApiCheckHelp from 'components/outputs/command-outputs/node-api-check-help.md';
|
||||
import NodeApiCheckQueryHelp from 'components/outputs/command-outputs/node-api-check-query-help.md'
|
||||
|
||||
# Node API Check
|
||||
|
||||
<Callout type="warning" emoji="⚠️">
|
||||
CLI tool `node_api_check.py`, helping operators to collect all API values about their nodes locally, is not up to date with the API changes introduced with `v2024.13-magura` release version. Please treat it as unstable before we fix it.
|
||||
</Callout>
|
||||
|
||||
<VarInfo />
|
||||
|
||||
Operating a `nym-node` is not a *"set and forget"* endeavor, it takes some work. To diagnose node network performance through querying APIs, is a good knowledge to have. These are the main places to look for API endpoints regarding `nym-node`:
|
||||
|
||||
- [`openapi.json`](https://validator.nymtech.net/api/v1/openapi.json): a list of all endpoints
|
||||
- [Swagger UI page](https://validator.nymtech.net/api/swagger/index.html)
|
||||
|
||||
Besides that, node operators can check out their node performance, connectivity and much more on [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net/).
|
||||
|
||||
### Basic API usage
|
||||
|
||||
For information about available endpoints and their status, you can refer to:
|
||||
```sh
|
||||
# for http
|
||||
http://<PUBLIC_IP>:8080/api/v1/swagger/#/
|
||||
# or
|
||||
http://<PUBLIC_IP>/api/v1/swagger/#/
|
||||
|
||||
# for reversed proxy/WSS
|
||||
https://<HOSTNAME>/api/v1/swagger/#/
|
||||
```
|
||||
|
||||
For example to determine which mode your node is running, you can check `:8080/api/v1/roles` endpoint:
|
||||
```sh
|
||||
# for http
|
||||
http://<PUBLIC_IP>:8080/api/v1/roles
|
||||
# or
|
||||
http://<PUBLIC_IP>/api/v1/roles
|
||||
|
||||
# for reversed proxy/WSS
|
||||
https://<HOSTNAME>/api/v1/roles
|
||||
```
|
||||
|
||||
## `node_api_check.py`
|
||||
|
||||
To make this a bit easier, we made a CLI tool querying all available API endpoints based on node `Identity Key` (further denoted as `<ID_KEY>`) called `node_api_check.py`. To diagnose your node performance, whether by yourself or by sharing an output in our [operator channel](https://matrix.to/#/#operators:nymtech.chat), this tool provides you with a quick overview of live data. We recommend to run this checker alongside [`nym_gateway_probe`](gateway-probe.md) to triage both performance and an actual routing.
|
||||
|
||||
Besides querying any bonded node APIs, `nym_api_check.py` has a function counting all existing nodes in provided version.
|
||||
|
||||
### Setup
|
||||
|
||||
#### Pre-requsities
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. Install and configure Python3
|
||||
|
||||
|
||||
- Start with installing Python3:
|
||||
```sh
|
||||
sudo apt install python3
|
||||
```
|
||||
|
||||
- Make sure Python3 is your default Python version:
|
||||
```sh
|
||||
update-alternatives --install /usr/bin/python python /usr/bin/python3 1
|
||||
```
|
||||
|
||||
- Verify:
|
||||
```sh
|
||||
python --version
|
||||
|
||||
# should return higher than 3
|
||||
```
|
||||
|
||||
- Install Python modules `tabulate`, `pandas` and `argparse` either using [`pip`](https://python.land/virtual-environments/installing-packages-with-pip) or if you installed Python3 system-wide you can install modules directly:
|
||||
|
||||
<div>
|
||||
<Tabs items={[
|
||||
<strong>Using <code>pip</code></strong>,
|
||||
<strong>System-wide Python installation</strong>,
|
||||
]} defaultIndex="1">
|
||||
<MyTab>
|
||||
```sh
|
||||
pip install tabulate pandas argparse
|
||||
```
|
||||
</MyTab>
|
||||
<MyTab>
|
||||
```sh
|
||||
sudo apt install python3-tabulate python3-pandas python3-argparse -y
|
||||
```
|
||||
</MyTab>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
###### 2. Install `node_api_check.py` and make executable
|
||||
|
||||
To run the program you neet to have [`node_api_check.py`](https://github.com/nymtech/nym/tree/develop/scripts/node_api_check.py) and [`api_endpoints.json`](https://github.com/nymtech/nym/tree/develop/scripts/api_endpoints.json).
|
||||
|
||||
- If you [compiled from source](../../binaries/building-nym.mdx), you already have both of these files. Note that the latest version of this program may be on `develop` branch.
|
||||
|
||||
- If you prefer to download them individually, do it by opening terminal in your desired location and running:
|
||||
```sh
|
||||
wget https://raw.githubusercontent.com/nymtech/nym/tree/develop/node_api_check.py
|
||||
|
||||
wget https://raw.githubusercontent.com/nymtech/nym/tree/develop/api_endpoints.json
|
||||
```
|
||||
|
||||
- Make executable:
|
||||
```sh
|
||||
chmod u+x node_api_check.py
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
Now you are ready to check your node.
|
||||
|
||||
### Usage & Commands
|
||||
|
||||
- Run the program with `--help` flag to see the available commands:
|
||||
```sh
|
||||
./node_api_check.py --help
|
||||
```
|
||||
|
||||
- Command Output:
|
||||
|
||||
<NodeApiCheckHelp />
|
||||
|
||||
#### `query_stats`
|
||||
|
||||
When you want to see all the options connected to any command, add a `--help` flag after the command of your choice. Command `query_stats` is the most useful one of this program.
|
||||
|
||||
```sh
|
||||
./node_api_check query_stats --help
|
||||
```
|
||||
|
||||
- Command output:
|
||||
|
||||
<NodeApiCheckQueryHelp/ >
|
||||
|
||||
The most common usage may be `./node_api_check.py query_stats <ID_KEY>` where `<ID_KEY>` is required, substitute it with node Identity Key.
|
||||
|
||||
**Optional arguments**
|
||||
|
||||
| Flag | Shortcut | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `--markdown` | `-m` | returns output in markdown format |
|
||||
| `--no_routing_history` | None | returns output without routing history which can be lengthy |
|
||||
| `--no_verloc_metrics` | None | returns output without verloc measurement which can be lengthy |
|
||||
| `--output` | `-o` | exports output to a file, possible to add a target path |
|
||||
|
||||
#### `version_count`
|
||||
|
||||
<Callout>
|
||||
To see a quick overview of `nym-node` version distribution in numbers and graph, visit [Nym Harbourmaster](https://harbourmaster.nymtech.net).
|
||||
</Callout>
|
||||
|
||||
Another command is `version_count` where at least one `nym-node` version is required. In case of multiple version count, separate the versions with space. We recommend to run this command with `--markdown` flag for a nicer output. This is an example where we want to look up how many registered nodes are on versions `1.1.0`, `1.1.1`, `1.1.2` and `1.1.3`:
|
||||
```sh
|
||||
./node_api_check version_count 1.1.0 1.1.1 1.1.2 1.1.3 --markdown
|
||||
```
|
||||
|
||||
@@ -11,22 +11,38 @@ Tor community created a very helpful table called [*Good Bad ISPs*](https://comm
|
||||
|
||||
<div>
|
||||
<Tabs items={[
|
||||
<code>nym-node</code>,
|
||||
<><code>nym-node</code> - dedicated server</>,
|
||||
<><code>nym-node</code> - VPS</>,
|
||||
<code>validator</code>,
|
||||
]} defaultIndex="0">
|
||||
<MyTab>
|
||||
#### `nym-node`
|
||||
Before we conclude the testing with exact results, these are the rough specs:
|
||||
#### `nym-node` - dedicated server
|
||||
If you are about to start a new Nym Node, look for a server with this specification:
|
||||
|
||||
| **Hardware** | **Minimum Specification** |
|
||||
| :--- | ---: |
|
||||
| CPU Cores | 4 |
|
||||
| Memory | 8 GB RAM |
|
||||
| Storage | 80 GB |
|
||||
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
|
||||
| Bandwidth | > 1Tb |
|
||||
| Port speed | 1Gbps |
|
||||
| **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 |
|
||||
|
||||
</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
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
```
|
||||
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 furthermore, the Digital Millennium Copyright Act's ("DMCA") safe harbors likely protect you from liability arising from this complaint. The notice is likely based upon misunderstandings about the law and about some of the software I run.
|
||||
|
||||
As you know, the DMCA creates four "safe harbors" for service providers to protect them from copyright liability for the acts of their users, when the ISPs fulfill certain requirements. (17 U.S.C. 512)
|
||||
|
||||
The DMCA's requirements vary depending on the ISP's role. You may be familiar with the "notice and takedown" provisions of section 512(c) of the DMCA; however, those do not apply when an ISP merely acts as a conduit.
|
||||
Instead, the "conduit" safe harbor of section 512(a) of the DMCA has 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.
|
||||
|
||||
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. It 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 hop 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 was conceived during the EU-funded project 'Panoramix' R&D project (Horizon 2020 programme) with the goal of protecting communication privacy by building a comprehensive mixnet infrastructure. This project has received funding from the European Union’s 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/ ). Nym has continued this work by building a decentralized routing, authentication and payment protocol. Nym will enable developers to build their own sustainable privacy-enhanced services on top of its platform.
|
||||
|
||||
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 copyright-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. I do nothing to encourage or promote the use of the Nym network for copyright infringement.
|
||||
|
||||
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.
|
||||
|
||||
Therefore, you should continue to be protected under the DMCA 512(a) safe harbor 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>
|
||||
```
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
```
|
||||
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 and privacy protection to ordinary people and businesses via the Nym platform and NymVPN app.
|
||||
|
||||
The Nym traffic pattern is somewhat unique, as we route traffic using the “sphinx” packet format (similar to Tor 's onion encryption), which makes all traffic look uniform. I wanted to introduce myself and the machines I am running just so that this unique traffic pattern doesn’t raise any flags or issues with you!
|
||||
|
||||
We have the highest security standards, which also means I simply relay traffic and do not know the end-destination nor its content. I can not be selective about 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. More information can be found at https://nym.com.
|
||||
|
||||
Feel free to ask any questions.
|
||||
|
||||
Many thanks,
|
||||
|
||||
<YOUR_NAME/PSEUDONYM>
|
||||
```
|
||||
+1
-1
@@ -1 +1 @@
|
||||
805_903_308
|
||||
807_251_217
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1_022_821
|
||||
1_025_628
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
402_951_654
|
||||
403_625_608
|
||||
|
||||
+4
-4
@@ -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 | 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 |
|
||||
| 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 |
|
||||
|
||||
@@ -1 +1 @@
|
||||
Thursday, January 23rd 2025, 10:41:32 UTC
|
||||
Monday, February 3rd 2025, 13:47:19 UTC
|
||||
|
||||
@@ -4,6 +4,8 @@ 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,9 +1,11 @@
|
||||
import { RedocStandalone } from 'redoc';
|
||||
|
||||
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 []().
|
||||
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 />
|
||||
|
||||
<RedocStandalone
|
||||
specUrl=""
|
||||
specUrl="https://api.sandbox.nymtech.net/swagger/swagger.yaml"
|
||||
options={{
|
||||
nativeScrollbars: true,
|
||||
theme: {
|
||||
|
||||
@@ -3,6 +3,8 @@ 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,6 +2,8 @@ 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,6 +2,8 @@ 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,6 +2,8 @@ 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,10 +1,27 @@
|
||||
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>
|
||||
|
||||
> Interactive Redoc component coming soon
|
||||
<br /><br />
|
||||
|
||||
<RedocStandalone
|
||||
specUrl="https://validator.nymtech.net/api-docs/openapi.json"
|
||||
options={{
|
||||
nativeScrollbars: true,
|
||||
theme: {
|
||||
sidebar: {
|
||||
backgroundColor: '#273239',
|
||||
textColor: '#FCFDFE'
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
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>
|
||||
@@ -29,6 +33,11 @@ export const TunnelManagerCommands = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
export const LoadEndpointInfo = () => (
|
||||
<div>
|
||||
Developer notes behind <code>/load</code> endpoint
|
||||
</div>
|
||||
);
|
||||
|
||||
# Changelog
|
||||
|
||||
@@ -38,6 +47,406 @@ 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
|
||||
- Timely upgrades without direct message notifications: Only Latest version, and the two preceding are accepted
|
||||
- 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. 20Tb 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 run [reverse proxy](nodes/nym-node/configuration) with landing page URL starting with `nym-exit` and [Web Secure Socket](nodes/nym-node/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
|
||||
- Timely upgrades without direct message notifications: Only Latest version, and the two preceding are accepted
|
||||
- Stronger specs for VPS are to ensure that the `nym-node` operation will remain solid even when sharing a server with other users
|
||||
- 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 1Gbps expected speed of Nym network is met even when sharing a server with other users
|
||||
- Operators run [reverse proxy](nodes/nym-node/configuration) with landing page URL starting with `nym-exit` and [Web Secure Socket](nodes/nym-node/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. 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,5 +1,7 @@
|
||||
{
|
||||
"exit-gateway": "Exit Gateway",
|
||||
"legal": "Legal Counsel",
|
||||
"templates": "Templates",
|
||||
"jurisdictions": "Jurisdictions",
|
||||
"isp-list": "ISP List",
|
||||
"landing-pages": "Landing Pages",
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
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).
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
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 />
|
||||
@@ -35,9 +35,7 @@ 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)
|
||||
- [Nym wallet](nodes/preliminary-steps/wallet-preparation)
|
||||
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)**
|
||||
|
||||
2. **[Setup](nodes/nym-node/setup.mdx) the node**
|
||||
|
||||
|
||||
@@ -126,13 +126,32 @@ mkdir -pv <PATH_TO_TARGET_DIRECTORY>
|
||||
# mkdir -pv $HOME/backup/my_nym_node/.nym
|
||||
```
|
||||
|
||||
###### 2. Copy configuration folder `.nym` from your VPS to your newly created backup directory
|
||||
###### 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
|
||||
|
||||
```sh
|
||||
scp -r <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes/<ID> <PATH_TO_TARGET_DIRECTORY>
|
||||
```
|
||||
|
||||
###### 3. Verify the success of the backup
|
||||
###### 4. 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.
|
||||
|
||||
@@ -227,7 +246,33 @@ 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).
|
||||
* 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.
|
||||
|
||||
###### 4. Change the node smart contract info via the wallet interface
|
||||
@@ -313,21 +358,71 @@ mkdir ~/.nym
|
||||
#in case no `nym-node` was initialized previously
|
||||
mkdir ~/.nym/nym-nodes
|
||||
```
|
||||
###### 2. Backup `clients.sqlite` database
|
||||
|
||||
###### 2. Move the node data and keys to the new machine
|
||||
- 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
|
||||
|
||||
* 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/
|
||||
```
|
||||
|
||||
###### 3. Open new/target VPS terminal and configure the node
|
||||
###### 4. 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.
|
||||
|
||||
* 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:
|
||||
###### 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:
|
||||
```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-01-16T11:54:17.079662337Z
|
||||
Build Version: 1.3.1
|
||||
Commit SHA: 5ab164d229f85bd2dd27ec6e38292c281df2f678
|
||||
Commit Date: 2025-01-16T12:51:53.000000000+01:00
|
||||
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.0
|
||||
rustc Version: 1.84.1
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
@@ -39,21 +39,35 @@ 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 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.
|
||||
**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!**
|
||||
</Callout>
|
||||
|
||||
Nym Node has three functionalities in the network: `entry-gateway`, `mixnode` and `exit-gateway`.
|
||||
### Mixnet Routing
|
||||
|
||||
- **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.
|
||||
***Mixnet mode (5-hop) is the full anonymising option of NymVPN. Read more about the Mixnet architecture [here](http://localhost:3000/docs/network/architecture)***
|
||||
|
||||
- **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
|
||||
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
|
||||
|
||||
- **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 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.
|
||||
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.**
|
||||
</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 node’s selection chances.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.44"
|
||||
version = "1.1.46"
|
||||
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 * 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 {NodeDelegationsTable, VestingDelegationWarning} from "@/app/network-components/nodes/[id]/NodeDelegationsTable";
|
||||
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,28 +71,33 @@ 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">
|
||||
@@ -109,81 +114,128 @@ 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 ({selectedNymNode.rewarding_details.unique_delegations} delegates)
|
||||
</VestingDelegationWarning> :
|
||||
<>Delegates ({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 (
|
||||
{
|
||||
selectedNymNode.rewarding_details
|
||||
.unique_delegations
|
||||
}{" "}
|
||||
delegates)
|
||||
</VestingDelegationWarning>
|
||||
) : (
|
||||
<>
|
||||
Delegates (
|
||||
{
|
||||
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}>
|
||||
@@ -205,42 +257,41 @@ 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])
|
||||
}, [id, fetchNodeById]);
|
||||
|
||||
if (isLoading) {
|
||||
return <CircularProgress />
|
||||
return <CircularProgress />;
|
||||
}
|
||||
|
||||
// loaded, but not found
|
||||
@@ -248,31 +299,38 @@ 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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return <PageNymNodeDetailsWithState selectedNymNode={selectedNode} />
|
||||
}
|
||||
if (!selectedNode) {
|
||||
return (
|
||||
<Alert severity="warning">
|
||||
<AlertTitle>Legacy Node</AlertTitle>
|
||||
Unable to load details for the selected Nym node.
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
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,4 +1,14 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const 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,
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
"../assets/*"
|
||||
]
|
||||
},
|
||||
"moduleResolution": "node"
|
||||
"moduleResolution": "node",
|
||||
"target": "ES2017"
|
||||
},
|
||||
"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: {}). Try to split it before attempting again", i64::MAX)]
|
||||
#[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)]
|
||||
UnsupportedBandwidthValue(u64),
|
||||
|
||||
#[error("failed to parse the bandwidth voucher value: {source}")]
|
||||
|
||||
+1
-3
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.48"
|
||||
version = "1.1.50"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
@@ -16,7 +16,6 @@ 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"
|
||||
@@ -84,7 +83,6 @@ 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" }
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
DROP TABLE partial_bloomfilter;
|
||||
DROP TABLE bloomfilter_parameters;
|
||||
@@ -28,6 +28,7 @@ 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"] }
|
||||
|
||||
@@ -12,6 +12,12 @@ 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
|
||||
|
||||
@@ -6,6 +6,7 @@ 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::{
|
||||
@@ -89,12 +90,6 @@ 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(
|
||||
@@ -127,27 +122,17 @@ async fn verify_ticket(
|
||||
return reject_ticket(EcashTicketVerificationRejection::InvalidTicket);
|
||||
}
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
.await?;
|
||||
if was_present {
|
||||
if !was_inserted {
|
||||
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,
|
||||
@@ -240,5 +225,8 @@ async fn batch_redeem_tickets(
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn double_spending_filter_v1() -> AxumResult<Json<SpentCredentialsResponse>> {
|
||||
AxumResult::Err(AxumErrorResponse::internal_msg("permanently restricted"))
|
||||
AxumResult::Err(AxumErrorResponse::new(
|
||||
"permanently restricted",
|
||||
StatusCode::GONE,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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 })
|
||||
}
|
||||
}
|
||||
@@ -2,106 +2,14 @@
|
||||
// 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::{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,
|
||||
))
|
||||
}
|
||||
use tracing::warn;
|
||||
|
||||
pub(crate) fn ensure_sane_expiration_date(expiration_date: Date) -> Result<(), EcashError> {
|
||||
let today = ecash_today();
|
||||
|
||||
@@ -9,9 +9,7 @@ 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,
|
||||
};
|
||||
@@ -21,69 +19,6 @@ 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,
|
||||
@@ -207,9 +142,6 @@ 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>>>,
|
||||
}
|
||||
@@ -218,7 +150,6 @@ impl LocalEcashState {
|
||||
pub(crate) fn new(
|
||||
ecash_keypair: KeyPair,
|
||||
identity_keypair: identity::KeyPair,
|
||||
double_spending_filter: TicketDoubleSpendingFilter,
|
||||
explicitly_disabled: bool,
|
||||
) -> Self {
|
||||
LocalEcashState {
|
||||
@@ -228,7 +159,6 @@ 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()),
|
||||
}
|
||||
}
|
||||
|
||||
+51
-172
@@ -8,11 +8,9 @@ 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, prepare_partial_bloomfilter_builder, query_all_threshold_apis,
|
||||
try_rebuild_bloomfilter,
|
||||
};
|
||||
use crate::ecash::state::helpers::{ensure_sane_expiration_date, query_all_threshold_apis};
|
||||
use crate::ecash::state::local::{DailyMerkleTree, LocalEcashState};
|
||||
use crate::ecash::storage::models::{SerialNumberWrapper, TicketProvider};
|
||||
use crate::ecash::storage::EcashStorageExt;
|
||||
@@ -33,31 +31,30 @@ use nym_compact_ecash::scheme::expiration_date_signatures::{
|
||||
aggregate_annotated_expiration_signatures, ExpirationDateSignatureShare,
|
||||
};
|
||||
use nym_compact_ecash::{
|
||||
constants, scheme::expiration_date_signatures::sign_expiration_date, BlindedSignature, Bytable,
|
||||
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_double_spending::DoubleSpendingFilter;
|
||||
use nym_ecash_time::{cred_exp_date, ecash_today_date};
|
||||
use nym_ecash_time::{ecash_default_expiration_date, ecash_today_date};
|
||||
use nym_task::TaskClient;
|
||||
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;
|
||||
pub(crate) mod bloom;
|
||||
mod cleaner;
|
||||
pub(crate) mod global;
|
||||
mod helpers;
|
||||
pub(crate) mod local;
|
||||
@@ -84,10 +81,24 @@ 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,
|
||||
|
||||
@@ -99,7 +110,8 @@ pub struct EcashState {
|
||||
}
|
||||
|
||||
impl EcashState {
|
||||
pub(crate) async fn new<C, D>(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new<C, D>(
|
||||
global_config: &Config,
|
||||
contract_address: AccountId,
|
||||
client: C,
|
||||
@@ -107,24 +119,38 @@ impl EcashState {
|
||||
key_pair: KeyPair,
|
||||
comm_channel: D,
|
||||
storage: NymApiStorage,
|
||||
) -> Result<Self>
|
||||
task_client: TaskClient,
|
||||
) -> Self
|
||||
where
|
||||
C: LocalClient + Send + Sync + 'static,
|
||||
D: APICommunicationChannel + Send + Sync + 'static,
|
||||
{
|
||||
let double_spending_filter = try_rebuild_bloomfilter(&storage).await?;
|
||||
|
||||
Ok(Self {
|
||||
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
|
||||
@@ -766,12 +792,6 @@ 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(())
|
||||
@@ -807,6 +827,12 @@ 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?;
|
||||
@@ -863,18 +889,17 @@ 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<()> {
|
||||
) -> Result<bool> {
|
||||
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(
|
||||
@@ -921,150 +946,4 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::ecash::storage::models::{
|
||||
IssuedHash, RawExpirationDateSignatures, RawIssuedTicketbook, SerialNumberWrapper,
|
||||
StoredBloomfilterParams, TicketProvider, VerifiedTicket,
|
||||
TicketProvider, VerifiedTicket,
|
||||
};
|
||||
use crate::support::storage::manager::StorageManager;
|
||||
use async_trait::async_trait;
|
||||
@@ -65,6 +65,7 @@ pub trait EcashStorageManagerExt {
|
||||
gateway_address: &str,
|
||||
) -> Result<Option<TicketProvider>, sqlx::Error>;
|
||||
|
||||
/// Returns a boolean to indicate whether the ticket has actually been inserted
|
||||
async fn insert_verified_ticket(
|
||||
&self,
|
||||
provider_id: i64,
|
||||
@@ -72,7 +73,7 @@ pub trait EcashStorageManagerExt {
|
||||
verified_at: OffsetDateTime,
|
||||
ticket_data: Vec<u8>,
|
||||
serial_number: Vec<u8>,
|
||||
) -> Result<(), sqlx::Error>;
|
||||
) -> Result<bool, sqlx::Error>;
|
||||
|
||||
async fn get_ticket(&self, serial_number: &[u8])
|
||||
-> Result<Option<VerifiedTicket>, sqlx::Error>;
|
||||
@@ -145,41 +146,6 @@ pub trait EcashStorageManagerExt {
|
||||
data: &[u8],
|
||||
) -> Result<(), sqlx::Error>;
|
||||
|
||||
async fn insert_double_spending_filter_params(
|
||||
&self,
|
||||
num_hashes: u32,
|
||||
bitmap_size: u32,
|
||||
sip0_key0: &[u8],
|
||||
sip0_key1: &[u8],
|
||||
sip1_key0: &[u8],
|
||||
sip1_key1: &[u8],
|
||||
) -> Result<i64, sqlx::Error>;
|
||||
|
||||
async fn get_latest_double_spending_filter_params(
|
||||
&self,
|
||||
) -> Result<Option<StoredBloomfilterParams>, sqlx::Error>;
|
||||
|
||||
async fn update_archived_partial_bloomfilter(
|
||||
&self,
|
||||
date: Date,
|
||||
new_bitmap: &[u8],
|
||||
) -> Result<(), sqlx::Error>;
|
||||
|
||||
async fn try_load_partial_bloomfilter_bitmap(
|
||||
&self,
|
||||
date: Date,
|
||||
params_id: i64,
|
||||
) -> Result<Option<Vec<u8>>, sqlx::Error>;
|
||||
|
||||
async fn insert_partial_bloomfilter(
|
||||
&self,
|
||||
date: Date,
|
||||
params_id: i64,
|
||||
bitmap: &[u8],
|
||||
) -> Result<(), sqlx::Error>;
|
||||
|
||||
async fn remove_old_partial_bloomfilters(&self, cutoff: Date) -> Result<(), sqlx::Error>;
|
||||
|
||||
async fn remove_expired_verified_tickets(&self, cutoff: Date) -> Result<(), sqlx::Error>;
|
||||
}
|
||||
|
||||
@@ -330,6 +296,8 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns a boolean to indicate whether the ticket has actually been inserted
|
||||
async fn insert_verified_ticket(
|
||||
&self,
|
||||
provider_id: i64,
|
||||
@@ -337,10 +305,10 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
verified_at: OffsetDateTime,
|
||||
ticket_data: Vec<u8>,
|
||||
serial_number: Vec<u8>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
) -> Result<bool, sqlx::Error> {
|
||||
let affected = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO verified_tickets(ticket_data, serial_number, spending_date, verified_at, gateway_id)
|
||||
INSERT OR IGNORE INTO verified_tickets(ticket_data, serial_number, spending_date, verified_at, gateway_id)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
"#,
|
||||
ticket_data,
|
||||
@@ -350,9 +318,9 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
provider_id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
.await?.rows_affected();
|
||||
|
||||
Ok(())
|
||||
Ok(affected == 1)
|
||||
}
|
||||
|
||||
async fn get_ticket(
|
||||
@@ -573,95 +541,9 @@ impl EcashStorageManagerExt for StorageManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn insert_double_spending_filter_params(
|
||||
&self,
|
||||
num_hashes: u32,
|
||||
bitmap_size: u32,
|
||||
sip0_key0: &[u8],
|
||||
sip0_key1: &[u8],
|
||||
sip1_key0: &[u8],
|
||||
sip1_key1: &[u8],
|
||||
) -> Result<i64, sqlx::Error> {
|
||||
let row_id = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO bloomfilter_parameters(num_hashes, bitmap_size,sip0_key0, sip0_key1, sip1_key0, sip1_key1)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
num_hashes,
|
||||
bitmap_size,
|
||||
sip0_key0,
|
||||
sip0_key1,
|
||||
sip1_key0,
|
||||
sip1_key1
|
||||
).execute(&self.connection_pool).await?.last_insert_rowid();
|
||||
Ok(row_id)
|
||||
}
|
||||
|
||||
async fn get_latest_double_spending_filter_params(
|
||||
&self,
|
||||
) -> Result<Option<StoredBloomfilterParams>, sqlx::Error> {
|
||||
sqlx::query_as("SELECT * FROM bloomfilter_parameters ORDER BY id DESC LIMIT 1")
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_archived_partial_bloomfilter(
|
||||
&self,
|
||||
date: Date,
|
||||
new_bitmap: &[u8],
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE partial_bloomfilter SET bitmap = ? WHERE date = ?",
|
||||
new_bitmap,
|
||||
date
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_load_partial_bloomfilter_bitmap(
|
||||
&self,
|
||||
date: Date,
|
||||
params_id: i64,
|
||||
) -> Result<Option<Vec<u8>>, sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"SELECT bitmap FROM partial_bloomfilter WHERE date = ? AND parameters = ?",
|
||||
date,
|
||||
params_id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
.map(|maybe_record| maybe_record.map(|r| r.bitmap))
|
||||
}
|
||||
|
||||
async fn insert_partial_bloomfilter(
|
||||
&self,
|
||||
date: Date,
|
||||
params_id: i64,
|
||||
bitmap: &[u8],
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO partial_bloomfilter(date, parameters, bitmap) VALUES (?, ?, ?)",
|
||||
date,
|
||||
params_id,
|
||||
bitmap
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_old_partial_bloomfilters(&self, cutoff: Date) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM partial_bloomfilter WHERE date > ?", cutoff)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_expired_verified_tickets(&self, cutoff: Date) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM verified_tickets WHERE spending_date > ?",
|
||||
"DELETE FROM verified_tickets WHERE spending_date < ?",
|
||||
cutoff
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user