Преглед на файлове

Move cache TTL to each wallet

Each wallet have their own cache TTL and access to cache freshness status
Cesar Rodas преди 10 месеца
родител
ревизия
a28b6ac58c
променени са 5 файла, в които са добавени 135 реда и са изтрити 28 реда
  1. 3 2
      crates/cdk/src/wallet/builder.rs
  2. 25 5
      crates/cdk/src/wallet/keysets.rs
  3. 40 18
      crates/cdk/src/wallet/mint_metadata_cache.rs
  4. 62 2
      crates/cdk/src/wallet/mod.rs
  5. 5 1
      crates/cdk/src/wallet/swap.rs

+ 3 - 2
crates/cdk/src/wallet/builder.rs

@@ -154,7 +154,7 @@ impl WalletBuilder {
                 cache.clone()
             } else {
                 // Create a new one
-                Arc::new(MintMetadataCache::new(mint_url.clone(), None))
+                Arc::new(MintMetadataCache::new(mint_url.clone()))
             }
         });
 
@@ -207,7 +207,7 @@ impl WalletBuilder {
                 cache.clone()
             } else {
                 // Create a new one
-                Arc::new(MintMetadataCache::new(mint_url.clone(), None))
+                Arc::new(MintMetadataCache::new(mint_url.clone()))
             }
         });
 
@@ -216,6 +216,7 @@ impl WalletBuilder {
             unit,
             localstore,
             metadata_cache,
+            metadata_cache_ttl: None.into(),
             target_proof_count: self.target_proof_count.unwrap_or(3),
             #[cfg(feature = "auth")]
             auth_wallet: Arc::new(RwLock::new(self.auth_wallet)),

+ 25 - 5
crates/cdk/src/wallet/keysets.rs

@@ -15,7 +15,11 @@ impl Wallet {
     #[instrument(skip(self))]
     pub async fn load_keyset_keys(&self, keyset_id: Id) -> Result<Keys, Error> {
         self.metadata_cache
-            .load(&self.localstore, &self.client)
+            .load(
+                &self.localstore,
+                &self.client,
+                self.metadata_cache_ttl.clone_inner(),
+            )
             .await?
             .keys
             .get(&keyset_id)
@@ -38,7 +42,11 @@ impl Wallet {
     pub async fn get_mint_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
         let keysets = self
             .metadata_cache
-            .load(&self.localstore, &self.client)
+            .load(
+                &self.localstore,
+                &self.client,
+                self.metadata_cache_ttl.clone_inner(),
+            )
             .await?
             .keysets
             .iter()
@@ -69,7 +77,11 @@ impl Wallet {
 
         let keysets = self
             .metadata_cache
-            .load_from_mint(&self.localstore, &self.client)
+            .load_from_mint(
+                &self.localstore,
+                &self.client,
+                self.metadata_cache_ttl.clone_inner(),
+            )
             .await?
             .keysets
             .iter()
@@ -111,7 +123,11 @@ impl Wallet {
     #[instrument(skip(self))]
     pub async fn get_active_keyset(&self) -> Result<KeySetInfo, Error> {
         self.metadata_cache
-            .load(&self.localstore, &self.client)
+            .load(
+                &self.localstore,
+                &self.client,
+                self.metadata_cache_ttl.clone_inner(),
+            )
             .await?
             .active_keysets
             .iter()
@@ -127,7 +143,11 @@ impl Wallet {
     pub async fn get_keyset_fees_and_amounts(&self) -> Result<KeysetFeeAndAmounts, Error> {
         let metadata = self
             .metadata_cache
-            .load(&self.localstore, &self.client)
+            .load(
+                &self.localstore,
+                &self.client,
+                self.metadata_cache_ttl.clone_inner(),
+            )
             .await?;
 
         let mut fees = HashMap::new();

+ 40 - 18
crates/cdk/src/wallet/mint_metadata_cache.rs

@@ -39,10 +39,10 @@ use cdk_common::{KeySet, MintInfo};
 use tokio::sync::Mutex;
 
 use crate::nuts::Id;
-#[cfg(feature = "auth")]
-use crate::wallet::AuthMintConnector;
 use crate::wallet::MintConnector;
-use crate::Error;
+#[cfg(feature = "auth")]
+use crate::wallet::{AuthMintConnector, AuthWallet};
+use crate::{Error, Wallet};
 
 /// Default TTL
 pub const DEFAULT_TTL: Duration = Duration::from_secs(300);
@@ -52,12 +52,12 @@ pub const DEFAULT_TTL: Duration = Duration::from_secs(300);
 /// Tracks when data was last fetched and which version is currently cached.
 /// Used to determine if cache is ready and if database sync is needed.
 #[derive(Clone, Debug)]
-struct FreshnessStatus {
+pub struct FreshnessStatus {
     /// Whether this data has been successfully fetched at least once
-    is_populated: bool,
+    pub is_populated: bool,
 
     /// A future time when the cache would be considered as staled.
-    valid_until: Instant,
+    pub updated_at: Instant,
 
     /// Monotonically increasing version number (for database sync tracking)
     version: usize,
@@ -67,7 +67,7 @@ impl Default for FreshnessStatus {
     fn default() -> Self {
         Self {
             is_populated: false,
-            valid_until: Instant::now() + DEFAULT_TTL,
+            updated_at: Instant::now(),
             version: 0,
         }
     }
@@ -122,8 +122,6 @@ pub struct MintMetadataCache {
     /// The mint server URL this cache manages
     mint_url: MintUrl,
 
-    default_ttl: Duration,
-
     /// Atomically-updated metadata snapshot (lock-free reads)
     metadata: Arc<ArcSwap<MintMetadata>>,
 
@@ -146,6 +144,27 @@ impl std::fmt::Debug for MintMetadataCache {
     }
 }
 
+impl Wallet {
+    /// Sets the metadata cache TTL
+    pub fn set_metadata_cache_ttl(&self, ttl: Option<Duration>) {
+        let mut guarded_ttl = self.metadata_cache_ttl.lock();
+        *guarded_ttl = ttl;
+    }
+
+    /// Get information about metada cache info
+    pub fn get_metadata_cache_info(&self) -> FreshnessStatus {
+        self.metadata_cache.metadata.load().status.clone()
+    }
+}
+
+#[cfg(feature = "auth")]
+impl AuthWallet {
+    /// Get information about metada cache info
+    pub fn get_metadata_cache_info(&self) -> FreshnessStatus {
+        self.metadata_cache.metadata.load().auth_status.clone()
+    }
+}
+
 impl MintMetadataCache {
     /// Compute a unique identifier for an Arc pointer
     ///
@@ -170,10 +189,9 @@ impl MintMetadataCache {
     /// let cache = MintMetadataCache::new(mint_url, None);
     /// // No data loaded yet - call load() to fetch
     /// ```
-    pub fn new(mint_url: MintUrl, default_ttl: Option<Duration>) -> Self {
+    pub fn new(mint_url: MintUrl) -> Self {
         Self {
             mint_url,
-            default_ttl: default_ttl.unwrap_or(DEFAULT_TTL),
             metadata: Arc::new(ArcSwap::default()),
             db_sync_versions: Arc::new(Default::default()),
             fetch_lock: Arc::new(Mutex::new(())),
@@ -212,15 +230,17 @@ impl MintMetadataCache {
         &self,
         storage: &Arc<dyn WalletDatabase<Err = database::Error> + Send + Sync>,
         client: &Arc<dyn MintConnector + Send + Sync>,
+        ttl: Option<Duration>,
     ) -> Result<Arc<MintMetadata>, Error> {
         // Acquire lock to ensure only one fetch at a time
+        let ttl = ttl.unwrap_or(DEFAULT_TTL);
         let current_version = self.metadata.load().status.version;
         let _guard = self.fetch_lock.lock().await;
 
         // Check if another caller already updated the cache while we waited
         let current_metadata = self.metadata.load().clone();
         if current_metadata.status.is_populated
-            && current_metadata.status.valid_until > Instant::now()
+            && current_metadata.status.updated_at + ttl > Instant::now()
             && current_metadata.status.version > current_version
         {
             // Cache was just updated by another caller - return it
@@ -271,9 +291,11 @@ impl MintMetadataCache {
         &self,
         storage: &Arc<dyn WalletDatabase<Err = database::Error> + Send + Sync>,
         client: &Arc<dyn MintConnector + Send + Sync>,
+        ttl: Option<Duration>,
     ) -> Result<Arc<MintMetadata>, Error> {
         let cached_metadata = self.metadata.load().clone();
         let storage_id = Self::arc_pointer_id(storage);
+        let ttl = ttl.unwrap_or(DEFAULT_TTL);
 
         // Check what version of cache this database has seen
         let db_synced_version = self
@@ -284,7 +306,7 @@ impl MintMetadataCache {
             .unwrap_or_default();
 
         if cached_metadata.status.is_populated
-            && cached_metadata.status.valid_until > Instant::now()
+            && cached_metadata.status.updated_at + ttl > Instant::now()
         {
             // Cache is ready - check if database needs updating
             if db_synced_version != cached_metadata.status.version {
@@ -297,7 +319,7 @@ impl MintMetadataCache {
         }
 
         // Cache not populated - fetch from mint
-        self.load_from_mint(storage, client).await
+        self.load_from_mint(storage, client, Some(ttl)).await
     }
 
     /// Load auth keysets and keys (auth feature only)
@@ -331,7 +353,7 @@ impl MintMetadataCache {
 
         // Check if auth data is populated in cache
         if cached_metadata.auth_status.is_populated
-            && cached_metadata.auth_status.valid_until > Instant::now()
+            && cached_metadata.auth_status.updated_at > Instant::now()
         {
             if db_synced_version != cached_metadata.status.version {
                 // Database needs updating - spawn background sync
@@ -346,7 +368,7 @@ impl MintMetadataCache {
         // Re-check if auth data was updated while waiting for lock
         let current_metadata = self.metadata.load().clone();
         if current_metadata.auth_status.is_populated
-            && current_metadata.auth_status.valid_until > Instant::now()
+            && current_metadata.auth_status.updated_at > Instant::now()
         {
             tracing::debug!(
                 "Auth cache was updated while waiting for fetch lock, returning cached data"
@@ -562,14 +584,14 @@ impl MintMetadataCache {
         // Update freshness status based on what was fetched
         if client.is_some() {
             new_metadata.status.is_populated = true;
-            new_metadata.status.valid_until = Instant::now() + self.default_ttl;
+            new_metadata.status.updated_at = Instant::now();
             new_metadata.status.version += 1;
         }
 
         #[cfg(feature = "auth")]
         if auth_client.is_some() {
             new_metadata.auth_status.is_populated = true;
-            new_metadata.auth_status.valid_until = Instant::now() + self.default_ttl;
+            new_metadata.auth_status.updated_at = Instant::now();
             new_metadata.auth_status.version += 1;
         }
 

+ 62 - 2
crates/cdk/src/wallet/mod.rs

@@ -1,12 +1,16 @@
 #![doc = include_str!("./README.md")]
 
 use std::collections::HashMap;
+use std::fmt::Debug;
+use std::ops::Deref;
 use std::str::FromStr;
 use std::sync::atomic::AtomicBool;
 use std::sync::Arc;
+use std::time::Duration;
 
 use cdk_common::amount::FeeAndAmounts;
 use cdk_common::database::{self, WalletDatabase};
+use cdk_common::parking_lot::Mutex;
 use cdk_common::subscription::WalletParams;
 use getrandom::getrandom;
 use subscription::{ActiveSubscription, SubscriptionManager};
@@ -75,6 +79,53 @@ pub use types::{MeltQuote, MintQuote, SendKind};
 
 use crate::nuts::nut00::ProofsMethods;
 
+/// Clonable mutex
+///
+/// Each instance is indepent from each other, that's why an Arc is not an option
+#[derive(Debug)]
+pub struct ClonableMutex<T>(Mutex<T>)
+where
+    T: Clone + Debug;
+
+impl<T> From<T> for ClonableMutex<T>
+where
+    T: Clone + Debug,
+{
+    fn from(value: T) -> Self {
+        Self(Mutex::new(value))
+    }
+}
+
+impl<T> ClonableMutex<T>
+where
+    T: Clone + Debug,
+{
+    /// Clone the inner guarded object
+    pub fn clone_inner(&self) -> T {
+        (*self.0.lock().deref()).clone()
+    }
+}
+
+impl<T> Clone for ClonableMutex<T>
+where
+    T: Clone + Debug,
+{
+    fn clone(&self) -> Self {
+        Self(Mutex::new(self.0.lock().clone()))
+    }
+}
+
+impl<T> Deref for ClonableMutex<T>
+where
+    T: Clone + Debug,
+{
+    type Target = Mutex<T>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
 /// CDK Wallet
 ///
 /// The CDK [`Wallet`] is a high level cashu wallet.
@@ -92,6 +143,7 @@ pub struct Wallet {
     pub metadata_cache: Arc<MintMetadataCache>,
     /// The targeted amount of proofs to have at each size
     pub target_proof_count: usize,
+    metadata_cache_ttl: ClonableMutex<Option<Duration>>,
     #[cfg(feature = "auth")]
     auth_wallet: Arc<RwLock<Option<AuthWallet>>>,
     seed: [u8; 64],
@@ -223,7 +275,11 @@ impl Wallet {
         let mut fee_per_keyset = HashMap::new();
         let metadata = self
             .metadata_cache
-            .load(&self.localstore, &self.client)
+            .load(
+                &self.localstore,
+                &self.client,
+                self.metadata_cache_ttl.clone_inner(),
+            )
             .await?;
 
         for keyset_id in proofs_per_keyset.keys() {
@@ -244,7 +300,11 @@ impl Wallet {
     pub async fn get_keyset_count_fee(&self, keyset_id: &Id, count: u64) -> Result<Amount, Error> {
         let input_fee_ppk = self
             .metadata_cache
-            .load(&self.localstore, &self.client)
+            .load(
+                &self.localstore,
+                &self.client,
+                self.metadata_cache_ttl.clone_inner(),
+            )
             .await?
             .keysets
             .get(keyset_id)

+ 5 - 1
crates/cdk/src/wallet/swap.rs

@@ -49,7 +49,11 @@ impl Wallet {
 
         let active_keys = self
             .metadata_cache
-            .load(&self.localstore, &self.client)
+            .load(
+                &self.localstore,
+                &self.client,
+                self.metadata_cache_ttl.clone_inner(),
+            )
             .await?
             .keys
             .get(&active_keyset_id)