Jelajahi Sumber

Add ability a TTL to the mint metadata cache

Cesar Rodas 1 Minggu lalu
induk
melakukan
18e04958c6

+ 1 - 1
crates/cdk/examples/proof-selection.rs

@@ -52,7 +52,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
     // Select proofs to send
     let amount = Amount::from(64);
     let active_keyset_ids = wallet
-        .refresh_keysets()
+        .get_mint_keysets()
         .await?
         .active()
         .map(|keyset| keyset.id)

+ 2 - 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()))
+                Arc::new(MintMetadataCache::new(mint_url.clone(), None))
             }
         });
 
@@ -207,7 +207,7 @@ impl WalletBuilder {
                 cache.clone()
             } else {
                 // Create a new one
-                Arc::new(MintMetadataCache::new(mint_url.clone()))
+                Arc::new(MintMetadataCache::new(mint_url.clone(), None))
             }
         });
 

+ 0 - 4
crates/cdk/src/wallet/issue/issue_bolt11.rs

@@ -52,8 +52,6 @@ impl Wallet {
         let mint_url = self.mint_url.clone();
         let unit = self.unit.clone();
 
-        self.refresh_keysets().await?;
-
         // If we have a description, we check that the mint supports it.
         if description.is_some() {
             let settings = self
@@ -196,8 +194,6 @@ impl Wallet {
         amount_split_target: SplitTarget,
         spending_conditions: Option<SpendingConditions>,
     ) -> Result<Proofs, Error> {
-        self.refresh_keysets().await?;
-
         let quote_info = self
             .localstore
             .get_mint_quote(quote_id)

+ 0 - 4
crates/cdk/src/wallet/issue/issue_bolt12.rs

@@ -29,8 +29,6 @@ impl Wallet {
         let mint_url = self.mint_url.clone();
         let unit = &self.unit;
 
-        self.refresh_keysets().await?;
-
         // If we have a description, we check that the mint supports it.
         if description.is_some() {
             let mint_method_settings = self
@@ -85,8 +83,6 @@ impl Wallet {
         amount_split_target: SplitTarget,
         spending_conditions: Option<SpendingConditions>,
     ) -> Result<Proofs, Error> {
-        self.refresh_keysets().await?;
-
         let quote_info = self.localstore.get_mint_quote(quote_id).await?;
 
         let quote_info = if let Some(quote) = quote_info {

+ 4 - 21
crates/cdk/src/wallet/keysets.rs

@@ -23,28 +23,10 @@ impl Wallet {
             .ok_or(Error::UnknownKeySet)
     }
 
-    /// Get keysets from metadata cache or fetch if missing
-    ///
-    /// First checks the metadata cache for keysets. If keysets are not cached,
-    /// fetches from the mint server and updates the cache.
-    /// This is the main method for getting keysets in token operations that can work offline
-    /// but will fall back to online if needed.
+    /// Alias of get_mint_keysets, kept for backwards compatibility reasons
     #[instrument(skip(self))]
     pub async fn load_mint_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
-        Ok(self
-            .metadata_cache
-            .load(&self.localstore, &self.client)
-            .await?
-            .keysets
-            .iter()
-            .filter_map(|(_, keyset)| {
-                if keyset.unit == self.unit && keyset.active {
-                    Some((*keyset.clone()).clone())
-                } else {
-                    None
-                }
-            })
-            .collect::<Vec<_>>())
+        self.get_mint_keysets().await
     }
 
     /// Get keysets from metadata cache (may fetch if not populated)
@@ -52,6 +34,7 @@ impl Wallet {
     /// Checks the metadata cache for keysets. If cache is not populated,
     /// fetches from mint and updates cache. Returns error if no active keysets found.
     #[instrument(skip(self))]
+    #[inline(always)]
     pub async fn get_mint_keysets(&self) -> Result<Vec<KeySetInfo>, Error> {
         let keysets = self
             .metadata_cache
@@ -113,7 +96,7 @@ impl Wallet {
     /// keyset information for operations.
     #[instrument(skip(self))]
     pub async fn fetch_active_keyset(&self) -> Result<KeySetInfo, Error> {
-        self.refresh_keysets()
+        self.get_mint_keysets()
             .await?
             .active()
             .min_by_key(|k| k.input_fee_ppk)

+ 1 - 3
crates/cdk/src/wallet/melt/melt_bolt11.rs

@@ -49,8 +49,6 @@ impl Wallet {
         request: String,
         options: Option<MeltOptions>,
     ) -> Result<MeltQuote, Error> {
-        self.refresh_keysets().await?;
-
         let invoice = Bolt11Invoice::from_str(&request)?;
 
         let quote_request = MeltQuoteBolt11Request {
@@ -390,7 +388,7 @@ impl Wallet {
         let available_proofs = self.get_unspent_proofs().await?;
 
         let active_keyset_ids = self
-            .refresh_keysets()
+            .get_mint_keysets()
             .await?
             .into_iter()
             .map(|k| k.id)

+ 21 - 11
crates/cdk/src/wallet/mint_metadata_cache.rs

@@ -27,7 +27,7 @@
 use std::collections::HashMap;
 use std::fmt::Debug;
 use std::sync::Arc;
-use std::time::Instant;
+use std::time::{Duration, Instant};
 
 use arc_swap::ArcSwap;
 use cdk_common::database::{self, WalletDatabase};
@@ -43,6 +43,9 @@ use crate::wallet::AuthMintConnector;
 use crate::wallet::MintConnector;
 use crate::Error;
 
+/// Default TTL
+pub const DEFAULT_TTL: Duration = Duration::from_secs(300);
+
 /// Metadata freshness and versioning information
 ///
 /// Tracks when data was last fetched and which version is currently cached.
@@ -52,8 +55,8 @@ struct FreshnessStatus {
     /// Whether this data has been successfully fetched at least once
     is_populated: bool,
 
-    /// When this data was last fetched from the mint
-    last_fetched_at: Instant,
+    /// A future time when the cache would be considered as staled.
+    valid_until: Instant,
 
     /// Monotonically increasing version number (for database sync tracking)
     version: usize,
@@ -63,7 +66,7 @@ impl Default for FreshnessStatus {
     fn default() -> Self {
         Self {
             is_populated: false,
-            last_fetched_at: Instant::now(),
+            valid_until: Instant::now() + DEFAULT_TTL,
             version: 0,
         }
     }
@@ -117,6 +120,8 @@ 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>>,
 
@@ -156,12 +161,13 @@ impl MintMetadataCache {
     /// # Example
     ///
     /// ```ignore
-    /// let cache = MintMetadataCache::new(mint_url);
+    /// let cache = MintMetadataCache::new(mint_url, None);
     /// // No data loaded yet - call load() to fetch
     /// ```
-    pub fn new(mint_url: MintUrl) -> Self {
+    pub fn new(mint_url: MintUrl, default_ttl: Option<Duration>) -> Self {
         Self {
             mint_url,
+            default_ttl: default_ttl.unwrap_or(DEFAULT_TTL),
             metadata: Arc::new(ArcSwap::default()),
             db_sync_versions: Arc::new(Default::default()),
         }
@@ -210,7 +216,7 @@ impl MintMetadataCache {
 
     /// Load metadata from cache or fetch if not available
     ///
-    /// Returns cached metadata if available, otherwise fetches from the mint.
+    /// Returns cached metadata if available and it is still valid, otherwise fetches from the mint.
     /// If cache is stale relative to the database, spawns a background sync task.
     ///
     /// This is the primary method for normal operations - it balances freshness
@@ -248,7 +254,9 @@ impl MintMetadataCache {
             .cloned()
             .unwrap_or_default();
 
-        if cached_metadata.status.is_populated {
+        if cached_metadata.status.is_populated
+            && cached_metadata.status.valid_until > Instant::now()
+        {
             // Cache is ready - check if database needs updating
             if db_synced_version != cached_metadata.status.version {
                 // Database is stale - sync in background
@@ -293,7 +301,9 @@ impl MintMetadataCache {
             .unwrap_or_default();
 
         // Check if auth data is populated in cache
-        if cached_metadata.auth_status.is_populated {
+        if cached_metadata.auth_status.is_populated
+            && cached_metadata.auth_status.valid_until > Instant::now()
+        {
             if db_synced_version != cached_metadata.status.version {
                 // Database needs updating - spawn background sync
                 self.spawn_database_sync(storage.clone(), cached_metadata.clone());
@@ -509,14 +519,14 @@ impl MintMetadataCache {
         // Update freshness status based on what was fetched
         if client.is_some() {
             new_metadata.status.is_populated = true;
-            new_metadata.status.last_fetched_at = Instant::now();
+            new_metadata.status.valid_until = Instant::now() + self.default_ttl;
             new_metadata.status.version += 1;
         }
 
         #[cfg(feature = "auth")]
         if auth_client.is_some() {
             new_metadata.auth_status.is_populated = true;
-            new_metadata.auth_status.last_fetched_at = Instant::now();
+            new_metadata.auth_status.valid_until = Instant::now() + self.default_ttl;
             new_metadata.auth_status.version += 1;
         }
 

+ 0 - 2
crates/cdk/src/wallet/receive.rs

@@ -28,8 +28,6 @@ impl Wallet {
     ) -> Result<Amount, Error> {
         let mint_url = &self.mint_url;
 
-        self.refresh_keysets().await?;
-
         let active_keyset_id = self.fetch_active_keyset().await?.id;
 
         let keys = self.load_keyset_keys(active_keyset_id).await?;

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

@@ -21,8 +21,6 @@ impl Wallet {
         spending_conditions: Option<SpendingConditions>,
         include_fees: bool,
     ) -> Result<Option<Proofs>, Error> {
-        self.refresh_keysets().await?;
-
         tracing::info!("Swapping");
         let mint_url = &self.mint_url;
         let unit = &self.unit;
@@ -178,7 +176,7 @@ impl Wallet {
         ensure_cdk!(proofs_sum >= amount, Error::InsufficientFunds);
 
         let active_keyset_ids = self
-            .refresh_keysets()
+            .get_mint_keysets()
             .await?
             .active()
             .map(|k| k.id)