proofs.rs 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013
  1. //! Proofs tests
  2. use std::str::FromStr;
  3. use cashu::secret::Secret;
  4. use cashu::{Amount, Id, SecretKey};
  5. use crate::database::mint::test::setup_keyset;
  6. use crate::database::mint::{Database, Error, KeysDatabase, Proof, QuoteId};
  7. use crate::mint::Operation;
  8. use crate::state::check_state_transition;
  9. /// Test get proofs by keyset id
  10. pub async fn get_proofs_by_keyset_id<DB>(db: DB)
  11. where
  12. DB: Database<Error> + KeysDatabase<Err = Error>,
  13. {
  14. let keyset_id = setup_keyset(&db).await;
  15. let quote_id = QuoteId::new_uuid();
  16. let proofs = vec![
  17. Proof {
  18. amount: Amount::from(100),
  19. keyset_id,
  20. secret: Secret::generate(),
  21. c: SecretKey::generate().public_key(),
  22. witness: None,
  23. dleq: None,
  24. },
  25. Proof {
  26. amount: Amount::from(200),
  27. keyset_id,
  28. secret: Secret::generate(),
  29. c: SecretKey::generate().public_key(),
  30. witness: None,
  31. dleq: None,
  32. },
  33. ];
  34. // Add proofs to database
  35. let mut tx = Database::begin_transaction(&db).await.unwrap();
  36. tx.add_proofs(
  37. proofs,
  38. Some(quote_id),
  39. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  40. )
  41. .await
  42. .unwrap();
  43. assert!(tx.commit().await.is_ok());
  44. let (proofs, states) = db.get_proofs_by_keyset_id(&keyset_id).await.unwrap();
  45. assert_eq!(proofs.len(), 2);
  46. assert_eq!(proofs.len(), states.len());
  47. assert_eq!(
  48. states
  49. .into_iter()
  50. .map(|s| s.map(|x| x.to_string()).unwrap_or_default())
  51. .collect::<Vec<_>>(),
  52. vec!["UNSPENT".to_owned(), "UNSPENT".to_owned()]
  53. );
  54. let keyset_id = Id::from_str("00916bbf7ef91a34").unwrap();
  55. let (proofs, states) = db.get_proofs_by_keyset_id(&keyset_id).await.unwrap();
  56. assert_eq!(proofs.len(), 0);
  57. assert_eq!(proofs.len(), states.len());
  58. }
  59. /// Test the basic storing and retrieving proofs from the database. Probably the database would use
  60. /// binary/`Vec<u8>` to store data, that's why this test would quickly identify issues before running
  61. /// other tests
  62. pub async fn add_and_find_proofs<DB>(db: DB)
  63. where
  64. DB: Database<Error> + KeysDatabase<Err = Error>,
  65. {
  66. let keyset_id = setup_keyset(&db).await;
  67. let quote_id = QuoteId::new_uuid();
  68. let proofs = vec![
  69. Proof {
  70. amount: Amount::from(100),
  71. keyset_id,
  72. secret: Secret::generate(),
  73. c: SecretKey::generate().public_key(),
  74. witness: None,
  75. dleq: None,
  76. },
  77. Proof {
  78. amount: Amount::from(200),
  79. keyset_id,
  80. secret: Secret::generate(),
  81. c: SecretKey::generate().public_key(),
  82. witness: None,
  83. dleq: None,
  84. },
  85. ];
  86. // Add proofs to database
  87. let mut tx = Database::begin_transaction(&db).await.unwrap();
  88. tx.add_proofs(
  89. proofs.clone(),
  90. Some(quote_id.clone()),
  91. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  92. )
  93. .await
  94. .unwrap();
  95. assert!(tx.commit().await.is_ok());
  96. let proofs_from_db = db.get_proofs_by_ys(&[proofs[0].c, proofs[1].c]).await;
  97. assert!(proofs_from_db.is_ok());
  98. assert_eq!(proofs_from_db.unwrap().len(), 2);
  99. let proofs_from_db = db.get_proof_ys_by_quote_id(&quote_id).await;
  100. assert!(proofs_from_db.is_ok());
  101. assert_eq!(proofs_from_db.unwrap().len(), 2);
  102. }
  103. /// Test to add duplicate proofs
  104. pub async fn add_duplicate_proofs<DB>(db: DB)
  105. where
  106. DB: Database<Error> + KeysDatabase<Err = Error>,
  107. {
  108. let keyset_id = setup_keyset(&db).await;
  109. let quote_id = QuoteId::new_uuid();
  110. let proofs = vec![
  111. Proof {
  112. amount: Amount::from(100),
  113. keyset_id,
  114. secret: Secret::generate(),
  115. c: SecretKey::generate().public_key(),
  116. witness: None,
  117. dleq: None,
  118. },
  119. Proof {
  120. amount: Amount::from(200),
  121. keyset_id,
  122. secret: Secret::generate(),
  123. c: SecretKey::generate().public_key(),
  124. witness: None,
  125. dleq: None,
  126. },
  127. ];
  128. // Add proofs to database
  129. let mut tx = Database::begin_transaction(&db).await.unwrap();
  130. tx.add_proofs(
  131. proofs.clone(),
  132. Some(quote_id.clone()),
  133. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  134. )
  135. .await
  136. .unwrap();
  137. assert!(tx.commit().await.is_ok());
  138. let mut tx = Database::begin_transaction(&db).await.unwrap();
  139. let result = tx
  140. .add_proofs(
  141. proofs.clone(),
  142. Some(quote_id.clone()),
  143. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  144. )
  145. .await;
  146. assert!(
  147. matches!(result.unwrap_err(), Error::Duplicate),
  148. "Duplicate entry"
  149. );
  150. }
  151. /// Test updating proofs states
  152. pub async fn update_proofs_states<DB>(db: DB)
  153. where
  154. DB: Database<Error> + KeysDatabase<Err = Error>,
  155. {
  156. use cashu::State;
  157. let keyset_id = setup_keyset(&db).await;
  158. let quote_id = QuoteId::new_uuid();
  159. let proofs = vec![
  160. Proof {
  161. amount: Amount::from(100),
  162. keyset_id,
  163. secret: Secret::generate(),
  164. c: SecretKey::generate().public_key(),
  165. witness: None,
  166. dleq: None,
  167. },
  168. Proof {
  169. amount: Amount::from(200),
  170. keyset_id,
  171. secret: Secret::generate(),
  172. c: SecretKey::generate().public_key(),
  173. witness: None,
  174. dleq: None,
  175. },
  176. ];
  177. let ys: Vec<_> = proofs.iter().map(|p| p.c).collect();
  178. // Add proofs
  179. let mut tx = Database::begin_transaction(&db).await.unwrap();
  180. tx.add_proofs(
  181. proofs.clone(),
  182. Some(quote_id),
  183. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  184. )
  185. .await
  186. .unwrap();
  187. tx.commit().await.unwrap();
  188. // Check initial state - states may vary by implementation
  189. let states = db.get_proofs_states(&ys).await.unwrap();
  190. assert_eq!(states.len(), 2);
  191. assert!(states[0].is_some());
  192. assert!(states[1].is_some());
  193. // Update to pending
  194. let mut tx = Database::begin_transaction(&db).await.unwrap();
  195. let mut proofs = tx.get_proofs(&ys).await.unwrap();
  196. check_state_transition(proofs.state, State::Pending).unwrap();
  197. tx.update_proofs_state(&mut proofs, State::Pending)
  198. .await
  199. .unwrap();
  200. tx.commit().await.unwrap();
  201. // Verify new state
  202. let states = db.get_proofs_states(&ys).await.unwrap();
  203. assert_eq!(states[0], Some(State::Pending));
  204. assert_eq!(states[1], Some(State::Pending));
  205. // Update to spent
  206. let mut tx = Database::begin_transaction(&db).await.unwrap();
  207. let mut proofs = tx.get_proofs(&ys).await.unwrap();
  208. check_state_transition(proofs.state, State::Spent).unwrap();
  209. tx.update_proofs_state(&mut proofs, State::Spent)
  210. .await
  211. .unwrap();
  212. tx.commit().await.unwrap();
  213. // Verify final state
  214. let states = db.get_proofs_states(&ys).await.unwrap();
  215. assert_eq!(states[0], Some(State::Spent));
  216. assert_eq!(states[1], Some(State::Spent));
  217. }
  218. /// Test that update_proofs_state updates the ProofsWithState.state field
  219. pub async fn update_proofs_state_updates_proofs_with_state<DB>(db: DB)
  220. where
  221. DB: Database<Error> + KeysDatabase<Err = Error>,
  222. {
  223. use cashu::State;
  224. let keyset_id = setup_keyset(&db).await;
  225. let quote_id = QuoteId::new_uuid();
  226. let proofs = vec![Proof {
  227. amount: Amount::from(100),
  228. keyset_id,
  229. secret: Secret::generate(),
  230. c: SecretKey::generate().public_key(),
  231. witness: None,
  232. dleq: None,
  233. }];
  234. let ys: Vec<_> = proofs.iter().map(|p| p.y().unwrap()).collect();
  235. // Add proofs and verify initial state
  236. let mut tx = Database::begin_transaction(&db).await.unwrap();
  237. let mut proofs = tx
  238. .add_proofs(
  239. proofs.clone(),
  240. Some(quote_id),
  241. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  242. )
  243. .await
  244. .unwrap();
  245. assert_eq!(proofs.state, State::Unspent);
  246. // Update to Pending and verify ProofsWithState.state is updated
  247. tx.update_proofs_state(&mut proofs, State::Pending)
  248. .await
  249. .unwrap();
  250. assert_eq!(
  251. proofs.state,
  252. State::Pending,
  253. "ProofsWithState.state should be updated to Pending after update_proofs_state"
  254. );
  255. tx.commit().await.unwrap();
  256. // Get proofs again and update to Spent
  257. let mut tx = Database::begin_transaction(&db).await.unwrap();
  258. let mut proofs = tx.get_proofs(&ys).await.unwrap();
  259. assert_eq!(proofs.state, State::Pending);
  260. tx.update_proofs_state(&mut proofs, State::Spent)
  261. .await
  262. .unwrap();
  263. assert_eq!(
  264. proofs.state,
  265. State::Spent,
  266. "ProofsWithState.state should be updated to Spent after update_proofs_state"
  267. );
  268. tx.commit().await.unwrap();
  269. }
  270. /// Test removing proofs
  271. pub async fn remove_proofs<DB>(db: DB)
  272. where
  273. DB: Database<Error> + KeysDatabase<Err = Error>,
  274. {
  275. let keyset_id = setup_keyset(&db).await;
  276. let quote_id = QuoteId::new_uuid();
  277. let proofs = vec![
  278. Proof {
  279. amount: Amount::from(100),
  280. keyset_id,
  281. secret: Secret::generate(),
  282. c: SecretKey::generate().public_key(),
  283. witness: None,
  284. dleq: None,
  285. },
  286. Proof {
  287. amount: Amount::from(200),
  288. keyset_id,
  289. secret: Secret::generate(),
  290. c: SecretKey::generate().public_key(),
  291. witness: None,
  292. dleq: None,
  293. },
  294. ];
  295. let ys: Vec<_> = proofs.iter().map(|p| p.c).collect();
  296. // Add proofs
  297. let mut tx = Database::begin_transaction(&db).await.unwrap();
  298. tx.add_proofs(
  299. proofs.clone(),
  300. Some(quote_id.clone()),
  301. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  302. )
  303. .await
  304. .unwrap();
  305. tx.commit().await.unwrap();
  306. // Verify proofs exist
  307. let retrieved = db.get_proofs_by_ys(&ys).await.unwrap();
  308. assert_eq!(retrieved.len(), 2);
  309. // Note: proofs may not be returned in the same order or may be filtered
  310. let found_count = retrieved.iter().filter(|p| p.is_some()).count();
  311. assert!(found_count >= 1, "At least one proof should exist");
  312. // Remove first proof
  313. let mut tx = Database::begin_transaction(&db).await.unwrap();
  314. tx.remove_proofs(&[ys[0]], Some(quote_id)).await.unwrap();
  315. tx.commit().await.unwrap();
  316. // Verify proof was removed or marked as removed
  317. let retrieved = db.get_proofs_by_ys(&ys).await.unwrap();
  318. assert_eq!(retrieved.len(), 2);
  319. }
  320. /// Test get total redeemed by keyset
  321. pub async fn get_total_redeemed<DB>(db: DB)
  322. where
  323. DB: Database<Error> + KeysDatabase<Err = Error>,
  324. {
  325. use cashu::State;
  326. let keyset_id = setup_keyset(&db).await;
  327. let quote_id = QuoteId::new_uuid();
  328. let proofs = vec![
  329. Proof {
  330. amount: Amount::from(100),
  331. keyset_id,
  332. secret: Secret::generate(),
  333. c: SecretKey::generate().public_key(),
  334. witness: None,
  335. dleq: None,
  336. },
  337. Proof {
  338. amount: Amount::from(200),
  339. keyset_id,
  340. secret: Secret::generate(),
  341. c: SecretKey::generate().public_key(),
  342. witness: None,
  343. dleq: None,
  344. },
  345. Proof {
  346. amount: Amount::from(300),
  347. keyset_id,
  348. secret: Secret::generate(),
  349. c: SecretKey::generate().public_key(),
  350. witness: None,
  351. dleq: None,
  352. },
  353. ];
  354. let ys: Vec<_> = proofs.iter().map(|p| p.c).collect();
  355. // Add proofs
  356. let mut tx = Database::begin_transaction(&db).await.unwrap();
  357. tx.add_proofs(
  358. proofs.clone(),
  359. Some(quote_id),
  360. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  361. )
  362. .await
  363. .unwrap();
  364. tx.commit().await.unwrap();
  365. // First update to Pending (valid state transition)
  366. let mut tx = Database::begin_transaction(&db).await.unwrap();
  367. let mut proofs = tx.get_proofs(&[ys[0], ys[1]]).await.unwrap();
  368. check_state_transition(proofs.state, State::Pending).unwrap();
  369. tx.update_proofs_state(&mut proofs, State::Pending)
  370. .await
  371. .unwrap();
  372. tx.commit().await.unwrap();
  373. // Then mark some as spent
  374. let mut tx = Database::begin_transaction(&db).await.unwrap();
  375. let mut proofs = tx.get_proofs(&[ys[0], ys[1]]).await.unwrap();
  376. check_state_transition(proofs.state, State::Spent).unwrap();
  377. tx.update_proofs_state(&mut proofs, State::Spent)
  378. .await
  379. .unwrap();
  380. tx.commit().await.unwrap();
  381. // Get total redeemed
  382. let totals = db.get_total_redeemed().await.unwrap();
  383. let total = totals.get(&keyset_id).copied().unwrap_or(Amount::ZERO);
  384. // Should be 300 (100 + 200)
  385. assert!(total >= Amount::from(300));
  386. }
  387. /// Test get proof ys by quote id
  388. pub async fn get_proof_ys_by_quote_id<DB>(db: DB)
  389. where
  390. DB: Database<Error> + KeysDatabase<Err = Error>,
  391. {
  392. let keyset_id = setup_keyset(&db).await;
  393. let quote_id1 = QuoteId::new_uuid();
  394. let quote_id2 = QuoteId::new_uuid();
  395. let proofs1 = vec![
  396. Proof {
  397. amount: Amount::from(100),
  398. keyset_id,
  399. secret: Secret::generate(),
  400. c: SecretKey::generate().public_key(),
  401. witness: None,
  402. dleq: None,
  403. },
  404. Proof {
  405. amount: Amount::from(200),
  406. keyset_id,
  407. secret: Secret::generate(),
  408. c: SecretKey::generate().public_key(),
  409. witness: None,
  410. dleq: None,
  411. },
  412. ];
  413. let proofs2 = vec![Proof {
  414. amount: Amount::from(300),
  415. keyset_id,
  416. secret: Secret::generate(),
  417. c: SecretKey::generate().public_key(),
  418. witness: None,
  419. dleq: None,
  420. }];
  421. let expected_ys1: Vec<_> = proofs1.iter().map(|p| p.c).collect();
  422. let expected_ys2: Vec<_> = proofs2.iter().map(|p| p.c).collect();
  423. // Add proofs with different quote ids
  424. let mut tx = Database::begin_transaction(&db).await.unwrap();
  425. tx.add_proofs(
  426. proofs1.clone(),
  427. Some(quote_id1.clone()),
  428. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  429. )
  430. .await
  431. .unwrap();
  432. tx.add_proofs(
  433. proofs2.clone(),
  434. Some(quote_id2.clone()),
  435. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  436. )
  437. .await
  438. .unwrap();
  439. tx.commit().await.unwrap();
  440. // Get ys for first quote
  441. let ys1 = db.get_proof_ys_by_quote_id(&quote_id1).await.unwrap();
  442. assert_eq!(ys1.len(), 2);
  443. assert!(ys1.contains(&expected_ys1[0]));
  444. assert!(ys1.contains(&expected_ys1[1]));
  445. // Get ys for second quote
  446. let ys2 = db.get_proof_ys_by_quote_id(&quote_id2).await.unwrap();
  447. assert_eq!(ys2.len(), 1);
  448. assert!(ys2.contains(&expected_ys2[0]));
  449. }
  450. /// Test getting proofs states
  451. pub async fn get_proofs_states<DB>(db: DB)
  452. where
  453. DB: Database<Error> + KeysDatabase<Err = Error>,
  454. {
  455. use cashu::State;
  456. let keyset_id = setup_keyset(&db).await;
  457. let quote_id = QuoteId::new_uuid();
  458. let proofs = vec![
  459. Proof {
  460. amount: Amount::from(100),
  461. keyset_id,
  462. secret: Secret::generate(),
  463. c: SecretKey::generate().public_key(),
  464. witness: None,
  465. dleq: None,
  466. },
  467. Proof {
  468. amount: Amount::from(200),
  469. keyset_id,
  470. secret: Secret::generate(),
  471. c: SecretKey::generate().public_key(),
  472. witness: None,
  473. dleq: None,
  474. },
  475. ];
  476. let ys: Vec<_> = proofs.iter().map(|p| p.c).collect();
  477. // Add proofs
  478. let mut tx = Database::begin_transaction(&db).await.unwrap();
  479. tx.add_proofs(
  480. proofs.clone(),
  481. Some(quote_id),
  482. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  483. )
  484. .await
  485. .unwrap();
  486. tx.commit().await.unwrap();
  487. // Get states - behavior may vary by implementation
  488. let states = db.get_proofs_states(&ys).await.unwrap();
  489. assert_eq!(states.len(), 2);
  490. // Check that at least we get a proper response
  491. // States may or may not be present depending on how the database stores proofs
  492. for s in states.iter().flatten() {
  493. // If state is present, it should be a valid state
  494. match s {
  495. State::Unspent
  496. | State::Reserved
  497. | State::Pending
  498. | State::Spent
  499. | State::PendingSpent => {}
  500. }
  501. }
  502. // It's OK if state is None for some implementations
  503. }
  504. /// Test getting states for non-existent proofs
  505. pub async fn get_nonexistent_proof_states<DB>(db: DB)
  506. where
  507. DB: Database<Error> + KeysDatabase<Err = Error>,
  508. {
  509. let y1 = SecretKey::generate().public_key();
  510. let y2 = SecretKey::generate().public_key();
  511. // Try to get states for non-existent proofs
  512. let states = db.get_proofs_states(&[y1, y2]).await.unwrap();
  513. assert_eq!(states.len(), 2);
  514. assert!(states[0].is_none());
  515. assert!(states[1].is_none());
  516. }
  517. /// Test getting proofs by non-existent ys
  518. pub async fn get_proofs_by_nonexistent_ys<DB>(db: DB)
  519. where
  520. DB: Database<Error> + KeysDatabase<Err = Error>,
  521. {
  522. let y1 = SecretKey::generate().public_key();
  523. let y2 = SecretKey::generate().public_key();
  524. // Try to get proofs for non-existent ys
  525. let proofs = db.get_proofs_by_ys(&[y1, y2]).await.unwrap();
  526. assert_eq!(proofs.len(), 2);
  527. assert!(proofs[0].is_none());
  528. assert!(proofs[1].is_none());
  529. }
  530. /// Test proof transaction isolation - verifies that changes are only visible after commit
  531. pub async fn proof_transaction_isolation<DB>(db: DB)
  532. where
  533. DB: Database<Error> + KeysDatabase<Err = Error>,
  534. {
  535. let keyset_id = setup_keyset(&db).await;
  536. let quote_id = QuoteId::new_uuid();
  537. let proof = Proof {
  538. amount: Amount::from(100),
  539. keyset_id,
  540. secret: Secret::generate(),
  541. c: SecretKey::generate().public_key(),
  542. witness: None,
  543. dleq: None,
  544. };
  545. let y = proof.c;
  546. // Verify proof doesn't exist before transaction
  547. let proofs_before = db.get_proofs_by_ys(&[y]).await.unwrap();
  548. assert_eq!(proofs_before.len(), 1);
  549. assert!(proofs_before[0].is_none());
  550. // Start a transaction and add proof but don't commit
  551. let mut tx = Database::begin_transaction(&db).await.unwrap();
  552. tx.add_proofs(
  553. vec![proof.clone()],
  554. Some(quote_id),
  555. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  556. )
  557. .await
  558. .unwrap();
  559. // Commit the transaction
  560. tx.commit().await.unwrap();
  561. // After commit, verify the proof state is available
  562. let states = db.get_proofs_states(&[y]).await.unwrap();
  563. assert_eq!(states.len(), 1);
  564. // Verify we get a valid state response (behavior may vary by implementation)
  565. }
  566. /// Test rollback prevents proof insertion
  567. pub async fn proof_rollback<DB>(db: DB)
  568. where
  569. DB: Database<Error> + KeysDatabase<Err = Error>,
  570. {
  571. let keyset_id = setup_keyset(&db).await;
  572. let quote_id = QuoteId::new_uuid();
  573. let proof = Proof {
  574. amount: Amount::from(100),
  575. keyset_id,
  576. secret: Secret::generate(),
  577. c: SecretKey::generate().public_key(),
  578. witness: None,
  579. dleq: None,
  580. };
  581. let y = proof.c;
  582. // Start a transaction, add proof, then rollback
  583. let mut tx = Database::begin_transaction(&db).await.unwrap();
  584. tx.add_proofs(
  585. vec![proof.clone()],
  586. Some(quote_id),
  587. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  588. )
  589. .await
  590. .unwrap();
  591. tx.rollback().await.unwrap();
  592. // Proof should not exist after rollback
  593. let proofs = db.get_proofs_by_ys(&[y]).await.unwrap();
  594. assert_eq!(proofs.len(), 1);
  595. assert!(proofs[0].is_none());
  596. }
  597. /// Test multiple proofs with same keyset
  598. pub async fn multiple_proofs_same_keyset<DB>(db: DB)
  599. where
  600. DB: Database<Error> + KeysDatabase<Err = Error>,
  601. {
  602. let keyset_id = setup_keyset(&db).await;
  603. let proofs: Vec<_> = (0..10)
  604. .map(|i| Proof {
  605. amount: Amount::from((i + 1) * 100),
  606. keyset_id,
  607. secret: Secret::generate(),
  608. c: SecretKey::generate().public_key(),
  609. witness: None,
  610. dleq: None,
  611. })
  612. .collect();
  613. // Add all proofs
  614. let mut tx = Database::begin_transaction(&db).await.unwrap();
  615. tx.add_proofs(
  616. proofs.clone(),
  617. None,
  618. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  619. )
  620. .await
  621. .unwrap();
  622. tx.commit().await.unwrap();
  623. // Get proofs by keyset
  624. let (retrieved_proofs, states) = db.get_proofs_by_keyset_id(&keyset_id).await.unwrap();
  625. assert!(retrieved_proofs.len() >= 10);
  626. assert_eq!(retrieved_proofs.len(), states.len());
  627. // Calculate total amount
  628. let total: u64 = retrieved_proofs.iter().map(|p| u64::from(p.amount)).sum();
  629. assert!(total >= 5500); // 100 + 200 + ... + 1000 = 5500
  630. }
  631. /// Test that removing proofs in Spent state should fail
  632. ///
  633. /// This test verifies that the storage layer enforces the constraint that proofs
  634. /// in the `Spent` state cannot be removed via `remove_proofs`. The operation should
  635. /// fail with an error to prevent accidental deletion of spent proofs.
  636. pub async fn remove_spent_proofs_should_fail<DB>(db: DB)
  637. where
  638. DB: Database<Error> + KeysDatabase<Err = Error>,
  639. {
  640. use cashu::State;
  641. let keyset_id = setup_keyset(&db).await;
  642. let quote_id = QuoteId::new_uuid();
  643. let proofs = vec![
  644. Proof {
  645. amount: Amount::from(100),
  646. keyset_id,
  647. secret: Secret::generate(),
  648. c: SecretKey::generate().public_key(),
  649. witness: None,
  650. dleq: None,
  651. },
  652. Proof {
  653. amount: Amount::from(200),
  654. keyset_id,
  655. secret: Secret::generate(),
  656. c: SecretKey::generate().public_key(),
  657. witness: None,
  658. dleq: None,
  659. },
  660. ];
  661. let ys: Vec<_> = proofs.iter().map(|p| p.y().unwrap()).collect();
  662. // Add proofs to database (initial state is Unspent)
  663. let mut tx = Database::begin_transaction(&db).await.unwrap();
  664. tx.add_proofs(
  665. proofs.clone(),
  666. Some(quote_id.clone()),
  667. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  668. )
  669. .await
  670. .unwrap();
  671. tx.commit().await.unwrap();
  672. // Verify proofs exist and are in Unspent state
  673. let states = db.get_proofs_states(&ys).await.unwrap();
  674. assert_eq!(states.len(), 2);
  675. assert_eq!(states[0], Some(State::Unspent));
  676. assert_eq!(states[1], Some(State::Unspent));
  677. // Removing Unspent proofs should succeed
  678. let mut tx = Database::begin_transaction(&db).await.unwrap();
  679. let result = tx.remove_proofs(&[ys[0]], Some(quote_id.clone())).await;
  680. assert!(result.is_ok(), "Removing Unspent proof should succeed");
  681. tx.rollback().await.unwrap(); // Rollback to keep proofs for next test
  682. // Transition proofs to Pending state
  683. let mut tx = Database::begin_transaction(&db).await.unwrap();
  684. let mut records = tx.get_proofs(&ys).await.expect("valid records");
  685. check_state_transition(records.state, State::Pending).unwrap();
  686. tx.update_proofs_state(&mut records, State::Pending)
  687. .await
  688. .unwrap();
  689. tx.commit().await.unwrap();
  690. // Removing Pending proofs should also succeed
  691. let mut tx = Database::begin_transaction(&db).await.unwrap();
  692. let result = tx.remove_proofs(&[ys[0]], Some(quote_id.clone())).await;
  693. assert!(
  694. result.is_ok(),
  695. "Removing Pending proof should succeed: {:?}",
  696. result,
  697. );
  698. tx.rollback().await.unwrap(); // Rollback to keep proofs for next test
  699. // Now transition proofs to Spent state
  700. let mut tx = Database::begin_transaction(&db).await.unwrap();
  701. let mut records = tx.get_proofs(&ys).await.expect("valid records");
  702. check_state_transition(records.state, State::Spent).unwrap();
  703. tx.update_proofs_state(&mut records, State::Spent)
  704. .await
  705. .unwrap();
  706. tx.commit().await.unwrap();
  707. // Verify proofs are now in Spent state
  708. let states = db.get_proofs_states(&ys).await.unwrap();
  709. assert_eq!(states[0], Some(State::Spent));
  710. assert_eq!(states[1], Some(State::Spent));
  711. // Attempt to remove Spent proofs - this should FAIL
  712. let mut tx = Database::begin_transaction(&db).await.unwrap();
  713. let result = tx.remove_proofs(&ys, Some(quote_id.clone())).await;
  714. assert!(
  715. result.is_err(),
  716. "Removing proofs in Spent state should fail"
  717. );
  718. // Verify the error is the expected type
  719. assert!(
  720. matches!(result.unwrap_err(), Error::AttemptRemoveSpentProof),
  721. "Error should be AttemptRemoveSpentProof"
  722. );
  723. // Rollback the failed transaction to release locks
  724. tx.rollback().await.unwrap();
  725. // Verify proofs still exist after failed removal attempt
  726. let states = db.get_proofs_states(&ys).await.unwrap();
  727. assert_eq!(
  728. states[0],
  729. Some(State::Spent),
  730. "First proof should still exist"
  731. );
  732. assert_eq!(
  733. states[1],
  734. Some(State::Spent),
  735. "Second proof should still exist"
  736. );
  737. }
  738. /// Test that get_proofs fails when proofs have inconsistent states
  739. ///
  740. /// This validates the database layer's responsibility to ensure all proofs
  741. /// returned by get_proofs share the same state. The mint never needs proofs
  742. /// with different states, so this is an invariant the database must enforce.
  743. pub async fn get_proofs_with_inconsistent_states_fails<DB>(db: DB)
  744. where
  745. DB: Database<Error> + KeysDatabase<Err = Error>,
  746. {
  747. use cashu::State;
  748. let keyset_id = setup_keyset(&db).await;
  749. let quote_id = QuoteId::new_uuid();
  750. // Create three proofs
  751. let proofs = vec![
  752. Proof {
  753. amount: Amount::from(100),
  754. keyset_id,
  755. secret: Secret::generate(),
  756. c: SecretKey::generate().public_key(),
  757. witness: None,
  758. dleq: None,
  759. },
  760. Proof {
  761. amount: Amount::from(200),
  762. keyset_id,
  763. secret: Secret::generate(),
  764. c: SecretKey::generate().public_key(),
  765. witness: None,
  766. dleq: None,
  767. },
  768. Proof {
  769. amount: Amount::from(300),
  770. keyset_id,
  771. secret: Secret::generate(),
  772. c: SecretKey::generate().public_key(),
  773. witness: None,
  774. dleq: None,
  775. },
  776. ];
  777. let ys: Vec<_> = proofs.iter().map(|p| p.y().unwrap()).collect();
  778. // Add all proofs (initial state is Unspent)
  779. let mut tx = Database::begin_transaction(&db).await.unwrap();
  780. tx.add_proofs(
  781. proofs,
  782. Some(quote_id),
  783. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  784. )
  785. .await
  786. .unwrap();
  787. tx.commit().await.unwrap();
  788. // Transition only the first two proofs to Pending state
  789. let mut tx = Database::begin_transaction(&db).await.unwrap();
  790. let mut first_two_proofs = tx.get_proofs(&ys[0..2]).await.unwrap();
  791. check_state_transition(first_two_proofs.state, State::Pending).unwrap();
  792. tx.update_proofs_state(&mut first_two_proofs, State::Pending)
  793. .await
  794. .unwrap();
  795. tx.commit().await.unwrap();
  796. // Verify the states are now inconsistent via get_proofs_states
  797. let states = db.get_proofs_states(&ys).await.unwrap();
  798. assert_eq!(
  799. states[0],
  800. Some(State::Pending),
  801. "First proof should be Pending"
  802. );
  803. assert_eq!(
  804. states[1],
  805. Some(State::Pending),
  806. "Second proof should be Pending"
  807. );
  808. assert_eq!(
  809. states[2],
  810. Some(State::Unspent),
  811. "Third proof should be Unspent"
  812. );
  813. // Now try to get all three proofs via get_proofs - this should fail
  814. // because the proofs have inconsistent states
  815. let mut tx = Database::begin_transaction(&db).await.unwrap();
  816. let result = tx.get_proofs(&ys).await;
  817. assert!(
  818. result.is_err(),
  819. "get_proofs should fail when proofs have inconsistent states"
  820. );
  821. tx.rollback().await.unwrap();
  822. }
  823. /// Test that get_proofs fails when some requested proofs don't exist
  824. ///
  825. /// This validates that the database returns an error when not all requested
  826. /// proofs are found, rather than silently returning a partial result.
  827. pub async fn get_proofs_fails_when_some_not_found<DB>(db: DB)
  828. where
  829. DB: Database<Error> + KeysDatabase<Err = Error>,
  830. {
  831. let keyset_id = setup_keyset(&db).await;
  832. let quote_id = QuoteId::new_uuid();
  833. // Create two proofs that will be stored
  834. let stored_proofs = vec![
  835. Proof {
  836. amount: Amount::from(100),
  837. keyset_id,
  838. secret: Secret::generate(),
  839. c: SecretKey::generate().public_key(),
  840. witness: None,
  841. dleq: None,
  842. },
  843. Proof {
  844. amount: Amount::from(200),
  845. keyset_id,
  846. secret: Secret::generate(),
  847. c: SecretKey::generate().public_key(),
  848. witness: None,
  849. dleq: None,
  850. },
  851. ];
  852. // Create a third proof that will NOT be stored
  853. let non_existent_proof = Proof {
  854. amount: Amount::from(300),
  855. keyset_id,
  856. secret: Secret::generate(),
  857. c: SecretKey::generate().public_key(),
  858. witness: None,
  859. dleq: None,
  860. };
  861. let stored_ys: Vec<_> = stored_proofs.iter().map(|p| p.y().unwrap()).collect();
  862. let non_existent_y = non_existent_proof.y().unwrap();
  863. // Add only the first two proofs
  864. let mut tx = Database::begin_transaction(&db).await.unwrap();
  865. tx.add_proofs(
  866. stored_proofs,
  867. Some(quote_id),
  868. &Operation::new_swap(Amount::ZERO, Amount::ZERO, Amount::ZERO),
  869. )
  870. .await
  871. .unwrap();
  872. tx.commit().await.unwrap();
  873. // Verify the stored proofs exist
  874. let states = db.get_proofs_states(&stored_ys).await.unwrap();
  875. assert_eq!(states.len(), 2);
  876. assert!(states[0].is_some(), "First proof should exist");
  877. assert!(states[1].is_some(), "Second proof should exist");
  878. // Verify the non-existent proof doesn't exist
  879. let states = db.get_proofs_states(&[non_existent_y]).await.unwrap();
  880. assert_eq!(states[0], None, "Third proof should not exist");
  881. // Now try to get all three proofs (2 exist, 1 doesn't) - this should fail
  882. let all_ys = vec![stored_ys[0], stored_ys[1], non_existent_y];
  883. let mut tx = Database::begin_transaction(&db).await.unwrap();
  884. let result = tx.get_proofs(&all_ys).await;
  885. assert!(
  886. result.is_err(),
  887. "get_proofs should fail when some proofs don't exist (got 2 of 3)"
  888. );
  889. tx.rollback().await.unwrap();
  890. }