diff --git a/Cargo.lock b/Cargo.lock index 4bf784f6cf..8d4d22f6eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -720,12 +720,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" @@ -821,17 +815,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" @@ -4635,7 +4618,6 @@ dependencies = [ "axum-test", "bincode", "bip39", - "bloomfilter", "bs58", "cfg-if", "clap", @@ -4666,7 +4648,6 @@ dependencies = [ "nym-crypto", "nym-dkg", "nym-ecash-contract-common", - "nym-ecash-double-spending", "nym-ecash-time", "nym-gateway-client", "nym-http-api-common", @@ -5377,7 +5358,6 @@ dependencies = [ "nym-credentials", "nym-credentials-interface", "nym-ecash-contract-common", - "nym-ecash-double-spending", "nym-gateway-requests", "nym-gateway-storage", "nym-task", @@ -5494,15 +5474,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[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" @@ -7549,7 +7520,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] [[package]] @@ -7558,7 +7529,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] [[package]] @@ -9144,12 +9115,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" @@ -10857,7 +10822,7 @@ checksum = "71dc8573a7b1ac4b71643d6da34888273ebfc03440c525121f1b3634ad3417a2" dependencies = [ "anyhow", "bytes", - "siphasher 0.3.11", + "siphasher", "uniffi_checksum_derive", ] diff --git a/Cargo.toml b/Cargo.toml index dc20a58404..a5d0b678aa 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/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/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index d790deda70..ca2d72e3dd 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -849,20 +849,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 ec14678279..a195b87da3 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/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/nym-api/Cargo.toml b/nym-api/Cargo.toml index 32485a9e48..98f24e5106 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -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, } } }