diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c888a8eb2..c7c85b9918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,86 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [2025.2-hu] (2025-02-04) + +- Feature/remove double spending bloomfilter ([#5417]) +- HU - Downgrade harmless log message from info to debug ([#5405]) +- lower default ticket verification quorum to 0.7 ([#5404]) +- Downgrade harmless log message from info to debug ([#5403]) +- Redirect from mixnode page to nodes page ([#5397]) +- chore :update version of chain watcher and validator rewarder ([#5394]) +- bugfix: correctly handle ingore epoch roles flag ([#5390]) +- bugfix: terminate mixnet socket listener on shutdown ([#5389]) +- feat: make client ignore dual mode nodes by default ([#5388]) +- Handle ecash network errors differently ([#5378]) +- Remove empty ephemeral keys ([#5376]) +- fixed sql migration for adding default message timestamp ([#5374]) +- Bind to [::] on nym-node for both IP versions ([#5361]) +- exposed NymApiClient method for obtaining node performance history ([#5360]) +- Client gateway selection ([#5358]) +- chore: refresh wasm sdk ([#5353]) +- chore: update indexed_db_futures ([#5347]) +- build(deps): bump mikefarah/yq from 4.44.6 to 4.45.1 ([#5342]) +- updated cosmrs and tendermint-rpc to their most recent versions ([#5339]) +- build(deps): bump ts-rs from 10.0.0 to 10.1.0 ([#5338]) +- build(deps): bump tempfile from 3.14.0 to 3.15.0 ([#5337]) +- build(deps): bump the patch-updates group with 8 updates ([#5336]) +- feature: introduce /load endpoint for self-reported quantised NymNode load ([#5326]) +- feature: `CancellationToken`-based shutdowns ([#5325]) +- Use expect in geodata test to give error message on failure ([#5314]) +- feature: periodically remove stale gateway messages ([#5312]) +- build(deps): bump the patch-updates group across 1 directory with 35 updates ([#5310]) +- Add dependabot assignes for the root cargo ecosystem ([#5297]) +- Move tun constants to network defaults ([#5286]) +- Include IPINFO_API_TOKEN in nightly CI ([#5285]) +- Nyx Chain Watcher ([#5274]) +- bugfix: remove unnecessary arguments for nym-api swagger endpoints ([#5272]) +- feature: nym topology revamp ([#5271]) +- Add windows to CI builds ([#5269]) +- http-api-client: deduplicate code ([#5267]) +- build(deps): bump http from 1.1.0 to 1.2.0 ([#5228]) +- NS API: add mixnet scraper ([#5200]) +- build(deps): bump criterion from 0.4.0 to 0.5.1 ([#4911]) + +[#5417]: https://github.com/nymtech/nym/pull/5417 +[#5405]: https://github.com/nymtech/nym/pull/5405 +[#5404]: https://github.com/nymtech/nym/pull/5404 +[#5403]: https://github.com/nymtech/nym/pull/5403 +[#5397]: https://github.com/nymtech/nym/pull/5397 +[#5394]: https://github.com/nymtech/nym/pull/5394 +[#5390]: https://github.com/nymtech/nym/pull/5390 +[#5389]: https://github.com/nymtech/nym/pull/5389 +[#5388]: https://github.com/nymtech/nym/pull/5388 +[#5378]: https://github.com/nymtech/nym/pull/5378 +[#5376]: https://github.com/nymtech/nym/pull/5376 +[#5374]: https://github.com/nymtech/nym/pull/5374 +[#5361]: https://github.com/nymtech/nym/pull/5361 +[#5360]: https://github.com/nymtech/nym/pull/5360 +[#5358]: https://github.com/nymtech/nym/pull/5358 +[#5353]: https://github.com/nymtech/nym/pull/5353 +[#5347]: https://github.com/nymtech/nym/pull/5347 +[#5342]: https://github.com/nymtech/nym/pull/5342 +[#5339]: https://github.com/nymtech/nym/pull/5339 +[#5338]: https://github.com/nymtech/nym/pull/5338 +[#5337]: https://github.com/nymtech/nym/pull/5337 +[#5336]: https://github.com/nymtech/nym/pull/5336 +[#5326]: https://github.com/nymtech/nym/pull/5326 +[#5325]: https://github.com/nymtech/nym/pull/5325 +[#5314]: https://github.com/nymtech/nym/pull/5314 +[#5312]: https://github.com/nymtech/nym/pull/5312 +[#5310]: https://github.com/nymtech/nym/pull/5310 +[#5297]: https://github.com/nymtech/nym/pull/5297 +[#5286]: https://github.com/nymtech/nym/pull/5286 +[#5285]: https://github.com/nymtech/nym/pull/5285 +[#5274]: https://github.com/nymtech/nym/pull/5274 +[#5272]: https://github.com/nymtech/nym/pull/5272 +[#5271]: https://github.com/nymtech/nym/pull/5271 +[#5269]: https://github.com/nymtech/nym/pull/5269 +[#5267]: https://github.com/nymtech/nym/pull/5267 +[#5228]: https://github.com/nymtech/nym/pull/5228 +[#5200]: https://github.com/nymtech/nym/pull/5200 +[#4911]: https://github.com/nymtech/nym/pull/4911 + ## [2025.1-reeses] (2025-01-15) - Feture/legacy alert ([#5346]) diff --git a/Cargo.lock b/Cargo.lock index 73fc74c6f1..e650ab52e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -721,12 +721,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "bit-vec" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" - [[package]] name = "bitcoin_hashes" version = "0.11.0" @@ -822,17 +816,6 @@ dependencies = [ "generic-array 0.14.7", ] -[[package]] -name = "bloomfilter" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0bdbcf2078e0ba8a74e1fe0cf36f54054a04485759b61dfd60b174658e9607" -dependencies = [ - "bit-vec", - "getrandom", - "siphasher 1.0.1", -] - [[package]] name = "bls12_381" version = "0.8.0" @@ -2425,7 +2408,7 @@ dependencies = [ [[package]] name = "explorer-api" -version = "1.1.44" +version = "1.1.45" dependencies = [ "chrono", "clap", @@ -4738,7 +4721,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nym-api" -version = "1.1.48" +version = "1.1.49" dependencies = [ "anyhow", "async-trait", @@ -4747,7 +4730,6 @@ dependencies = [ "axum-test", "bincode", "bip39", - "bloomfilter", "bs58", "cfg-if", "clap", @@ -4778,7 +4760,6 @@ dependencies = [ "nym-crypto", "nym-dkg", "nym-ecash-contract-common", - "nym-ecash-double-spending", "nym-ecash-time", "nym-gateway-client", "nym-http-api-common", @@ -4988,7 +4969,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.46" +version = "1.1.47" dependencies = [ "anyhow", "base64 0.22.1", @@ -5071,7 +5052,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.46" +version = "1.1.47" dependencies = [ "bs58", "clap", @@ -5491,7 +5472,6 @@ dependencies = [ "nym-credentials", "nym-credentials-interface", "nym-ecash-contract-common", - "nym-ecash-double-spending", "nym-gateway-requests", "nym-gateway-storage", "nym-task", @@ -5608,15 +5588,6 @@ dependencies = [ "thiserror 2.0.11", ] -[[package]] -name = "nym-ecash-double-spending" -version = "0.1.0" -dependencies = [ - "bit-vec", - "bloomfilter", - "nym-network-defaults", -] - [[package]] name = "nym-ecash-time" version = "0.1.0" @@ -6140,7 +6111,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.47" +version = "1.1.48" dependencies = [ "addr", "anyhow", @@ -6191,7 +6162,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.3.1" +version = "1.4.0" dependencies = [ "anyhow", "arc-swap", @@ -6565,7 +6536,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.46" +version = "1.1.47" dependencies = [ "bs58", "clap", @@ -7008,7 +6979,7 @@ dependencies = [ [[package]] name = "nym-validator-rewarder" -version = "0.2.0" +version = "0.2.1" dependencies = [ "anyhow", "bip39", @@ -7170,7 +7141,7 @@ dependencies = [ [[package]] name = "nymvisor" -version = "0.1.11" +version = "0.1.12" dependencies = [ "anyhow", "bytes", @@ -7200,7 +7171,7 @@ dependencies = [ [[package]] name = "nyx-chain-watcher" -version = "0.1.8" +version = "0.1.9" dependencies = [ "anyhow", "async-trait", @@ -7667,7 +7638,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] [[package]] @@ -7676,7 +7647,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] [[package]] @@ -9290,12 +9261,6 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - [[package]] name = "slab" version = "0.4.9" @@ -11003,7 +10968,7 @@ checksum = "71dc8573a7b1ac4b71643d6da34888273ebfc03440c525121f1b3634ad3417a2" dependencies = [ "anyhow", "bytes", - "siphasher 0.3.11", + "siphasher", "uniffi_checksum_derive", ] diff --git a/Cargo.toml b/Cargo.toml index db440a3d77..f07836d73b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,6 @@ members = [ "common/credentials-interface", "common/crypto", "common/dkg", - "common/ecash-double-spending", "common/ecash-time", "common/execute", "common/exit-policy", diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 5f95bbb088..1ccddfa33a 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.46" +version = "1.1.47" 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 d538cd5b75..17a5e45bf0 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.46" +version = "1.1.47" 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/client-core/config-types/src/lib.rs b/common/client-core/config-types/src/lib.rs index ae9d5c0659..2a2993dda5 100644 --- a/common/client-core/config-types/src/lib.rs +++ b/common/client-core/config-types/src/lib.rs @@ -517,7 +517,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 +558,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 +599,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, } } } diff --git a/common/client-core/src/cli_helpers/client_add_gateway.rs b/common/client-core/src/cli_helpers/client_add_gateway.rs index 0205169521..a7f9f85d10 100644 --- a/common/client-core/src/cli_helpers/client_add_gateway.rs +++ b/common/client-core/src/cli_helpers/client_add_gateway.rs @@ -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? }; diff --git a/common/client-core/src/cli_helpers/client_init.rs b/common/client-core/src/cli_helpers/client_init.rs index 95ee336765..a81bee1c6d 100644 --- a/common/client-core/src/cli_helpers/client_init.rs +++ b/common/client-core/src/cli_helpers/client_init.rs @@ -175,6 +175,7 @@ where &core.client.nym_api_urls, user_agent, core.debug.topology.minimum_gateway_performance, + core.debug.topology.ignore_ingress_epoch_role, ) .await? }; diff --git a/common/client-core/src/client/base_client/storage/mod.rs b/common/client-core/src/client/base_client/storage/mod.rs index 5129a5fc41..5f24c8ef7f 100644 --- a/common/client-core/src/client/base_client/storage/mod.rs +++ b/common/client-core/src/client/base_client/storage/mod.rs @@ -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(), + } } } diff --git a/common/client-core/src/client/key_manager/persistence.rs b/common/client-core/src/client/key_manager/persistence.rs index f781cd3da6..42a0b6a19a 100644 --- a/common/client-core/src/client/key_manager/persistence.rs +++ b/common/client-core/src/client/key_manager/persistence.rs @@ -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>>, + keys: Arc>, +} + +impl InMemEphemeralKeys { + pub fn new(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 { - 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(()) } } diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index 1dabaa9d31..f29983bbf7 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -92,6 +92,7 @@ pub async fn gateways_for_init( nym_apis: &[Url], user_agent: Option, minimum_performance: u8, + ignore_epoch_roles: bool, ) -> Result, ClientCoreError> { let nym_api = nym_apis .choose(rng) @@ -113,7 +114,7 @@ pub async fn gateways_for_init( // (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::>(); diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index d3e2f2bf1a..e5a9a7ea8a 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -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 { - Ok(self.nym_api.double_spending_filter_v1().await?) - } - pub async fn partial_expiration_date_signatures( &self, expiration_date: Option, diff --git a/common/client-libs/validator-client/src/coconut/mod.rs b/common/client-libs/validator-client/src/coconut/mod.rs index 09ce560b1c..95270a25ed 100644 --- a/common/client-libs/validator-client/src/coconut/mod.rs +++ b/common/client-libs/validator-client/src/coconut/mod.rs @@ -65,6 +65,12 @@ pub enum EcashApiError { #[from] source: cosmrs::ErrorReport, }, + + #[error("nym api error")] + NymApi { + #[from] + source: crate::ValidatorClientError, + }, } impl TryFrom for EcashApiClient { diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index efbf613acc..e1bac67513 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -850,20 +850,6 @@ pub trait NymApiClientExt: ApiClient { .await } - #[deprecated] - #[instrument(level = "debug", skip(self))] - async fn double_spending_filter_v1(&self) -> Result { - 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, diff --git a/common/client-libs/validator-client/src/nym_api/routes.rs b/common/client-libs/validator-client/src/nym_api/routes.rs index 64481a5134..767acc0b8d 100644 --- a/common/client-libs/validator-client/src/nym_api/routes.rs +++ b/common/client-libs/validator-client/src/nym_api/routes.rs @@ -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::*; diff --git a/common/credential-verification/Cargo.toml b/common/credential-verification/Cargo.toml index 8585ad2cfa..a86b7da382 100644 --- a/common/credential-verification/Cargo.toml +++ b/common/credential-verification/Cargo.toml @@ -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" } diff --git a/common/credential-verification/src/ecash/credential_sender.rs b/common/credential-verification/src/ecash/credential_sender.rs index 140e3e4b4d..4d4a0b8fe6 100644 --- a/common/credential-verification/src/ecash/credential_sender.rs +++ b/common/credential-verification/src/ecash/credential_sender.rs @@ -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, + })) } } } diff --git a/common/ecash-double-spending/Cargo.toml b/common/ecash-double-spending/Cargo.toml deleted file mode 100644 index feb237f5d6..0000000000 --- a/common/ecash-double-spending/Cargo.toml +++ /dev/null @@ -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" } diff --git a/common/ecash-double-spending/src/lib.rs b/common/ecash-double-spending/src/lib.rs deleted file mode 100644 index 78f24dba38..0000000000 --- a/common/ecash-double-spending/src/lib.rs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// 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>, -} - -impl Default for DoubleSpendingFilter { - fn default() -> Self { - DoubleSpendingFilter::new_empty_ecash() - } -} - -pub fn bloom_from_params(params: &BloomfilterParameters, bitvec: BitVec) -> Bloom> { - 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 { - self.inner.bitmap() - } - - pub fn set(&mut self, b: &Vec) { - self.inner.set(b); - } - - pub fn check(&self, b: &Vec) -> bool { - self.inner.check(b) - } -} - -pub struct DoubleSpendingFilterBuilder { - params: BloomfilterParameters, - bit_vec_builder: Option, -} - -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 - } -} diff --git a/common/gateway-storage/migrations/20250106120000_add_timestamps.sql b/common/gateway-storage/migrations/20250106120000_add_timestamps.sql index 6c3b3bcf85..0fcd2cef73 100644 --- a/common/gateway-storage/migrations/20250106120000_add_timestamps.sql +++ b/common/gateway-storage/migrations/20250106120000_add_timestamps.sql @@ -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; + diff --git a/common/wasm/client-core/src/config/mod.rs b/common/wasm/client-core/src/config/mod.rs index 5c20f832d3..ebc07e413c 100644 --- a/common/wasm/client-core/src/config/mod.rs +++ b/common/wasm/client-core/src/config/mod.rs @@ -395,6 +395,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 +423,7 @@ impl From 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 +441,7 @@ impl From 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, } } } diff --git a/common/wasm/client-core/src/config/override.rs b/common/wasm/client-core/src/config/override.rs index a1a10769fa..418750266f 100644 --- a/common/wasm/client-core/src/config/override.rs +++ b/common/wasm/client-core/src/config/override.rs @@ -281,6 +281,11 @@ pub struct TopologyWasmOverride { /// when constructing the final hop packets. #[tsify(optional)] pub ignore_egress_epoch_role: Option, + + /// 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, } impl From for TopologyWasm { @@ -310,6 +315,9 @@ impl From 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), } } } diff --git a/common/wasm/client-core/src/helpers.rs b/common/wasm/client-core/src/helpers.rs index 9e9078be04..411c05f1c8 100644 --- a/common/wasm/client-core/src/helpers.rs +++ b/common/wasm/client-core/src/helpers.rs @@ -132,9 +132,17 @@ pub async fn setup_gateway_from_api( chosen_gateway: Option, nym_apis: &[Url], minimum_performance: u8, + ignore_epoch_roles: bool, ) -> Result { 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, minimum_performance: u8, + ignore_epoch_roles: bool, ) -> Result, 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, latency_based_selection: Option, @@ -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 diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index fd2880c4fd..e42af8fcbf 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.44" +version = "1.1.45" edition = "2021" license.workspace = true diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 1711f5921a..98f24e5106 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.48" +version = "1.1.49" 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" } diff --git a/nym-api/migrations/20250131120000_remove_bloomfilters.sql b/nym-api/migrations/20250131120000_remove_bloomfilters.sql new file mode 100644 index 0000000000..59a4d457f6 --- /dev/null +++ b/nym-api/migrations/20250131120000_remove_bloomfilters.sql @@ -0,0 +1,7 @@ +/* + * Copyright 2025 - Nym Technologies SA + * SPDX-License-Identifier: GPL-3.0-only + */ + +DROP TABLE partial_bloomfilter; +DROP TABLE bloomfilter_parameters; \ No newline at end of file diff --git a/nym-api/src/ecash/api_routes/spending.rs b/nym-api/src/ecash/api_routes/spending.rs index 873d9044d0..c0fd546ef6 100644 --- a/nym-api/src/ecash/api_routes/spending.rs +++ b/nym-api/src/ecash/api_routes/spending.rs @@ -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> { - AxumResult::Err(AxumErrorResponse::internal_msg("permanently restricted")) + AxumResult::Err(AxumErrorResponse::new( + "permanently restricted", + StatusCode::GONE, + )) } diff --git a/nym-api/src/ecash/state/bloom.rs b/nym-api/src/ecash/state/bloom.rs deleted file mode 100644 index 939f19b3a9..0000000000 --- a/nym-api/src/ecash/state/bloom.rs +++ /dev/null @@ -1,2 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only diff --git a/nym-api/src/ecash/state/cleaner.rs b/nym-api/src/ecash/state/cleaner.rs new file mode 100644 index 0000000000..4b4c242e35 --- /dev/null +++ b/nym-api/src/ecash/state/cleaner.rs @@ -0,0 +1,89 @@ +// Copyright 2025 - Nym Technologies SA +// 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 }) + } +} diff --git a/nym-api/src/ecash/state/helpers.rs b/nym-api/src/ecash/state/helpers.rs index b96ab53099..516aa19a4f 100644 --- a/nym-api/src/ecash/state/helpers.rs +++ b/nym-api/src/ecash/state/helpers.rs @@ -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 { - 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 { - 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 { - 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(); diff --git a/nym-api/src/ecash/state/local.rs b/nym-api/src/ecash/state/local.rs index cf09e050f6..61b5bab2c4 100644 --- a/nym-api/src/ecash/state/local.rs +++ b/nym-api/src/ecash/state/local.rs @@ -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) -> 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) -> 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) -> bool { - let existed = self.global_filter.check(sn); - self.global_filter.set(sn); - existed - } - - pub(crate) fn export_today_bitmap(&self) -> Vec { - 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, - // the actual, up to date, bloomfilter - pub(crate) double_spending_filter: Arc>, - // merkle trees for ticketbooks issued for particular expiration dates pub(crate) issued_merkle_trees: Arc>>, } @@ -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()), } } diff --git a/nym-api/src/ecash/state/mod.rs b/nym-api/src/ecash/state/mod.rs index 3e716d08f4..5d3c6412af 100644 --- a/nym-api/src/ecash/state/mod.rs +++ b/nym-api/src/ecash/state/mod.rs @@ -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( + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( global_config: &Config, contract_address: AccountId, client: C, @@ -107,24 +119,38 @@ impl EcashState { key_pair: KeyPair, comm_channel: D, storage: NymApiStorage, - ) -> Result + 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 { 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) -> 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, - ) -> 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, - spending_date: Date, - today: Date, - ) -> Result { - 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 - } } diff --git a/nym-api/src/ecash/storage/manager.rs b/nym-api/src/ecash/storage/manager.rs index 7e49215c70..aadb48d7bf 100644 --- a/nym-api/src/ecash/storage/manager.rs +++ b/nym-api/src/ecash/storage/manager.rs @@ -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, 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, serial_number: Vec, - ) -> Result<(), sqlx::Error>; + ) -> Result; async fn get_ticket(&self, serial_number: &[u8]) -> Result, 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; - - async fn get_latest_double_spending_filter_params( - &self, - ) -> Result, 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>, 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, serial_number: Vec, - ) -> Result<(), sqlx::Error> { - sqlx::query!( + ) -> Result { + 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 { - 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, 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>, 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) diff --git a/nym-api/src/ecash/storage/mod.rs b/nym-api/src/ecash/storage/mod.rs index 2d0d802f06..b0655d7823 100644 --- a/nym-api/src/ecash/storage/mod.rs +++ b/nym-api/src/ecash/storage/mod.rs @@ -14,7 +14,6 @@ use async_trait::async_trait; use nym_coconut_dkg_common::types::EpochId; use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature; use nym_compact_ecash::{BlindedSignature, VerificationKeyAuth}; -use nym_config::defaults::BloomfilterParameters; use nym_credentials::CredentialSpendingData; use nym_credentials_interface::TicketType; use nym_ecash_contract_common::deposit::DepositId; @@ -22,7 +21,7 @@ use nym_ticketbooks_merkle::{IssuedTicketbook, MerkleLeaf}; use nym_validator_client::nyxd::AccountId; use std::collections::HashSet; use time::{Date, OffsetDateTime}; -use tracing::{info, warn}; +use tracing::warn; mod helpers; pub(crate) mod manager; @@ -30,32 +29,6 @@ pub(crate) mod models; #[async_trait] pub trait EcashStorageExt { - async fn get_double_spending_filter_params( - &self, - ) -> Result<(i64, BloomfilterParameters), NymApiStorageError>; - - async fn update_archived_partial_bloomfilter( - &self, - date: Date, - new_bitmap: &[u8], - ) -> Result<(), NymApiStorageError>; - - async fn try_load_partial_bloomfilter_bitmap( - &self, - date: Date, - params_id: i64, - ) -> Result>, NymApiStorageError>; - - async fn insert_partial_bloomfilter( - &self, - date: Date, - params_id: i64, - bitmap: &[u8], - ) -> Result<(), NymApiStorageError>; - - async fn remove_old_partial_bloomfilters(&self, cutoff: Date) - -> Result<(), NymApiStorageError>; - async fn remove_expired_verified_tickets(&self, cutoff: Date) -> Result<(), NymApiStorageError>; @@ -96,11 +69,12 @@ pub trait EcashStorageExt { serial_number: &[u8], ) -> Result, NymApiStorageError>; + /// Returns a boolean to indicate whether the ticket has actually been inserted async fn store_verified_ticket( &self, ticket_data: &CredentialSpendingData, gateway_addr: &AccountId, - ) -> Result<(), NymApiStorageError>; + ) -> Result; async fn get_ticket_provider( &self, @@ -118,6 +92,7 @@ pub trait EcashStorageExt { last_batch_verification: OffsetDateTime, ) -> Result<(), NymApiStorageError>; + #[allow(dead_code)] async fn get_all_spent_tickets_on( &self, date: Date, @@ -181,75 +156,6 @@ pub trait EcashStorageExt { #[async_trait] impl EcashStorageExt for NymApiStorage { - async fn get_double_spending_filter_params( - &self, - ) -> Result<(i64, BloomfilterParameters), NymApiStorageError> { - match self - .manager - .get_latest_double_spending_filter_params() - .await? - { - Some(raw) => Ok((raw.id, (&raw).try_into()?)), - None => { - let default = BloomfilterParameters::default_ecash(); - info!("using default bloomfilter parameters: {default:?}"); - let id = self - .manager - .insert_double_spending_filter_params( - default.num_hashes, - default.bitmap_size as u32, - &default.sip_keys[0].0.to_be_bytes(), - &default.sip_keys[0].1.to_be_bytes(), - &default.sip_keys[1].0.to_be_bytes(), - &default.sip_keys[1].1.to_be_bytes(), - ) - .await?; - Ok((id, default)) - } - } - } - - async fn update_archived_partial_bloomfilter( - &self, - date: Date, - new_bitmap: &[u8], - ) -> Result<(), NymApiStorageError> { - Ok(self - .manager - .update_archived_partial_bloomfilter(date, new_bitmap) - .await?) - } - - async fn try_load_partial_bloomfilter_bitmap( - &self, - date: Date, - params_id: i64, - ) -> Result>, NymApiStorageError> { - Ok(self - .manager - .try_load_partial_bloomfilter_bitmap(date, params_id) - .await?) - } - - async fn insert_partial_bloomfilter( - &self, - date: Date, - params_id: i64, - bitmap: &[u8], - ) -> Result<(), NymApiStorageError> { - Ok(self - .manager - .insert_partial_bloomfilter(date, params_id, bitmap) - .await?) - } - - async fn remove_old_partial_bloomfilters( - &self, - cutoff: Date, - ) -> Result<(), NymApiStorageError> { - Ok(self.manager.remove_old_partial_bloomfilters(cutoff).await?) - } - async fn remove_expired_verified_tickets( &self, cutoff: Date, @@ -354,11 +260,12 @@ impl EcashStorageExt for NymApiStorage { .transpose() } + /// Returns a boolean to indicate whether the ticket has actually been inserted async fn store_verified_ticket( &self, ticket_data: &CredentialSpendingData, gateway_addr: &AccountId, - ) -> Result<(), NymApiStorageError> { + ) -> Result { let provider_id = self .get_or_create_ticket_provider_with_id(gateway_addr.as_ref()) .await?; @@ -411,6 +318,7 @@ impl EcashStorageExt for NymApiStorage { .await?) } + #[allow(dead_code)] async fn get_all_spent_tickets_on( &self, date: Date, diff --git a/nym-api/src/ecash/storage/models.rs b/nym-api/src/ecash/storage/models.rs index 4c084ad707..a07b1f5e74 100644 --- a/nym-api/src/ecash/storage/models.rs +++ b/nym-api/src/ecash/storage/models.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::node_status_api::models::NymApiStorageError; -use nym_config::defaults::BloomfilterParameters; use nym_credentials_interface::TicketType; use nym_ecash_contract_common::deposit::DepositId; use nym_ticketbooks_merkle::IssuedTicketbook; @@ -89,50 +88,3 @@ pub struct RawExpirationDateSignatures { pub epoch_id: u32, pub serialised_signatures: Vec, } - -#[derive(FromRow)] -pub(crate) struct StoredBloomfilterParams { - pub(crate) id: i64, - pub(crate) num_hashes: u32, - pub(crate) bitmap_size: u32, - - pub(crate) sip0_key0: Vec, - pub(crate) sip0_key1: Vec, - - pub(crate) sip1_key0: Vec, - pub(crate) sip1_key1: Vec, -} - -impl<'a> TryFrom<&'a StoredBloomfilterParams> for BloomfilterParameters { - type Error = NymApiStorageError; - fn try_from(value: &'a StoredBloomfilterParams) -> Result { - let Ok(sip0_key0) = <[u8; 8]>::try_from(value.sip0_key0.as_ref()) else { - return Err(NymApiStorageError::database_inconsistency( - "malformed sip0 key0", - )); - }; - let Ok(sip0_key1) = <[u8; 8]>::try_from(value.sip0_key1.as_ref()) else { - return Err(NymApiStorageError::database_inconsistency( - "malformed sip0 key1", - )); - }; - let Ok(sip1_key0) = <[u8; 8]>::try_from(value.sip1_key0.as_ref()) else { - return Err(NymApiStorageError::database_inconsistency( - "malformed sip1 key0", - )); - }; - let Ok(sip1_key1) = <[u8; 8]>::try_from(value.sip1_key1.as_ref()) else { - return Err(NymApiStorageError::database_inconsistency( - "malformed sip1 key1", - )); - }; - Ok(BloomfilterParameters { - num_hashes: value.num_hashes, - bitmap_size: value.bitmap_size as u64, - sip_keys: [ - (u64::from_be_bytes(sip0_key0), u64::from_be_bytes(sip0_key1)), - (u64::from_be_bytes(sip1_key0), u64::from_be_bytes(sip1_key1)), - ], - }) - } -} diff --git a/nym-api/src/ecash/tests/mod.rs b/nym-api/src/ecash/tests/mod.rs index 9af4c78052..9a71802f5c 100644 --- a/nym-api/src/ecash/tests/mod.rs +++ b/nym-api/src/ecash/tests/mod.rs @@ -54,6 +54,7 @@ use nym_crypto::asymmetric::identity; use nym_dkg::{NodeIndex, Threshold}; use nym_ecash_contract_common::blacklist::{BlacklistedAccountResponse, Blacklisting}; use nym_ecash_contract_common::deposit::{Deposit, DepositId, DepositResponse}; +use nym_task::TaskClient; use nym_validator_client::nym_api::routes::{ API_VERSION, ECASH_BLIND_SIGN, ECASH_ISSUED_TICKETBOOKS_CHALLENGE, ECASH_ISSUED_TICKETBOOKS_FOR, ECASH_ROUTES, @@ -1333,9 +1334,8 @@ impl TestFixture { staged_key_pair, comm_channel, storage.clone(), - ) - .await - .unwrap(); + TaskClient::dummy(), + ); TestFixture { axum: TestServer::new( @@ -1459,6 +1459,7 @@ mod credential_tests { use super::*; use crate::ecash::storage::EcashStorageExt; use axum::http::StatusCode; + use nym_task::TaskClient; use nym_ticketbooks_merkle::MerkleLeaf; #[tokio::test] @@ -1547,9 +1548,8 @@ mod credential_tests { staged_key_pair, comm_channel, storage.clone(), - ) - .await - .unwrap(); + TaskClient::dummy(), + ); let deposit_id = 42; assert!(state.already_issued(deposit_id).await.unwrap().is_none()); diff --git a/nym-api/src/node_status_api/models.rs b/nym-api/src/node_status_api/models.rs index 2e33fd8025..19120e88b8 100644 --- a/nym-api/src/node_status_api/models.rs +++ b/nym-api/src/node_status_api/models.rs @@ -329,6 +329,13 @@ pub(crate) struct AxumErrorResponse { } impl AxumErrorResponse { + pub(crate) fn new>(message: S, status: StatusCode) -> Self { + AxumErrorResponse { + message: RequestError::new(message), + status, + } + } + pub(crate) fn internal_msg(msg: impl Display) -> Self { Self { message: RequestError::new(msg.to_string()), diff --git a/nym-api/src/support/cli/run.rs b/nym-api/src/support/cli/run.rs index fa229d6425..c506d32508 100644 --- a/nym-api/src/support/cli/run.rs +++ b/nym-api/src/support/cli/run.rs @@ -106,6 +106,8 @@ pub(crate) struct Args { } async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result { + let task_manager = TaskManager::new(TASK_MANAGER_TIMEOUT_S); + let nyxd_client = nyxd::Client::new(config); let connected_nyxd = config.get_nyxd_url(); let nym_network_details = NymNetworkDetails::new_from_env(); @@ -148,7 +150,7 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result anyhow::Result anyhow::Result anyhow::Result Self { EcashSignerDebug { - dkg_contract_polling_rate: DEFAULT_DKG_CONTRACT_POLLING_RATE, - issued_ticketbooks_retention_period_days: DEFAULT_MAX_ISSUED_TICKETBOOKS_RETENTION_DAYS, + dkg_contract_polling_rate: Self::DEFAULT_DKG_CONTRACT_POLLING_RATE, + stale_data_cleaner_interval: Self::DEFAULT_STALE_DATA_CLEANER_INTERVAL, + issued_ticketbooks_retention_period_days: + Self::DEFAULT_MAX_ISSUED_TICKETBOOKS_RETENTION_DAYS, + verified_tickets_retention_period_days: + Self::DEFAULT_VERIFIED_TICKETS_RETENTION_PERIOD_DAYS, } } } diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 865c8b7679..d65523f9ff 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node" -version = "1.3.1" +version = "1.4.0" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/nym-node/src/config/gateway_tasks.rs b/nym-node/src/config/gateway_tasks.rs index 5000115967..9f814f2bf5 100644 --- a/nym-node/src/config/gateway_tasks.rs +++ b/nym-node/src/config/gateway_tasks.rs @@ -97,7 +97,7 @@ pub struct ZkNymTicketHandlerDebug { impl ZkNymTicketHandlerDebug { pub const DEFAULT_REVOCATION_BANDWIDTH_PENALTY: f32 = 10.0; pub const DEFAULT_PENDING_POLLER: Duration = Duration::from_secs(300); - pub const DEFAULT_MINIMUM_API_QUORUM: f32 = 0.8; + pub const DEFAULT_MINIMUM_API_QUORUM: f32 = 0.7; pub const DEFAULT_MINIMUM_REDEMPTION_TICKETS: usize = 100; // use min(4/5 of max validity, validity - 1), but making sure it's no greater than 1 day diff --git a/nym-node/src/node/mixnet/listener.rs b/nym-node/src/node/mixnet/listener.rs index 738f591c97..f5900414b0 100644 --- a/nym-node/src/node/mixnet/listener.rs +++ b/nym-node/src/node/mixnet/listener.rs @@ -5,7 +5,7 @@ use crate::node::mixnet::SharedData; use nym_task::ShutdownToken; use std::net::SocketAddr; use tokio::task::JoinHandle; -use tracing::{error, info, trace}; +use tracing::{debug, error, info, trace}; pub(crate) struct Listener { bind_address: SocketAddr, @@ -39,12 +39,14 @@ impl Listener { biased; _ = self.shutdown.cancelled() => { trace!("mixnet listener: received shutdown"); + break } connection = tcp_listener.accept() => { self.shared_data.try_handle_connection(connection); } } } + debug!("mixnet socket listener: Exiting"); } pub(crate) fn start(mut self) -> JoinHandle<()> { diff --git a/nym-validator-rewarder/Cargo.toml b/nym-validator-rewarder/Cargo.toml index a70f19bf09..4d875d0e62 100644 --- a/nym-validator-rewarder/Cargo.toml +++ b/nym-validator-rewarder/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-validator-rewarder" -version = "0.2.0" +version = "0.2.1" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index e7c1e7e72b..3c90119e0b 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -4223,7 +4223,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.96", ] [[package]] diff --git a/nyx-chain-watcher/Cargo.toml b/nyx-chain-watcher/Cargo.toml index 592f03c7fe..c0cca19041 100644 --- a/nyx-chain-watcher/Cargo.toml +++ b/nyx-chain-watcher/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nyx-chain-watcher" -version = "0.1.8" +version = "0.1.9" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -27,15 +27,15 @@ nym-node-requests = { path = "../nym-node/nym-node-requests", features = [ "openapi", ] } nym-validator-client = { path = "../common/client-libs/validator-client" } -nyxd-scraper = {path = "../common/nyxd-scraper"} -reqwest = {workspace= true, features = ["rustls-tls"]} +nyxd-scraper = { path = "../common/nyxd-scraper" } +reqwest = { workspace = true, features = ["rustls-tls"] } rocket = { workspace = true } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "time"] } thiserror = { workspace = true } -time = {version = "0.3.37"} +time = { version = "0.3.37" } tokio = { workspace = true, features = ["process", "rt-multi-thread"] } tokio-util = { workspace = true } tracing = { workspace = true } diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index af0f059cfa..3c3a9a2eb0 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -489,7 +489,7 @@ where user_chosen_gateway: &str, ) -> Result { let storage = self.storage.gateway_details_store(); - // Stricly speaking, `set_active_gateway` does this check internally as well, but since the + // Strictly speaking, `set_active_gateway` does this check internally as well, but since the // error is boxed away and we're using a generic storage, it's not so easy to match on it. // This function is at least less likely to fail on something unrelated to the existence of // the gateway in the set of registered gateways @@ -512,15 +512,14 @@ where let user_agent = self.user_agent.clone(); + let topology_cfg = &self.config.debug_config.topology; let mut rng = OsRng; let available_gateways = gateways_for_init( &mut rng, &nym_api_endpoints, user_agent, - self.config - .debug_config - .topology - .minimum_gateway_performance, + topology_cfg.minimum_gateway_performance, + topology_cfg.ignore_ingress_epoch_role, ) .await?; diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 2f14ad8021..a3a0dccd3f 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.47" +version = "1.1.48" 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 8b4583dcbc..0eb2adea0d 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.46" +version = "1.1.47" authors.workspace = true edition = "2021" license.workspace = true diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml index d1cde0c60d..71ab3209d8 100644 --- a/tools/nymvisor/Cargo.toml +++ b/tools/nymvisor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nymvisor" -version = "0.1.11" +version = "0.1.12" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/wasm/client/src/client.rs b/wasm/client/src/client.rs index f7c23bdad8..338532b097 100644 --- a/wasm/client/src/client.rs +++ b/wasm/client/src/client.rs @@ -217,6 +217,7 @@ impl NymClientBuilder { &self.config.base.client.nym_api_urls, bin_info!().into(), self.config.base.debug.topology.minimum_gateway_performance, + self.config.base.debug.topology.ignore_ingress_epoch_role, &client_store, ) .await?; diff --git a/wasm/mix-fetch/src/client.rs b/wasm/mix-fetch/src/client.rs index b513cb3ca9..fa7e284fb3 100644 --- a/wasm/mix-fetch/src/client.rs +++ b/wasm/mix-fetch/src/client.rs @@ -156,6 +156,7 @@ impl MixFetchClientBuilder { &self.config.base.client.nym_api_urls, bin_info!().into(), self.config.base.debug.topology.minimum_gateway_performance, + self.config.base.debug.topology.ignore_ingress_epoch_role, &client_store, ) .await?;