|
@@ -39,25 +39,22 @@ use cdk_common::{KeySet, MintInfo};
|
|
|
use tokio::sync::Mutex;
|
|
use tokio::sync::Mutex;
|
|
|
|
|
|
|
|
use crate::nuts::Id;
|
|
use crate::nuts::Id;
|
|
|
-#[cfg(feature = "auth")]
|
|
|
|
|
-use crate::wallet::AuthMintConnector;
|
|
|
|
|
use crate::wallet::MintConnector;
|
|
use crate::wallet::MintConnector;
|
|
|
-use crate::Error;
|
|
|
|
|
-
|
|
|
|
|
-/// Default TTL
|
|
|
|
|
-pub const DEFAULT_TTL: Duration = Duration::from_secs(300);
|
|
|
|
|
|
|
+#[cfg(feature = "auth")]
|
|
|
|
|
+use crate::wallet::{AuthMintConnector, AuthWallet};
|
|
|
|
|
+use crate::{Error, Wallet};
|
|
|
|
|
|
|
|
/// Metadata freshness and versioning information
|
|
/// Metadata freshness and versioning information
|
|
|
///
|
|
///
|
|
|
/// Tracks when data was last fetched and which version is currently cached.
|
|
/// 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.
|
|
/// Used to determine if cache is ready and if database sync is needed.
|
|
|
#[derive(Clone, Debug)]
|
|
#[derive(Clone, Debug)]
|
|
|
-struct FreshnessStatus {
|
|
|
|
|
|
|
+pub struct FreshnessStatus {
|
|
|
/// Whether this data has been successfully fetched at least once
|
|
/// 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.
|
|
/// 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)
|
|
/// Monotonically increasing version number (for database sync tracking)
|
|
|
version: usize,
|
|
version: usize,
|
|
@@ -67,7 +64,7 @@ impl Default for FreshnessStatus {
|
|
|
fn default() -> Self {
|
|
fn default() -> Self {
|
|
|
Self {
|
|
Self {
|
|
|
is_populated: false,
|
|
is_populated: false,
|
|
|
- valid_until: Instant::now() + DEFAULT_TTL,
|
|
|
|
|
|
|
+ updated_at: Instant::now(),
|
|
|
version: 0,
|
|
version: 0,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -122,8 +119,6 @@ pub struct MintMetadataCache {
|
|
|
/// The mint server URL this cache manages
|
|
/// The mint server URL this cache manages
|
|
|
mint_url: MintUrl,
|
|
mint_url: MintUrl,
|
|
|
|
|
|
|
|
- default_ttl: Duration,
|
|
|
|
|
-
|
|
|
|
|
/// Atomically-updated metadata snapshot (lock-free reads)
|
|
/// Atomically-updated metadata snapshot (lock-free reads)
|
|
|
metadata: Arc<ArcSwap<MintMetadata>>,
|
|
metadata: Arc<ArcSwap<MintMetadata>>,
|
|
|
|
|
|
|
@@ -146,6 +141,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 metadata 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 metadata cache info
|
|
|
|
|
+ pub fn get_metadata_cache_info(&self) -> FreshnessStatus {
|
|
|
|
|
+ self.metadata_cache.metadata.load().auth_status.clone()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
impl MintMetadataCache {
|
|
impl MintMetadataCache {
|
|
|
/// Compute a unique identifier for an Arc pointer
|
|
/// Compute a unique identifier for an Arc pointer
|
|
|
///
|
|
///
|
|
@@ -170,10 +186,9 @@ impl MintMetadataCache {
|
|
|
/// let cache = MintMetadataCache::new(mint_url, None);
|
|
/// let cache = MintMetadataCache::new(mint_url, None);
|
|
|
/// // No data loaded yet - call load() to fetch
|
|
/// // 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 {
|
|
Self {
|
|
|
mint_url,
|
|
mint_url,
|
|
|
- default_ttl: default_ttl.unwrap_or(DEFAULT_TTL),
|
|
|
|
|
metadata: Arc::new(ArcSwap::default()),
|
|
metadata: Arc::new(ArcSwap::default()),
|
|
|
db_sync_versions: Arc::new(Default::default()),
|
|
db_sync_versions: Arc::new(Default::default()),
|
|
|
fetch_lock: Arc::new(Mutex::new(())),
|
|
fetch_lock: Arc::new(Mutex::new(())),
|
|
@@ -196,6 +211,7 @@ impl MintMetadataCache {
|
|
|
///
|
|
///
|
|
|
/// * `storage` - Database to persist metadata to (async background write)
|
|
/// * `storage` - Database to persist metadata to (async background write)
|
|
|
/// * `client` - HTTP client for fetching from mint server
|
|
/// * `client` - HTTP client for fetching from mint server
|
|
|
|
|
+ /// * `ttl` - Optional TTL, if not provided it is asumed that any cached data is good enough
|
|
|
///
|
|
///
|
|
|
/// # Returns
|
|
/// # Returns
|
|
|
///
|
|
///
|
|
@@ -212,6 +228,7 @@ impl MintMetadataCache {
|
|
|
&self,
|
|
&self,
|
|
|
storage: &Arc<dyn WalletDatabase<Err = database::Error> + Send + Sync>,
|
|
storage: &Arc<dyn WalletDatabase<Err = database::Error> + Send + Sync>,
|
|
|
client: &Arc<dyn MintConnector + Send + Sync>,
|
|
client: &Arc<dyn MintConnector + Send + Sync>,
|
|
|
|
|
+ ttl: Option<Duration>,
|
|
|
) -> Result<Arc<MintMetadata>, Error> {
|
|
) -> Result<Arc<MintMetadata>, Error> {
|
|
|
// Acquire lock to ensure only one fetch at a time
|
|
// Acquire lock to ensure only one fetch at a time
|
|
|
let current_version = self.metadata.load().status.version;
|
|
let current_version = self.metadata.load().status.version;
|
|
@@ -220,7 +237,9 @@ impl MintMetadataCache {
|
|
|
// Check if another caller already updated the cache while we waited
|
|
// Check if another caller already updated the cache while we waited
|
|
|
let current_metadata = self.metadata.load().clone();
|
|
let current_metadata = self.metadata.load().clone();
|
|
|
if current_metadata.status.is_populated
|
|
if current_metadata.status.is_populated
|
|
|
- && current_metadata.status.valid_until > Instant::now()
|
|
|
|
|
|
|
+ && ttl
|
|
|
|
|
+ .map(|ttl| current_metadata.status.updated_at + ttl > Instant::now())
|
|
|
|
|
+ .unwrap_or(true)
|
|
|
&& current_metadata.status.version > current_version
|
|
&& current_metadata.status.version > current_version
|
|
|
{
|
|
{
|
|
|
// Cache was just updated by another caller - return it
|
|
// Cache was just updated by another caller - return it
|
|
@@ -255,6 +274,7 @@ impl MintMetadataCache {
|
|
|
///
|
|
///
|
|
|
/// * `storage` - Database to persist metadata to (if fetched or stale)
|
|
/// * `storage` - Database to persist metadata to (if fetched or stale)
|
|
|
/// * `client` - HTTP client for fetching from mint (only if cache empty)
|
|
/// * `client` - HTTP client for fetching from mint (only if cache empty)
|
|
|
|
|
+ /// * `ttl` - Optional TTL, if not provided it is asumed that any cached data is good enough
|
|
|
///
|
|
///
|
|
|
/// # Returns
|
|
/// # Returns
|
|
|
///
|
|
///
|
|
@@ -271,6 +291,7 @@ impl MintMetadataCache {
|
|
|
&self,
|
|
&self,
|
|
|
storage: &Arc<dyn WalletDatabase<Err = database::Error> + Send + Sync>,
|
|
storage: &Arc<dyn WalletDatabase<Err = database::Error> + Send + Sync>,
|
|
|
client: &Arc<dyn MintConnector + Send + Sync>,
|
|
client: &Arc<dyn MintConnector + Send + Sync>,
|
|
|
|
|
+ ttl: Option<Duration>,
|
|
|
) -> Result<Arc<MintMetadata>, Error> {
|
|
) -> Result<Arc<MintMetadata>, Error> {
|
|
|
let cached_metadata = self.metadata.load().clone();
|
|
let cached_metadata = self.metadata.load().clone();
|
|
|
let storage_id = Self::arc_pointer_id(storage);
|
|
let storage_id = Self::arc_pointer_id(storage);
|
|
@@ -284,7 +305,9 @@ impl MintMetadataCache {
|
|
|
.unwrap_or_default();
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
if cached_metadata.status.is_populated
|
|
if cached_metadata.status.is_populated
|
|
|
- && cached_metadata.status.valid_until > Instant::now()
|
|
|
|
|
|
|
+ && ttl
|
|
|
|
|
+ .map(|ttl| cached_metadata.status.updated_at + ttl > Instant::now())
|
|
|
|
|
+ .unwrap_or(true)
|
|
|
{
|
|
{
|
|
|
// Cache is ready - check if database needs updating
|
|
// Cache is ready - check if database needs updating
|
|
|
if db_synced_version != cached_metadata.status.version {
|
|
if db_synced_version != cached_metadata.status.version {
|
|
@@ -297,7 +320,7 @@ impl MintMetadataCache {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Cache not populated - fetch from mint
|
|
// Cache not populated - fetch from mint
|
|
|
- self.load_from_mint(storage, client).await
|
|
|
|
|
|
|
+ self.load_from_mint(storage, client, ttl).await
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Load auth keysets and keys (auth feature only)
|
|
/// Load auth keysets and keys (auth feature only)
|
|
@@ -331,7 +354,7 @@ impl MintMetadataCache {
|
|
|
|
|
|
|
|
// Check if auth data is populated in cache
|
|
// 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()
|
|
|
|
|
|
|
+ && cached_metadata.auth_status.updated_at > Instant::now()
|
|
|
{
|
|
{
|
|
|
if db_synced_version != cached_metadata.status.version {
|
|
if db_synced_version != cached_metadata.status.version {
|
|
|
// Database needs updating - spawn background sync
|
|
// Database needs updating - spawn background sync
|
|
@@ -346,7 +369,7 @@ impl MintMetadataCache {
|
|
|
// Re-check if auth data was updated while waiting for lock
|
|
// Re-check if auth data was updated while waiting for lock
|
|
|
let current_metadata = self.metadata.load().clone();
|
|
let current_metadata = self.metadata.load().clone();
|
|
|
if current_metadata.auth_status.is_populated
|
|
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!(
|
|
tracing::debug!(
|
|
|
"Auth cache was updated while waiting for fetch lock, returning cached data"
|
|
"Auth cache was updated while waiting for fetch lock, returning cached data"
|
|
@@ -562,14 +585,14 @@ impl MintMetadataCache {
|
|
|
// Update freshness status based on what was fetched
|
|
// Update freshness status based on what was fetched
|
|
|
if client.is_some() {
|
|
if client.is_some() {
|
|
|
new_metadata.status.is_populated = true;
|
|
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;
|
|
new_metadata.status.version += 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "auth")]
|
|
#[cfg(feature = "auth")]
|
|
|
if auth_client.is_some() {
|
|
if auth_client.is_some() {
|
|
|
new_metadata.auth_status.is_populated = true;
|
|
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;
|
|
new_metadata.auth_status.version += 1;
|
|
|
}
|
|
}
|
|
|
|
|
|