Skip to content

Commit c8f6c92

Browse files
authored
Merge pull request #73 from lightningdevkit/cashu-cache-fix
Deduplicate cached Cashu proofs
2 parents 9fe087a + 9df82d3 commit c8f6c92

1 file changed

Lines changed: 91 additions & 7 deletions

File tree

orange-sdk/src/trusted_wallet/cashu/cashu_store.rs

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::HashMap;
1+
use std::collections::{HashMap, HashSet};
22
use std::fmt::Debug;
33
use std::str::FromStr;
44
use std::sync::{Arc, RwLock};
@@ -257,6 +257,29 @@ impl CashuKvDatabase {
257257
format!("proof_{}", hex::encode(proof.y.serialize()))
258258
}
259259

260+
fn update_proofs_cache(
261+
cache: &mut Vec<ProofInfo>, added: Vec<ProofInfo>, removed_ys: Vec<PublicKey>,
262+
) {
263+
let mut added_ys = HashSet::new();
264+
let mut added_deduped = Vec::with_capacity(added.len());
265+
for proof in added.into_iter().rev() {
266+
if added_ys.insert(proof.y) {
267+
added_deduped.push(proof);
268+
}
269+
}
270+
added_deduped.reverse();
271+
272+
let removed_ys: HashSet<_> = removed_ys.into_iter().collect();
273+
274+
// Match the KV store's proof_<Y> uniqueness by replacing cached entries with
275+
// the same Y before appending the updated proof data.
276+
cache.retain(|proof| !added_ys.contains(&proof.y));
277+
cache.extend(added_deduped);
278+
279+
// Remove proofs with matching Y values
280+
cache.retain(|proof| !removed_ys.contains(&proof.y));
281+
}
282+
260283
fn generate_mint_key(mint_url: &MintUrl) -> String {
261284
// Generate a deterministic hash of the mint URL for use as a key
262285
let mut hasher = DefaultHasher::new();
@@ -736,12 +759,7 @@ impl WalletDatabase<cdk::cdk_database::Error> for CashuKvDatabase {
736759
// Update cache
737760
{
738761
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));
762+
Self::update_proofs_cache(&mut cache, added, removed_ys);
745763
}
746764

747765
Ok(())
@@ -1262,3 +1280,69 @@ pub(super) async fn write_has_recovered(
12621280
.await
12631281
.map_err(TrustedError::IOError)
12641282
}
1283+
1284+
#[cfg(test)]
1285+
mod tests {
1286+
use super::*;
1287+
use cdk::Amount;
1288+
use cdk::nuts::Proof;
1289+
use cdk::secret::Secret;
1290+
1291+
fn proof_info(secret: &str, amount: u64) -> ProofInfo {
1292+
let proof = Proof::new(
1293+
Amount::from(amount),
1294+
Id::from_str("009a1f293253e41e").unwrap(),
1295+
Secret::new(secret),
1296+
PublicKey::from_str(
1297+
"024369d2d22a80ecf78f3937da9d5f30c1b9f74f0c32684d583cca0fa6a61cdcfc",
1298+
)
1299+
.unwrap(),
1300+
);
1301+
1302+
ProofInfo::new(
1303+
proof,
1304+
MintUrl::from_str("https://mint.example.com").unwrap(),
1305+
State::Unspent,
1306+
CurrencyUnit::Sat,
1307+
)
1308+
.unwrap()
1309+
}
1310+
1311+
#[test]
1312+
fn update_proofs_cache_replaces_existing_proof_with_same_y() {
1313+
let original = proof_info("same cached proof", 1);
1314+
let replacement = proof_info("same cached proof", 2);
1315+
let mut cache = vec![original.clone()];
1316+
1317+
CashuKvDatabase::update_proofs_cache(&mut cache, vec![replacement.clone()], vec![]);
1318+
1319+
assert_eq!(cache, vec![replacement]);
1320+
assert_eq!(cache.iter().filter(|proof| proof.y == original.y).count(), 1);
1321+
}
1322+
1323+
#[test]
1324+
fn update_proofs_cache_deduplicates_added_proofs_by_y() {
1325+
let original = proof_info("duplicate added proof", 1);
1326+
let replacement = proof_info("duplicate added proof", 2);
1327+
let mut cache = Vec::new();
1328+
1329+
CashuKvDatabase::update_proofs_cache(
1330+
&mut cache,
1331+
vec![original.clone(), replacement.clone()],
1332+
vec![],
1333+
);
1334+
1335+
assert_eq!(cache, vec![replacement]);
1336+
assert_eq!(cache.iter().filter(|proof| proof.y == original.y).count(), 1);
1337+
}
1338+
1339+
#[test]
1340+
fn update_proofs_cache_removal_wins_over_added_proof() {
1341+
let proof = proof_info("removed cached proof", 1);
1342+
let mut cache = vec![proof.clone()];
1343+
1344+
CashuKvDatabase::update_proofs_cache(&mut cache, vec![proof.clone()], vec![proof.y]);
1345+
1346+
assert!(cache.is_empty());
1347+
}
1348+
}

0 commit comments

Comments
 (0)