diff --git a/CHANGELOG.md b/CHANGELOG.md index fbed61bd54..209e993f51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,82 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [2024.11-wedel] (2024-09-23) + +- Backport #4894 to fix ci ([#4899]) +- Bugfix/ticketbook false double spending ([#4892]) +- fix: allow updating globally stored signatures ([#4891]) +- [DOCs/operators]: Document changelog for patch/2024.10-caramello ([#4886]) +- [DOCs/operators]: Post release docs updates ([#4874]) +- Bump defguard to github latest version ([#4872]) +- chore: removed completed queued mixnet migration ([#4865]) +- Disable push trigger and add missing paths in ci-build ([#4864]) +- Fix linux conditional in ci-build.yml ([#4863]) +- Remove golang workaround in ci-sdk-wasm ([#4858]) +- Revert runner for ci-docs ([#4855]) +- Move credential verification into common crate ([#4853]) +- Fix test failure in ipr request size ([#4844]) +- Start switching over jobs to arc-ubuntu-20.04 ([#4843]) +- Use ecash credential type for bandwidth value ([#4840]) +- Create nym-repo-setup debian package and nym-vpn meta package ([#4837]) +- Remove serde_crate named import ([#4832]) +- Run cargo autoinherit following last weeks dependabot updates ([#4831]) +- revamped ticketbook serialisation and exposed additional cli methods ([#4827]) +- Expose wireguard details on self described endpoint ([#4825]) +- Remove unused wireguard flag from SDK ([#4823]) +- Add `axum` server to `nym-api` ([#4803]) +- Run cargo-autoinherit for a few new crates ([#4801]) +- Update dependabot ([#4796]) +- Fix clippy for unwrap_or_default ([#4783]) +- Enable dependabot version upgrades for root rust workspace ([#4778]) +- Persist used wireguard private IPs ([#4771]) +- Avoid race on ip and registration structures ([#4766]) +- docs/hotfix ([#4765]) +- chore: remove repetitive words ([#4763]) +- Make gateway latency check generic ([#4759]) +- Remove duplicate stat count for retransmissions ([#4756]) +- Update peer refresh value ([#4754]) +- Remove deprecated mark_as_success and use new disarm ([#4751]) +- Add get_mixnodes_described to validator_client ([#4725]) +- New Network Monitor ([#4610]) + +[#4899]: https://github.com/nymtech/nym/pull/4899 +[#4892]: https://github.com/nymtech/nym/pull/4892 +[#4891]: https://github.com/nymtech/nym/pull/4891 +[#4886]: https://github.com/nymtech/nym/pull/4886 +[#4874]: https://github.com/nymtech/nym/pull/4874 +[#4872]: https://github.com/nymtech/nym/pull/4872 +[#4865]: https://github.com/nymtech/nym/pull/4865 +[#4864]: https://github.com/nymtech/nym/pull/4864 +[#4863]: https://github.com/nymtech/nym/pull/4863 +[#4858]: https://github.com/nymtech/nym/pull/4858 +[#4855]: https://github.com/nymtech/nym/pull/4855 +[#4853]: https://github.com/nymtech/nym/pull/4853 +[#4844]: https://github.com/nymtech/nym/pull/4844 +[#4843]: https://github.com/nymtech/nym/pull/4843 +[#4840]: https://github.com/nymtech/nym/pull/4840 +[#4837]: https://github.com/nymtech/nym/pull/4837 +[#4832]: https://github.com/nymtech/nym/pull/4832 +[#4831]: https://github.com/nymtech/nym/pull/4831 +[#4827]: https://github.com/nymtech/nym/pull/4827 +[#4825]: https://github.com/nymtech/nym/pull/4825 +[#4823]: https://github.com/nymtech/nym/pull/4823 +[#4803]: https://github.com/nymtech/nym/pull/4803 +[#4801]: https://github.com/nymtech/nym/pull/4801 +[#4796]: https://github.com/nymtech/nym/pull/4796 +[#4783]: https://github.com/nymtech/nym/pull/4783 +[#4778]: https://github.com/nymtech/nym/pull/4778 +[#4771]: https://github.com/nymtech/nym/pull/4771 +[#4766]: https://github.com/nymtech/nym/pull/4766 +[#4765]: https://github.com/nymtech/nym/pull/4765 +[#4763]: https://github.com/nymtech/nym/pull/4763 +[#4759]: https://github.com/nymtech/nym/pull/4759 +[#4756]: https://github.com/nymtech/nym/pull/4756 +[#4754]: https://github.com/nymtech/nym/pull/4754 +[#4751]: https://github.com/nymtech/nym/pull/4751 +[#4725]: https://github.com/nymtech/nym/pull/4725 +[#4610]: https://github.com/nymtech/nym/pull/4610 + ## [2024.10-caramello] (2024-09-10) - Backport 4844 and 4845 ([#4857]) diff --git a/Cargo.lock b/Cargo.lock index 23340b568b..02e9018d17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1912,11 +1912,10 @@ checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "defguard_wireguard_rs" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba16f17698d4b389907310af018b0c3a80b025bba9c38d947cbc6dd70921743" +version = "0.4.7" +source = "git+https://github.com/DefGuard/wireguard-rs.git?rev=v0.4.7#ef1cf3714629bf5016fb38cbb7320451dc69fb09" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "libc", "log", "netlink-packet-core", @@ -1925,7 +1924,7 @@ dependencies = [ "netlink-packet-utils", "netlink-packet-wireguard", "netlink-sys", - "nix 0.27.1", + "nix 0.29.0", "serde", "thiserror", ] @@ -2318,7 +2317,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "explorer-api" -version = "1.1.39" +version = "1.1.40" dependencies = [ "chrono", "clap 4.5.17", @@ -4013,14 +4012,15 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.17.1" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +checksum = "55e5bda7ca0f9ac5e75b5debac3b75e29a8ac8e2171106a2c3bb466389a8dd83" dependencies = [ "anyhow", - "bitflags 1.3.2", + "bitflags 2.5.0", "byteorder", "libc", + "log", "netlink-packet-core", "netlink-packet-utils", ] @@ -4080,7 +4080,6 @@ dependencies = [ "bitflags 2.5.0", "cfg-if", "libc", - "memoffset", ] [[package]] @@ -4093,6 +4092,7 @@ dependencies = [ "cfg-if", "cfg_aliases", "libc", + "memoffset", ] [[package]] @@ -4223,7 +4223,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nym-api" -version = "1.1.43" +version = "1.1.44" dependencies = [ "anyhow", "async-trait", @@ -4469,7 +4469,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.41" +version = "1.1.42" dependencies = [ "anyhow", "base64 0.22.1", @@ -4550,7 +4550,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.40" +version = "1.1.41" dependencies = [ "bs58", "clap 4.5.17", @@ -5589,7 +5589,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.41" +version = "1.1.42" dependencies = [ "addr", "anyhow", @@ -5640,7 +5640,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.1.7" +version = "1.1.8" dependencies = [ "anyhow", "bip39", @@ -5929,7 +5929,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.40" +version = "1.1.41" dependencies = [ "bs58", "clap 4.5.17", @@ -6457,7 +6457,7 @@ dependencies = [ [[package]] name = "nymvisor" -version = "0.1.6" +version = "0.1.7" dependencies = [ "anyhow", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 904936c477..7c0102e3c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -221,7 +221,8 @@ ctr = "0.9.1" cupid = "0.6.1" curve25519-dalek = "4.1" dashmap = "5.5.3" -defguard_wireguard_rs = "0.4.2" +# We want https://github.com/DefGuard/wireguard-rs/pull/64 , but there's no crates.io release being pushed out anymore +defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.4.7" } digest = "0.10.7" dirs = "5.0" doc-comment = "0.3" diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 7e253719c2..74de15b8a9 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.40" +version = "1.1.41" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index ee15f6747c..7100923a87 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.40" +version = "1.1.41" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/common/authenticator-requests/src/v1/registration.rs b/common/authenticator-requests/src/v1/registration.rs index b3226541a6..4e71f0f84f 100644 --- a/common/authenticator-requests/src/v1/registration.rs +++ b/common/authenticator-requests/src/v1/registration.rs @@ -26,7 +26,7 @@ pub type HmacSha256 = Hmac; pub type Nonce = u64; pub type Taken = Option; -pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB +pub const BANDWIDTH_CAP_PER_DAY: i64 = 1024 * 1024 * 1024; // 1 GB #[derive(Serialize, Deserialize, Debug, Clone)] pub struct InitMessage { diff --git a/common/authenticator-requests/src/v2/registration.rs b/common/authenticator-requests/src/v2/registration.rs index a37e06c2eb..b7bf2d8430 100644 --- a/common/authenticator-requests/src/v2/registration.rs +++ b/common/authenticator-requests/src/v2/registration.rs @@ -66,7 +66,7 @@ pub struct RegistredData { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct RemainingBandwidthData { - pub available_bandwidth: u64, + pub available_bandwidth: i64, } /// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret. diff --git a/common/client-libs/gateway-client/src/bandwidth.rs b/common/client-libs/gateway-client/src/bandwidth.rs index 50641f9894..25e9a44394 100644 --- a/common/client-libs/gateway-client/src/bandwidth.rs +++ b/common/client-libs/gateway-client/src/bandwidth.rs @@ -2,21 +2,37 @@ // SPDX-License-Identifier: Apache-2.0 use si_scale::helpers::bibytes2; -use std::sync::atomic::{AtomicI64, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicI64, Ordering}; use std::sync::Arc; use std::time::Duration; use time::OffsetDateTime; -#[derive(Clone, Default)] +pub(crate) struct BandwidthClaimGuard { + inner: Arc, +} + +impl Drop for BandwidthClaimGuard { + fn drop(&mut self) { + let old = self.inner.claiming_more.swap(false, Ordering::SeqCst); + assert!( + old, + "critical failure: there were multiple BandwidthClaimGuard existing" + ) + } +} + +#[derive(Clone)] pub struct ClientBandwidth { inner: Arc, } -#[derive(Default)] struct ClientBandwidthInner { /// the actual bandwidth amount (in bytes) available available: AtomicI64, + /// flag to indicate whether this client is currently in the process of claiming additional bandwidth + claiming_more: AtomicBool, + /// defines the timestamp when the bandwidth information has been logged to the logs stream last_logged_ts: AtomicI64, @@ -29,11 +45,28 @@ impl ClientBandwidth { ClientBandwidth { inner: Arc::new(ClientBandwidthInner { available: AtomicI64::new(0), + claiming_more: AtomicBool::new(false), last_logged_ts: AtomicI64::new(0), last_updated_ts: AtomicI64::new(0), }), } } + + pub(crate) fn begin_bandwidth_claim(&self) -> Option { + if self + .inner + .claiming_more + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + { + Some(BandwidthClaimGuard { + inner: self.inner.clone(), + }) + } else { + None + } + } + pub(crate) fn remaining(&self) -> i64 { self.inner.available.load(Ordering::Acquire) } diff --git a/common/client-libs/gateway-client/src/client/mod.rs b/common/client-libs/gateway-client/src/client/mod.rs index b1e343c68b..2278c6fa47 100644 --- a/common/client-libs/gateway-client/src/client/mod.rs +++ b/common/client-libs/gateway-client/src/client/mod.rs @@ -724,6 +724,11 @@ impl GatewayClient { return Err(GatewayClientError::NoBandwidthControllerAvailable); } + let Some(_claim_guard) = self.bandwidth.begin_bandwidth_claim() else { + debug!("there's already an existing bandwidth claim ongoing"); + return Ok(()); + }; + warn!("Not enough bandwidth. Trying to get more bandwidth, this might take a while"); if !self.cfg.bandwidth.require_tickets { info!("The client is running in disabled credentials mode - attempting to claim bandwidth without a credential"); diff --git a/common/credential-storage/src/backends/sqlite.rs b/common/credential-storage/src/backends/sqlite.rs index 39dbdc8702..8c5b20c7d8 100644 --- a/common/credential-storage/src/backends/sqlite.rs +++ b/common/credential-storage/src/backends/sqlite.rs @@ -171,10 +171,20 @@ impl SqliteEcashTicketbookManager { data: &[u8], ) -> Result<(), sqlx::Error> { sqlx::query!( - "INSERT INTO master_verification_key(epoch_id, serialised_key, serialization_revision) VALUES (?, ?, ?)", + r#" + INSERT OR IGNORE INTO master_verification_key(epoch_id, serialised_key, serialization_revision) VALUES (?, ?, ?); + UPDATE master_verification_key + SET + serialised_key = ?, + serialization_revision = ? + WHERE epoch_id = ? + "#, epoch_id, data, - serialisation_revision + serialisation_revision, + data, + serialisation_revision, + epoch_id ) .execute(&self.connection_pool) .await?; @@ -204,10 +214,20 @@ impl SqliteEcashTicketbookManager { data: &[u8], ) -> Result<(), sqlx::Error> { sqlx::query!( - "INSERT INTO coin_indices_signatures(epoch_id, serialised_signatures, serialization_revision) VALUES (?, ?, ?)", + r#" + INSERT OR IGNORE INTO coin_indices_signatures(epoch_id, serialised_signatures, serialization_revision) VALUES (?, ?, ?); + UPDATE coin_indices_signatures + SET + serialised_signatures = ?, + serialization_revision = ? + WHERE epoch_id = ? + "#, epoch_id, data, - serialisation_revision + serialisation_revision, + data, + serialisation_revision, + epoch_id, ) .execute(&self.connection_pool) .await?; @@ -240,13 +260,21 @@ impl SqliteEcashTicketbookManager { ) -> Result<(), sqlx::Error> { sqlx::query!( r#" - INSERT INTO expiration_date_signatures(expiration_date, epoch_id, serialised_signatures, serialization_revision) - VALUES (?, ?, ?, ?) + INSERT OR IGNORE INTO expiration_date_signatures(expiration_date, epoch_id, serialised_signatures, serialization_revision) + VALUES (?, ?, ?, ?); + UPDATE expiration_date_signatures + SET + serialised_signatures = ?, + serialization_revision = ? + WHERE expiration_date = ? "#, expiration_date, epoch_id, data, - serialisation_revision + serialisation_revision, + data, + serialisation_revision, + expiration_date ) .execute(&self.connection_pool) .await?; diff --git a/common/credential-verification/src/bandwidth_storage_manager.rs b/common/credential-verification/src/bandwidth_storage_manager.rs index 8d538f3aa7..3e35fd9eb2 100644 --- a/common/credential-verification/src/bandwidth_storage_manager.rs +++ b/common/credential-verification/src/bandwidth_storage_manager.rs @@ -1,18 +1,17 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::error::*; +use crate::BandwidthFlushingBehaviourConfig; +use crate::ClientBandwidth; use nym_credentials::ecash::utils::ecash_today; -use nym_credentials_interface::{AvailableBandwidth, Bandwidth}; +use nym_credentials_interface::Bandwidth; use nym_gateway_requests::ServerResponse; use nym_gateway_storage::Storage; use si_scale::helpers::bibytes2; use time::OffsetDateTime; use tracing::*; -use crate::error::*; -use crate::BandwidthFlushingBehaviourConfig; -use crate::ClientBandwidth; - const FREE_TESTNET_BANDWIDTH_VALUE: Bandwidth = Bandwidth::new_unchecked(64 * 1024 * 1024 * 1024); // 64GB #[derive(Clone)] @@ -41,13 +40,13 @@ impl BandwidthStorageManager { } } - pub fn available_bandwidth(&self) -> AvailableBandwidth { - self.client_bandwidth.bandwidth + pub async fn available_bandwidth(&self) -> i64 { + self.client_bandwidth.available().await } async fn sync_expiration(&mut self) -> Result<()> { self.storage - .set_expiration(self.client_id, self.client_bandwidth.bandwidth.expiration) + .set_expiration(self.client_id, self.client_bandwidth.expiration().await) .await?; Ok(()) } @@ -61,17 +60,17 @@ impl BandwidthStorageManager { self.increase_bandwidth(FREE_TESTNET_BANDWIDTH_VALUE, ecash_today()) .await?; - let available_total = self.client_bandwidth.bandwidth.bytes; + let available_total = self.client_bandwidth.available().await; Ok(ServerResponse::Bandwidth { available_total }) } #[instrument(skip_all)] pub async fn try_use_bandwidth(&mut self, required_bandwidth: i64) -> Result { - if self.client_bandwidth.bandwidth.expired() { + if self.client_bandwidth.expired().await { self.expire_bandwidth().await?; } - let available_bandwidth = self.client_bandwidth.bandwidth.bytes; + let available_bandwidth = self.client_bandwidth.available().await; if available_bandwidth < required_bandwidth { return Err(Error::OutOfBandwidth { @@ -90,8 +89,7 @@ impl BandwidthStorageManager { async fn expire_bandwidth(&mut self) -> Result<()> { self.storage.reset_bandwidth(self.client_id).await?; - self.client_bandwidth.bandwidth = Default::default(); - self.client_bandwidth.update_sync_data(); + self.client_bandwidth.expire_bandwidth().await; Ok(()) } @@ -101,31 +99,31 @@ impl BandwidthStorageManager { /// /// * `amount`: amount to decrease the available bandwidth by. async fn consume_bandwidth(&mut self, amount: i64) -> Result<()> { - self.client_bandwidth.bandwidth.bytes -= amount; - self.client_bandwidth.bytes_delta_since_sync -= amount; + self.client_bandwidth.decrease_bandwidth(amount).await; // since we're going to be operating on a fair use policy anyway, even if we crash and let extra few packets // through, that's completely fine - if self.client_bandwidth.should_sync(self.bandwidth_cfg) { - self.sync_bandwidth().await?; + if self.client_bandwidth.should_sync(self.bandwidth_cfg).await { + self.sync_storage_bandwidth().await?; } Ok(()) } #[instrument(level = "trace", skip_all)] - async fn sync_bandwidth(&mut self) -> Result<()> { + async fn sync_storage_bandwidth(&mut self) -> Result<()> { trace!("syncing client bandwidth with the underlying storage"); let updated = self .storage - .increase_bandwidth(self.client_id, self.client_bandwidth.bytes_delta_since_sync) + .increase_bandwidth( + self.client_id, + self.client_bandwidth.delta_since_sync().await, + ) .await?; - trace!(updated); - - self.client_bandwidth.bandwidth.bytes = updated; - - self.client_bandwidth.update_sync_data(); + self.client_bandwidth + .resync_bandwidth_with_storage(updated) + .await; Ok(()) } @@ -140,13 +138,14 @@ impl BandwidthStorageManager { bandwidth: Bandwidth, expiration: OffsetDateTime, ) -> Result<()> { - self.client_bandwidth.bandwidth.bytes += bandwidth.value() as i64; - self.client_bandwidth.bytes_delta_since_sync += bandwidth.value() as i64; - self.client_bandwidth.bandwidth.expiration = expiration; + self.client_bandwidth + .increase_bandwidth(bandwidth.value() as i64, expiration) + .await; // any increases to bandwidth should get flushed immediately // (we don't want to accidentally miss somebody claiming a gigabyte voucher) self.sync_expiration().await?; - self.sync_bandwidth().await + self.sync_storage_bandwidth().await?; + Ok(()) } } diff --git a/common/credential-verification/src/client_bandwidth.rs b/common/credential-verification/src/client_bandwidth.rs index 43a494798a..9b764714e8 100644 --- a/common/credential-verification/src/client_bandwidth.rs +++ b/common/credential-verification/src/client_bandwidth.rs @@ -1,10 +1,12 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::time::Duration; - +use nym_credentials::ecash::utils::ecash_today; use nym_credentials_interface::AvailableBandwidth; +use std::sync::Arc; +use std::time::Duration; use time::OffsetDateTime; +use tokio::sync::RwLock; const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5); const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB @@ -28,10 +30,15 @@ impl Default for BandwidthFlushingBehaviourConfig { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct ClientBandwidth { + inner: Arc>, +} + +#[derive(Debug)] +struct ClientBandwidthInner { pub(crate) bandwidth: AvailableBandwidth, - pub(crate) last_flushed: OffsetDateTime, + pub(crate) last_synced: OffsetDateTime, /// the number of bytes the client had during the last sync. /// it is used to determine whether the current value should be synced with the storage @@ -43,28 +50,74 @@ pub struct ClientBandwidth { impl ClientBandwidth { pub fn new(bandwidth: AvailableBandwidth) -> ClientBandwidth { ClientBandwidth { - bandwidth, - last_flushed: OffsetDateTime::now_utc(), - bytes_at_last_sync: bandwidth.bytes, - bytes_delta_since_sync: 0, + inner: Arc::new(RwLock::new(ClientBandwidthInner { + bandwidth, + last_synced: OffsetDateTime::now_utc(), + bytes_at_last_sync: bandwidth.bytes, + bytes_delta_since_sync: 0, + })), } } - pub(crate) fn should_sync(&self, cfg: BandwidthFlushingBehaviourConfig) -> bool { - if self.bytes_delta_since_sync.abs() >= cfg.client_bandwidth_max_delta_flushing_amount { + pub(crate) async fn should_sync(&self, cfg: BandwidthFlushingBehaviourConfig) -> bool { + let guard = self.inner.read().await; + + if guard.bytes_delta_since_sync.abs() >= cfg.client_bandwidth_max_delta_flushing_amount { return true; } - if self.last_flushed + cfg.client_bandwidth_max_flushing_rate < OffsetDateTime::now_utc() { + if guard.last_synced + cfg.client_bandwidth_max_flushing_rate < OffsetDateTime::now_utc() { return true; } false } - pub(crate) fn update_sync_data(&mut self) { - self.last_flushed = OffsetDateTime::now_utc(); - self.bytes_at_last_sync = self.bandwidth.bytes; - self.bytes_delta_since_sync = 0; + pub(crate) async fn available(&self) -> i64 { + self.inner.read().await.bandwidth.bytes + } + + pub(crate) async fn delta_since_sync(&self) -> i64 { + self.inner.read().await.bytes_delta_since_sync + } + pub(crate) async fn expiration(&self) -> OffsetDateTime { + self.inner.read().await.bandwidth.expiration + } + + pub(crate) async fn expired(&self) -> bool { + self.expiration().await < ecash_today() + } + + pub(crate) async fn decrease_bandwidth(&self, decrease: i64) { + let mut guard = self.inner.write().await; + + guard.bandwidth.bytes -= decrease; + guard.bytes_delta_since_sync -= decrease; + } + + pub(crate) async fn increase_bandwidth(&self, increase: i64, new_expiration: OffsetDateTime) { + let mut guard = self.inner.write().await; + + guard.bandwidth.bytes += increase; + guard.bandwidth.expiration = new_expiration; + guard.bytes_delta_since_sync += increase; + } + + pub(crate) async fn expire_bandwidth(&self) { + let mut guard = self.inner.write().await; + + guard.bandwidth = AvailableBandwidth::default(); + guard.last_synced = OffsetDateTime::now_utc(); + guard.bytes_at_last_sync = 0; + guard.bytes_delta_since_sync = 0; + } + + pub(crate) async fn resync_bandwidth_with_storage(&self, stored: i64) { + let mut guard = self.inner.write().await; + + guard.bandwidth.bytes = stored; + guard.bytes_at_last_sync = stored; + guard.bytes_delta_since_sync = 0; + guard.last_synced = OffsetDateTime::now_utc(); } } diff --git a/common/credential-verification/src/lib.rs b/common/credential-verification/src/lib.rs index bb3a0cab8f..4d2e7be2da 100644 --- a/common/credential-verification/src/lib.rs +++ b/common/credential-verification/src/lib.rs @@ -150,7 +150,7 @@ impl CredentialVerifier { Ok(self .bandwidth_storage_manager .client_bandwidth - .bandwidth - .bytes) + .available() + .await) } } diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index 599cbaf082..6567c2102c 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -114,6 +114,7 @@ pub async fn start_wireguard address: wireguard_data.inner.config().private_ip.to_string(), port: wireguard_data.inner.config().announced_port as u32, peers, + mtu: None, }; wg_api.configure_interface(&interface_config)?; diff --git a/common/wireguard/src/peer_controller.rs b/common/wireguard/src/peer_controller.rs index aa8d5f975e..9bc49b883b 100644 --- a/common/wireguard/src/peer_controller.rs +++ b/common/wireguard/src/peer_controller.rs @@ -226,7 +226,7 @@ impl PeerController { .read() .await .available_bandwidth() - .bytes as u64 + .await } else { let peer = self .host_information @@ -236,7 +236,7 @@ impl PeerController { .get(key) .ok_or(Error::PeerMismatch)? .clone(); - BANDWIDTH_CAP_PER_DAY.saturating_sub(peer.rx_bytes + peer.tx_bytes) + BANDWIDTH_CAP_PER_DAY.saturating_sub((peer.rx_bytes + peer.tx_bytes) as i64) }; Ok(Some(RemainingBandwidthData { diff --git a/envs/qa.env b/envs/qa.env index 781d4ef19f..e88f845416 100644 --- a/envs/qa.env +++ b/envs/qa.env @@ -11,16 +11,13 @@ STAKE_DENOM_DISPLAY=nyx DENOMS_EXPONENT=6 MIXNET_CONTRACT_ADDRESS=n1hm4y6fzgxgu688jgf7ek66px6xkrtmn3gyk8fax3eawhp68c2d5qujz296 -ECASH_CONTRACT_ADDRESS=n14y2x8a60knc5jjfeztt84kw8x8l5pwdgnqg256v0p9v4p7t2q6eswxyusw -GROUP_CONTRACT_ADDRESS=n1qp35fcj0v9u3trhaps5v9q0lc42t4m6aty2wryss75ee8zuqnsqqdcreyq -MULTISIG_CONTRACT_ADDRESS=n1qa4hswlcjmttulj0q9qa46jf64f93pecl6tydcsjldfe0hy5ju0sdmwzya -COCONUT_DKG_CONTRACT_ADDRESS=n1ayrk6wp6w5lf6njtnfjwljmtcc9vevv5sxwkz7uq24rp2pw67t0qhmmxdd +ECASH_CONTRACT_ADDRESS=n13xspq62y9gq6nueqmywxcdv2yep4p6nzv98w2889k25v3nhdy2dq2rkrk7 +GROUP_CONTRACT_ADDRESS=n13l7rwuwktklrwskc7m6lv70zws07en85uma28j7dxwsz9y5hvvhspl7a2t +MULTISIG_CONTRACT_ADDRESS=n138c9pyf7f3hyx0j3t6vmsz7ultnw2wj0lu6hzndep9z5grgq9haqlc25k0 +COCONUT_DKG_CONTRACT_ADDRESS=n1pk8jgr6y4c5k93gz7qf3xc0hvygmp7csk88c2tf8l39tkq6834wq2a6dtr VESTING_CONTRACT_ADDRESS=n1jlzdxnyces4hrhqz68dqk28mrw5jgwtcfq0c2funcwrmw0dx9l9s8nnnvj REWARDING_VALIDATOR_ADDRESS=n1rfvpsynktze6wvn6ldskj8xgwfzzk5v6pnff39 EXPLORER_API=https://qa-network-explorer.qa.nymte.ch/api NYXD="https://qa-validator.qa.nymte.ch" NYM_API="https://qa-nym-api.qa.nymte.ch/api" - -DKG_TIME_CONFIGURATION="600,300,300,60,60,1209600" -EXIT_POLICY="https://nymtech.net/.wellknown/network-requester/exit-policy.txt" diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 0ab11f2859..62ff0ad277 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.39" +version = "1.1.40" edition = "2021" license.workspace = true diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index b785e7bdb5..c15a273022 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -27,7 +27,7 @@ use nym_gateway_storage::{error::StorageError, Storage}; use nym_sphinx::forwarding::packet::MixPacket; use nym_task::TaskClient; use nym_validator_client::coconut::EcashApiError; -use rand::{CryptoRng, Rng}; +use rand::{random, CryptoRng, Rng}; use std::{process, time::Duration}; use thiserror::Error; use tokio::io::{AsyncRead, AsyncWrite}; @@ -236,11 +236,7 @@ where enc_credential: Vec, iv: Vec, ) -> Result { - // TODO: change it into a span field instead once we move to tracing - debug!( - "handling e-cash bandwidth request from {}", - self.client.address - ); + debug!("handling e-cash bandwidth request"); let credential = ClientControlRequest::try_from_enc_ecash_credential( enc_credential, @@ -253,7 +249,11 @@ where self.bandwidth_storage_manager.clone(), ); - let available_total = verifier.verify().await?; + let available_total = verifier + .verify() + .await + .inspect_err(|verification_failure| debug!("{verification_failure}"))?; + trace!("available total bandwidth: {available_total}"); Ok(ServerResponse::Bandwidth { available_total }) } @@ -340,20 +340,17 @@ where &mut self, ciphertext: Vec, nonce: Vec, - ) -> Message { + ) -> Result { let Ok(req) = ClientRequest::decrypt(&ciphertext, &nonce, &self.client.shared_keys) else { - return RequestHandlingError::InvalidEncryptedTextRequest.into_error_message(); + return Err(RequestHandlingError::InvalidEncryptedTextRequest); }; match req { ClientRequest::UpgradeKey { hkdf_salt, derived_key_digest, - } => self - .handle_key_upgrade(hkdf_salt, derived_key_digest) - .await - .into_ws_message(), - _ => RequestHandlingError::UnknownEncryptedTextRequest.into_error_message(), + } => self.handle_key_upgrade(hkdf_salt, derived_key_digest).await, + _ => Err(RequestHandlingError::UnknownEncryptedTextRequest), } } @@ -366,59 +363,64 @@ where /// * `raw_request`: raw message to handle. async fn handle_text(&mut self, raw_request: String) -> Message { trace!("text request"); - match ClientControlRequest::try_from(raw_request) { - Err(e) => RequestHandlingError::InvalidTextRequest(e).into_error_message(), - Ok(request) => match request { - ClientControlRequest::EncryptedRequest { ciphertext, nonce } => { - self.handle_encrypted_text_request(ciphertext, nonce).await - } - ClientControlRequest::EcashCredential { enc_credential, iv } => self - .handle_ecash_bandwidth(enc_credential, iv) - .await - .into_ws_message(), - ClientControlRequest::BandwidthCredential { .. } => { - RequestHandlingError::IllegalRequest { - additional_context: "coconut credential are not longer supported".into(), - } - .into_error_message() - } - ClientControlRequest::BandwidthCredentialV2 { .. } => { - RequestHandlingError::IllegalRequest { - additional_context: "coconut credential are not longer supported".into(), - } - .into_error_message() - } - ClientControlRequest::ClaimFreeTestnetBandwidth => self - .bandwidth_storage_manager - .handle_claim_testnet_bandwidth() - .await - .map_err(|e| e.into()) - .into_ws_message(), - ClientControlRequest::SupportedProtocol { .. } => self - .inner - .handle_supported_protocol_request() - .into_ws_message(), - other @ ClientControlRequest::Authenticate { .. } => { - RequestHandlingError::IllegalRequest { - additional_context: format!( - "received illegal message of type {} in an authenticated client", - other.name() - ), - } - .into_error_message() - } - other @ ClientControlRequest::RegisterHandshakeInitRequest { .. } => { - RequestHandlingError::IllegalRequest { - additional_context: format!( - "received illegal message of type {} in an authenticated client", - other.name() - ), - } - .into_error_message() - } - _ => RequestHandlingError::UnknownTextRequest.into_error_message(), - }, + + let request = match ClientControlRequest::try_from(raw_request) { + Ok(req) => { + debug!("received request of type {}", req.name()); + req + } + Err(err) => { + debug!("request was malformed: {err}"); + return RequestHandlingError::InvalidTextRequest(err).into_error_message(); + } + }; + + match request { + ClientControlRequest::EncryptedRequest { ciphertext, nonce } => { + self.handle_encrypted_text_request(ciphertext, nonce).await + } + ClientControlRequest::EcashCredential { enc_credential, iv } => { + self.handle_ecash_bandwidth(enc_credential, iv).await + } + ClientControlRequest::BandwidthCredential { .. } => { + Err(RequestHandlingError::IllegalRequest { + additional_context: "coconut credential are not longer supported".into(), + }) + } + ClientControlRequest::BandwidthCredentialV2 { .. } => { + Err(RequestHandlingError::IllegalRequest { + additional_context: "coconut credential are not longer supported".into(), + }) + } + ClientControlRequest::ClaimFreeTestnetBandwidth => self + .bandwidth_storage_manager + .handle_claim_testnet_bandwidth() + .await + .map_err(|e| e.into()), + ClientControlRequest::SupportedProtocol { .. } => { + Ok(self.inner.handle_supported_protocol_request()) + } + other @ ClientControlRequest::Authenticate { .. } => { + Err(RequestHandlingError::IllegalRequest { + additional_context: format!( + "received illegal message of type {} in an authenticated client", + other.name() + ), + }) + } + other @ ClientControlRequest::RegisterHandshakeInitRequest { .. } => { + Err(RequestHandlingError::IllegalRequest { + additional_context: format!( + "received illegal message of type {} in an authenticated client", + other.name() + ), + }) + } + _ => Err(RequestHandlingError::UnknownTextRequest), } + .inspect(|res| debug!(response = ?res, "success")) + .inspect_err(|err| debug!(error = %err, "failure")) + .into_ws_message() } /// Handles pong message received from the client. @@ -452,12 +454,13 @@ where /// # Arguments /// /// * `raw_request`: raw received websocket message. + #[instrument(level = "debug", skip_all, + fields( + client = %self.client.address.as_base58_string() + ) + )] async fn handle_request(&mut self, raw_request: Message) -> Option { - // TODO: this should be added via tracing - debug!( - "handling request from {}", - self.client.address.as_base58_string() - ); + trace!("new request"); // apparently tungstenite auto-handles ping/pong/close messages so for now let's ignore // them and let's test that claim. If that's not the case, just copy code from @@ -478,8 +481,8 @@ where where S: AsyncRead + AsyncWrite + Unpin, { - let tag: u64 = rand::thread_rng().gen(); - debug!("Got request to ping our connection: {}", tag); + let tag: u64 = random(); + debug!("got request to ping our connection: {tag}"); self.inner .send_websocket_message(Message::Ping(tag.to_be_bytes().to_vec())) .await?; diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index ddc266342f..9563f89cbc 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -420,7 +420,7 @@ where // we can't handle clients with higher protocol than ours // (perhaps we could try to negotiate downgrade on our end? sounds like a nice future improvement) if client_protocol_version <= CURRENT_PROTOCOL_VERSION { - info!("the client is using exactly the same (or older) protocol version as we are. We're good to continue!"); + debug!("the client is using exactly the same (or older) protocol version as we are. We're good to continue!"); Ok(CURRENT_PROTOCOL_VERSION) } else { let err = InitialAuthenticationError::IncompatibleProtocol { diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 4b2f7f776e..6f23a04747 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-api" license = "GPL-3.0" -version = "1.1.43" +version = "1.1.44" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index f33229d4bb..36af98832e 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node" -version = "1.1.7" +version = "1.1.8" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index f949feda83..3813f1ba4c 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -538,7 +538,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.22", + "semver", "serde", "serde_json", "thiserror", @@ -1141,7 +1141,7 @@ dependencies = [ "cosmwasm-std", "cw2", "schemars", - "semver 1.0.22", + "semver", "serde", "thiserror", ] @@ -1156,7 +1156,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus", "schemars", - "semver 1.0.22", + "semver", "serde", "thiserror", ] @@ -2158,7 +2158,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.0.0", + "indexmap 2.5.0", "slab", "tokio", "tokio-util", @@ -2190,9 +2190,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" @@ -2559,12 +2559,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.5", "serde", ] @@ -3115,7 +3115,7 @@ dependencies = [ "log", "pretty_env_logger", "schemars", - "semver 0.11.0", + "semver", "serde", "utoipa", "vergen", @@ -3173,7 +3173,7 @@ dependencies = [ "log", "nym-network-defaults", "serde", - "toml 0.7.6", + "toml 0.8.19", "url", ] @@ -3517,12 +3517,9 @@ dependencies = [ "base64 0.22.1", "log", "nym-config", - "nym-crypto", "nym-network-defaults", "serde", - "serde_json", "thiserror", - "utoipa", "x25519-dalek", ] @@ -4555,7 +4552,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.22", + "semver", ] [[package]] @@ -4798,31 +4795,13 @@ dependencies = [ [[package]] name = "semver" -version = "0.11.0" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - [[package]] name = "serde" version = "1.0.210" @@ -4897,9 +4876,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -5450,7 +5429,7 @@ dependencies = [ "raw-window-handle", "regex", "rfd", - "semver 1.0.22", + "semver", "serde", "serde_json", "serde_repr", @@ -5483,7 +5462,7 @@ dependencies = [ "cargo_toml", "heck 0.4.1", "json-patch", - "semver 1.0.22", + "semver", "serde_json", "tauri-utils", "winres", @@ -5504,7 +5483,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "semver 1.0.22", + "semver", "serde", "serde_json", "sha2 0.10.8", @@ -5587,7 +5566,7 @@ dependencies = [ "phf 0.10.1", "proc-macro2", "quote", - "semver 1.0.22", + "semver", "serde", "serde_json", "serde_with", @@ -5682,7 +5661,7 @@ dependencies = [ "serde", "serde_json", "tendermint 0.37.0", - "toml 0.8.2", + "toml 0.8.19", "url", ] @@ -5735,7 +5714,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "reqwest 0.11.22", - "semver 1.0.22", + "semver", "serde", "serde_bytes", "serde_json", @@ -5954,21 +5933,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.2" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.2", + "toml_edit 0.22.22", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -5979,24 +5958,24 @@ version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.5.10", ] [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.20", ] [[package]] @@ -6213,7 +6192,7 @@ version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.5.0", "serde", "serde_json", "utoipa-gen", @@ -6870,6 +6849,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index e449911693..325debcc48 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-network-requester" license = "GPL-3.0" -version = "1.1.41" +version = "1.1.42" authors.workspace = true edition.workspace = true rust-version = "1.70" diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index f930eba02b..8f272430d2 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.41" +version = "1.1.42" authors.workspace = true edition = "2021" license.workspace = true diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml index 6fb6a36e5e..f82494c850 100644 --- a/tools/nymvisor/Cargo.toml +++ b/tools/nymvisor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nymvisor" -version = "0.1.6" +version = "0.1.7" authors.workspace = true repository.workspace = true homepage.workspace = true