|
1 | | -use std::collections::HashMap; |
| 1 | +use std::collections::{HashMap, HashSet}; |
2 | 2 | use std::fmt::Debug; |
3 | 3 | use std::str::FromStr; |
4 | 4 | use std::sync::{Arc, RwLock}; |
@@ -257,6 +257,21 @@ impl CashuKvDatabase { |
257 | 257 | format!("proof_{}", hex::encode(proof.y.serialize())) |
258 | 258 | } |
259 | 259 |
|
| 260 | + fn update_proofs_cache( |
| 261 | + cache: &mut Vec<ProofInfo>, added: Vec<ProofInfo>, removed_ys: Vec<PublicKey>, |
| 262 | + ) { |
| 263 | + let added_ys: HashSet<_> = added.iter().map(|proof| proof.y).collect(); |
| 264 | + let removed_ys: HashSet<_> = removed_ys.into_iter().collect(); |
| 265 | + |
| 266 | + // Match the KV store's proof_<Y> uniqueness by replacing cached entries with |
| 267 | + // the same Y before appending the updated proof data. |
| 268 | + cache.retain(|proof| !added_ys.contains(&proof.y)); |
| 269 | + cache.extend(added); |
| 270 | + |
| 271 | + // Remove proofs with matching Y values |
| 272 | + cache.retain(|proof| !removed_ys.contains(&proof.y)); |
| 273 | + } |
| 274 | + |
260 | 275 | fn generate_mint_key(mint_url: &MintUrl) -> String { |
261 | 276 | // Generate a deterministic hash of the mint URL for use as a key |
262 | 277 | let mut hasher = DefaultHasher::new(); |
@@ -736,12 +751,7 @@ impl WalletDatabase<cdk::cdk_database::Error> for CashuKvDatabase { |
736 | 751 | // Update cache |
737 | 752 | { |
738 | 753 | let mut cache = self.proofs_cache.write().unwrap(); |
739 | | - |
740 | | - // Add new proofs |
741 | | - cache.extend(added); |
742 | | - |
743 | | - // Remove proofs with matching Y values |
744 | | - cache.retain(|proof| !removed_ys.contains(&proof.y)); |
| 754 | + Self::update_proofs_cache(&mut cache, added, removed_ys); |
745 | 755 | } |
746 | 756 |
|
747 | 757 | Ok(()) |
@@ -1262,3 +1272,53 @@ pub(super) async fn write_has_recovered( |
1262 | 1272 | .await |
1263 | 1273 | .map_err(TrustedError::IOError) |
1264 | 1274 | } |
| 1275 | + |
| 1276 | +#[cfg(test)] |
| 1277 | +mod tests { |
| 1278 | + use super::*; |
| 1279 | + use cdk::Amount; |
| 1280 | + use cdk::nuts::Proof; |
| 1281 | + use cdk::secret::Secret; |
| 1282 | + |
| 1283 | + fn proof_info(secret: &str, amount: u64) -> ProofInfo { |
| 1284 | + let proof = Proof::new( |
| 1285 | + Amount::from(amount), |
| 1286 | + Id::from_str("009a1f293253e41e").unwrap(), |
| 1287 | + Secret::new(secret), |
| 1288 | + PublicKey::from_str( |
| 1289 | + "024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc", |
| 1290 | + ) |
| 1291 | + .unwrap(), |
| 1292 | + ); |
| 1293 | + |
| 1294 | + ProofInfo::new( |
| 1295 | + proof, |
| 1296 | + MintUrl::from_str("https://mint.example.com").unwrap(), |
| 1297 | + State::Unspent, |
| 1298 | + CurrencyUnit::Sat, |
| 1299 | + ) |
| 1300 | + .unwrap() |
| 1301 | + } |
| 1302 | + |
| 1303 | + #[test] |
| 1304 | + fn update_proofs_cache_replaces_existing_proof_with_same_y() { |
| 1305 | + let original = proof_info("same cached proof", 1); |
| 1306 | + let replacement = proof_info("same cached proof", 2); |
| 1307 | + let mut cache = vec![original.clone()]; |
| 1308 | + |
| 1309 | + CashuKvDatabase::update_proofs_cache(&mut cache, vec![replacement.clone()], vec![]); |
| 1310 | + |
| 1311 | + assert_eq!(cache, vec![replacement]); |
| 1312 | + assert_eq!(cache.iter().filter(|proof| proof.y == original.y).count(), 1); |
| 1313 | + } |
| 1314 | + |
| 1315 | + #[test] |
| 1316 | + fn update_proofs_cache_removal_wins_over_added_proof() { |
| 1317 | + let proof = proof_info("removed cached proof", 1); |
| 1318 | + let mut cache = vec![proof.clone()]; |
| 1319 | + |
| 1320 | + CashuKvDatabase::update_proofs_cache(&mut cache, vec![proof.clone()], vec![proof.y]); |
| 1321 | + |
| 1322 | + assert!(cache.is_empty()); |
| 1323 | + } |
| 1324 | +} |
0 commit comments