Skip to content

Commit fca9633

Browse files
committed
fix(wallet): release pending melt proofs on cancel
Cancel prepared melts now reverts operation-owned Reserved or Pending proofs and clears their operation marker before deleting the saga. This keeps interrupted confirms from orphaning Pending proofs without touching unrelated pending proofs.
1 parent 10edbfc commit fca9633

1 file changed

Lines changed: 56 additions & 6 deletions

File tree

  • crates/cdk/src/wallet/melt

crates/cdk/src/wallet/melt/mod.rs

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,15 +1251,22 @@ impl Wallet {
12511251

12521252
if !all_ys.is_empty() {
12531253
let current = self.localstore.get_proofs_by_ys(all_ys).await?;
1254-
let ys_to_revert: Vec<_> = current
1254+
let mut proofs_to_revert: Vec<_> = current
12551255
.into_iter()
1256-
.filter(|proof| proof.state == State::Reserved)
1257-
.map(|proof| proof.y)
1256+
.filter(|proof| {
1257+
proof.used_by_operation == Some(operation_id)
1258+
&& matches!(proof.state, State::Reserved | State::Pending)
1259+
})
12581260
.collect();
12591261

1260-
if !ys_to_revert.is_empty() {
1262+
for proof in proofs_to_revert.iter_mut() {
1263+
proof.state = State::Unspent;
1264+
proof.used_by_operation = None;
1265+
}
1266+
1267+
if !proofs_to_revert.is_empty() {
12611268
self.localstore
1262-
.update_proofs_state(ys_to_revert, State::Unspent)
1269+
.update_proofs(proofs_to_revert, vec![])
12631270
.await?;
12641271
}
12651272
}
@@ -1858,6 +1865,49 @@ mod tests {
18581865
assert!(db.get_saga(&operation_id).await.unwrap().is_some());
18591866
}
18601867

1868+
#[tokio::test]
1869+
async fn test_cancel_prepared_melt_reverts_operation_pending_proofs() {
1870+
let db = create_test_db().await;
1871+
let mint_url = test_mint_url();
1872+
let keyset_id = crate::wallet::test_utils::test_keyset_id();
1873+
1874+
let proof_info = test_proof_info(keyset_id, 1200, mint_url.clone(), State::Unspent);
1875+
let proof_y = proof_info.y;
1876+
let proof = proof_info.proof.clone();
1877+
db.update_proofs(vec![proof_info], vec![]).await.unwrap();
1878+
1879+
let quote = test_melt_quote();
1880+
let quote_id = quote.id.clone();
1881+
db.add_melt_quote(quote).await.unwrap();
1882+
1883+
let mock_client = Arc::new(MockMintConnector::new());
1884+
let wallet = create_test_wallet_with_mock(db.clone(), mock_client).await;
1885+
let prepared = wallet
1886+
.prepare_melt_proofs(&quote_id, vec![proof], HashMap::new())
1887+
.await
1888+
.unwrap();
1889+
let operation_id = prepared.operation_id();
1890+
1891+
db.update_proofs_state(vec![proof_y], State::Pending)
1892+
.await
1893+
.unwrap();
1894+
1895+
wallet
1896+
.cancel_prepared_melt(
1897+
operation_id,
1898+
prepared.proofs().clone(),
1899+
prepared.proofs_to_swap().clone(),
1900+
)
1901+
.await
1902+
.unwrap();
1903+
1904+
let stored = db.get_proofs_by_ys(vec![proof_y]).await.unwrap();
1905+
assert_eq!(stored.len(), 1);
1906+
assert_eq!(stored[0].state, State::Unspent);
1907+
assert_eq!(stored[0].used_by_operation, None);
1908+
assert!(db.get_saga(&operation_id).await.unwrap().is_none());
1909+
}
1910+
18611911
#[tokio::test]
18621912
async fn test_cancel_prepared_melt_rejects_melt_requested_saga() {
18631913
let db = create_test_db().await;
@@ -2336,7 +2386,7 @@ mod tests {
23362386
}
23372387
_ => panic!("expected melt saga"),
23382388
}
2339-
stored_saga.update_state(stored_saga.state.clone());
2389+
stored_saga.update_state(stored_saga.state);
23402390
assert!(db.update_saga(stored_saga).await.unwrap());
23412391

23422392
let outcome = prepared

0 commit comments

Comments
 (0)