From 4989d47ea2ef740051c09c3f2d362a06e7ce4d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 30 Jul 2024 11:09:23 +0100 Subject: [PATCH] nym-api exporting bloomfilter in separate task --- nym-api/src/ecash/api_routes/spending.rs | 2 +- nym-api/src/ecash/state/local.rs | 50 +++++++++++++++++++++++- nym-api/src/ecash/state/mod.rs | 25 ++++-------- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/nym-api/src/ecash/api_routes/spending.rs b/nym-api/src/ecash/api_routes/spending.rs index 63cd4fc5a1..b5eedbcdbe 100644 --- a/nym-api/src/ecash/api_routes/spending.rs +++ b/nym-api/src/ecash/api_routes/spending.rs @@ -195,7 +195,7 @@ pub async fn batch_redeem_tickets( pub async fn double_spending_filter_v1( state: &RocketState, ) -> crate::ecash::error::Result> { - let spent_credentials_export = state.export_bloomfilter().await; + let spent_credentials_export = state.get_bloomfilter_bytes().await; Ok(Json(SpentCredentialsResponse::new( spent_credentials_export, ))) diff --git a/nym-api/src/ecash/state/local.rs b/nym-api/src/ecash/state/local.rs index 3f97c98a53..fd3b5080b1 100644 --- a/nym-api/src/ecash/state/local.rs +++ b/nym-api/src/ecash/state/local.rs @@ -9,8 +9,9 @@ use crate::ecash::keys::KeyPair; use nym_config::defaults::BloomfilterParameters; use nym_crypto::asymmetric::identity; use nym_ecash_double_spending::DoubleSpendingFilter; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use time::Date; +use time::{Date, OffsetDateTime}; use tokio::sync::RwLock; pub(crate) struct TicketDoubleSpendingFilter { @@ -80,6 +81,17 @@ impl TicketDoubleSpendingFilter { } } +pub(crate) struct ExportedDoubleSpendingFilterData { + pub(crate) last_exported_at: OffsetDateTime, + pub(crate) bytes: Vec, +} + +#[derive(Clone)] +pub(crate) struct ExportedDoubleSpendingFilter { + pub(crate) being_exported: Arc, + pub(crate) data: Arc>, +} + pub(crate) struct LocalEcashState { pub(crate) ecash_keypair: KeyPair, pub(crate) identity_keypair: identity::KeyPair, @@ -87,7 +99,12 @@ pub(crate) struct LocalEcashState { pub(crate) partial_coin_index_signatures: CachedImmutableEpochItem, pub(crate) partial_expiration_date_signatures: CachedImmutableItems, + + // the actual, up to date, bloomfilter pub(crate) double_spending_filter: Arc>, + + // the cached byte representation of the bloomfilter to be used by the clients + pub(crate) exported_double_spending_filter: ExportedDoubleSpendingFilter, } impl LocalEcashState { @@ -101,7 +118,38 @@ impl LocalEcashState { identity_keypair, partial_coin_index_signatures: Default::default(), partial_expiration_date_signatures: Default::default(), + exported_double_spending_filter: ExportedDoubleSpendingFilter { + being_exported: Arc::new(Default::default()), + data: Arc::new(RwLock::new(ExportedDoubleSpendingFilterData { + last_exported_at: OffsetDateTime::now_utc(), + bytes: double_spending_filter.export_global_bitmap(), + })), + }, double_spending_filter: Arc::new(RwLock::new(double_spending_filter)), } } + + pub(crate) fn maybe_background_update_exported_bloomfilter(&self) { + // make sure another query hasn't already spawned an exporting task + let Ok(should_export) = self + .exported_double_spending_filter + .being_exported + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + else { + return; + }; + + let filter = self.double_spending_filter.clone(); + let exported = self.exported_double_spending_filter.clone(); + + if should_export { + tokio::spawn(async move { + log::debug!("exporting bloomfilter bitmap"); + let new = filter.read().await.export_global_bitmap(); + let mut exported_guard = exported.data.write().await; + exported_guard.last_exported_at = OffsetDateTime::now_utc(); + exported_guard.bytes = new; + }); + } + } } diff --git a/nym-api/src/ecash/state/mod.rs b/nym-api/src/ecash/state/mod.rs index 9f0beca02e..1a80d86a18 100644 --- a/nym-api/src/ecash/state/mod.rs +++ b/nym-api/src/ecash/state/mod.rs @@ -45,7 +45,7 @@ use nym_ecash_time::cred_exp_date; use nym_validator_client::nyxd::AccountId; use nym_validator_client::EcashApiClient; use time::ext::NumericalDuration; -use time::{Date, OffsetDateTime}; +use time::{Date, Duration, OffsetDateTime}; use tokio::sync::RwLockReadGuard; use tokio::time::Instant; @@ -841,23 +841,14 @@ impl EcashState { res } - pub async fn export_bloomfilter(&self) -> Vec { - let export_start = Instant::now(); - let bytes = self - .local - .double_spending_filter - .read() - .await - .export_global_bitmap(); + pub async fn get_bloomfilter_bytes(&self) -> Vec { + let guard = self.local.exported_double_spending_filter.data.read().await; - let taken = export_start.elapsed(); - match taken.as_millis() { - // at that point it should somehow be cached; especially **IF** we have more reads than writes - ms if ms > 100 => error!("exporting the bloomfilter took {ms}ms to complete"), - ms if ms > 50 => warn!("exporting the bloomfilter took {ms}ms to complete"), - ms if ms > 10 => info!("exporting the bloomfilter took {ms}ms to complete"), - ms if ms > 2 => debug!("exporting the bloomfilter took {ms}ms to complete"), - ms => trace!("exporting the bloomfilter took {ms}ms to complete"), + let bytes = guard.bytes.clone(); + + // see if it's been > 5min since last export (that value is arbitrary) + if guard.last_exported_at + Duration::minutes(5) < OffsetDateTime::now_utc() { + self.local.maybe_background_update_exported_bloomfilter(); } bytes