integration_tests_pure.rs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880
  1. //! This file contains integration tests for the Cashu Development Kit (CDK)
  2. //!
  3. //! These tests verify the interaction between mint and wallet components, simulating real-world usage scenarios.
  4. //! They test the complete flow of operations including wallet funding, token swapping, sending tokens between wallets,
  5. //! and other operations that require client-mint interaction.
  6. use std::assert_eq;
  7. use std::collections::{HashMap, HashSet};
  8. use std::hash::RandomState;
  9. use std::str::FromStr;
  10. use cashu::dhke::construct_proofs;
  11. use cashu::mint_url::MintUrl;
  12. use cashu::{
  13. CurrencyUnit, Id, MeltBolt11Request, NotificationPayload, PreMintSecrets, ProofState,
  14. SecretKey, SpendingConditions, State, SwapRequest,
  15. };
  16. use cdk::amount::SplitTarget;
  17. use cdk::mint::Mint;
  18. use cdk::nuts::nut00::ProofsMethods;
  19. use cdk::subscription::{IndexableParams, Params};
  20. use cdk::wallet::SendOptions;
  21. use cdk::Amount;
  22. use cdk_fake_wallet::create_fake_invoice;
  23. use cdk_integration_tests::init_pure_tests::*;
  24. /// Tests the token swap and send functionality:
  25. /// 1. Alice gets funded with 64 sats
  26. /// 2. Alice prepares to send 40 sats (which requires internal swapping)
  27. /// 3. Alice sends the token
  28. /// 4. Carol receives the token and has the correct balance
  29. #[tokio::test]
  30. async fn test_swap_to_send() {
  31. setup_tracing();
  32. let mint_bob = create_and_start_test_mint()
  33. .await
  34. .expect("Failed to create test mint");
  35. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  36. .await
  37. .expect("Failed to create test wallet");
  38. // Alice gets 64 sats
  39. fund_wallet(wallet_alice.clone(), 64, None)
  40. .await
  41. .expect("Failed to fund wallet");
  42. let balance_alice = wallet_alice
  43. .total_balance()
  44. .await
  45. .expect("Failed to get balance");
  46. assert_eq!(Amount::from(64), balance_alice);
  47. // Alice wants to send 40 sats, which internally swaps
  48. let prepared_send = wallet_alice
  49. .prepare_send(Amount::from(40), SendOptions::default())
  50. .await
  51. .expect("Failed to prepare send");
  52. assert_eq!(
  53. HashSet::<_, RandomState>::from_iter(
  54. prepared_send.proofs().ys().expect("Failed to get ys")
  55. ),
  56. HashSet::from_iter(
  57. wallet_alice
  58. .get_reserved_proofs()
  59. .await
  60. .expect("Failed to get reserved proofs")
  61. .ys()
  62. .expect("Failed to get ys")
  63. )
  64. );
  65. let token = wallet_alice
  66. .send(prepared_send, None)
  67. .await
  68. .expect("Failed to send token");
  69. assert_eq!(
  70. Amount::from(40),
  71. token
  72. .proofs()
  73. .total_amount()
  74. .expect("Failed to get total amount")
  75. );
  76. assert_eq!(
  77. Amount::from(24),
  78. wallet_alice
  79. .total_balance()
  80. .await
  81. .expect("Failed to get balance")
  82. );
  83. assert_eq!(
  84. HashSet::<_, RandomState>::from_iter(token.proofs().ys().expect("Failed to get ys")),
  85. HashSet::from_iter(
  86. wallet_alice
  87. .get_pending_spent_proofs()
  88. .await
  89. .expect("Failed to get pending spent proofs")
  90. .ys()
  91. .expect("Failed to get ys")
  92. )
  93. );
  94. // Alice sends cashu, Carol receives
  95. let wallet_carol = create_test_wallet_for_mint(mint_bob.clone())
  96. .await
  97. .expect("Failed to create Carol's wallet");
  98. let received_amount = wallet_carol
  99. .receive_proofs(token.proofs(), SplitTarget::None, &[], &[])
  100. .await
  101. .expect("Failed to receive proofs");
  102. assert_eq!(Amount::from(40), received_amount);
  103. assert_eq!(
  104. Amount::from(40),
  105. wallet_carol
  106. .total_balance()
  107. .await
  108. .expect("Failed to get Carol's balance")
  109. );
  110. }
  111. /// Tests the NUT-06 functionality (mint discovery):
  112. /// 1. Alice gets funded with 64 sats
  113. /// 2. Verifies the initial mint URL is in the mint info
  114. /// 3. Updates the mint URL to a new value
  115. /// 4. Verifies the wallet balance is maintained after changing the mint URL
  116. #[tokio::test]
  117. async fn test_mint_nut06() {
  118. setup_tracing();
  119. let mint_bob = create_and_start_test_mint()
  120. .await
  121. .expect("Failed to create test mint");
  122. let mut wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  123. .await
  124. .expect("Failed to create test wallet");
  125. // Alice gets 64 sats
  126. fund_wallet(wallet_alice.clone(), 64, None)
  127. .await
  128. .expect("Failed to fund wallet");
  129. let balance_alice = wallet_alice
  130. .total_balance()
  131. .await
  132. .expect("Failed to get balance");
  133. assert_eq!(Amount::from(64), balance_alice);
  134. let initial_mint_url = wallet_alice.mint_url.clone();
  135. let mint_info_before = wallet_alice
  136. .get_mint_info()
  137. .await
  138. .expect("Failed to get mint info")
  139. .unwrap();
  140. assert!(mint_info_before
  141. .urls
  142. .unwrap()
  143. .contains(&initial_mint_url.to_string()));
  144. // Wallet updates mint URL
  145. let new_mint_url = MintUrl::from_str("https://new-mint-url").expect("Failed to parse mint URL");
  146. wallet_alice
  147. .update_mint_url(new_mint_url.clone())
  148. .await
  149. .expect("Failed to update mint URL");
  150. // Check balance after mint URL was updated
  151. let balance_alice_after = wallet_alice
  152. .total_balance()
  153. .await
  154. .expect("Failed to get balance after URL update");
  155. assert_eq!(Amount::from(64), balance_alice_after);
  156. }
  157. /// Attempt to double spend proofs on swap
  158. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  159. async fn test_mint_double_spend() {
  160. setup_tracing();
  161. let mint_bob = create_and_start_test_mint()
  162. .await
  163. .expect("Failed to create test mint");
  164. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  165. .await
  166. .expect("Failed to create test wallet");
  167. // Alice gets 64 sats
  168. fund_wallet(wallet_alice.clone(), 64, None)
  169. .await
  170. .expect("Failed to fund wallet");
  171. let proofs = wallet_alice
  172. .get_unspent_proofs()
  173. .await
  174. .expect("Could not get proofs");
  175. let keys = mint_bob
  176. .pubkeys()
  177. .await
  178. .unwrap()
  179. .keysets
  180. .first()
  181. .unwrap()
  182. .clone()
  183. .keys;
  184. let keyset_id = Id::from(&keys);
  185. let preswap = PreMintSecrets::random(
  186. keyset_id,
  187. proofs.total_amount().unwrap(),
  188. &SplitTarget::default(),
  189. )
  190. .unwrap();
  191. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  192. let swap = mint_bob.process_swap_request(swap_request).await;
  193. assert!(swap.is_ok());
  194. let preswap_two = PreMintSecrets::random(
  195. keyset_id,
  196. proofs.total_amount().unwrap(),
  197. &SplitTarget::default(),
  198. )
  199. .unwrap();
  200. let swap_two_request = SwapRequest::new(proofs, preswap_two.blinded_messages());
  201. match mint_bob.process_swap_request(swap_two_request).await {
  202. Ok(_) => panic!("Proofs double spent"),
  203. Err(err) => match err {
  204. cdk::Error::TokenAlreadySpent => (),
  205. _ => panic!("Wrong error returned"),
  206. },
  207. }
  208. }
  209. /// This attempts to swap for more outputs then inputs.
  210. /// This will work if the mint does not check for outputs amounts overflowing
  211. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  212. async fn test_attempt_to_swap_by_overflowing() {
  213. setup_tracing();
  214. let mint_bob = create_and_start_test_mint()
  215. .await
  216. .expect("Failed to create test mint");
  217. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  218. .await
  219. .expect("Failed to create test wallet");
  220. // Alice gets 64 sats
  221. fund_wallet(wallet_alice.clone(), 64, None)
  222. .await
  223. .expect("Failed to fund wallet");
  224. let proofs = wallet_alice
  225. .get_unspent_proofs()
  226. .await
  227. .expect("Could not get proofs");
  228. let amount = 2_u64.pow(63);
  229. let keys = mint_bob
  230. .pubkeys()
  231. .await
  232. .unwrap()
  233. .keysets
  234. .first()
  235. .unwrap()
  236. .clone()
  237. .keys;
  238. let keyset_id = Id::from(&keys);
  239. let pre_mint_amount =
  240. PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default()).unwrap();
  241. let pre_mint_amount_two =
  242. PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default()).unwrap();
  243. let mut pre_mint =
  244. PreMintSecrets::random(keyset_id, 1.into(), &SplitTarget::default()).unwrap();
  245. pre_mint.combine(pre_mint_amount);
  246. pre_mint.combine(pre_mint_amount_two);
  247. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  248. match mint_bob.process_swap_request(swap_request).await {
  249. Ok(_) => panic!("Swap occurred with overflow"),
  250. Err(err) => match err {
  251. cdk::Error::NUT03(cdk::nuts::nut03::Error::Amount(_)) => (),
  252. cdk::Error::AmountOverflow => (),
  253. cdk::Error::AmountError(_) => (),
  254. _ => {
  255. println!("{:?}", err);
  256. panic!("Wrong error returned in swap overflow")
  257. }
  258. },
  259. }
  260. }
  261. /// Tests that the mint correctly rejects unbalanced swap requests:
  262. /// 1. Attempts to swap for less than the input amount (95 < 100)
  263. /// 2. Attempts to swap for more than the input amount (101 > 100)
  264. /// 3. Both should fail with TransactionUnbalanced error
  265. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  266. async fn test_swap_unbalanced() {
  267. setup_tracing();
  268. let mint_bob = create_and_start_test_mint()
  269. .await
  270. .expect("Failed to create test mint");
  271. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  272. .await
  273. .expect("Failed to create test wallet");
  274. // Alice gets 100 sats
  275. fund_wallet(wallet_alice.clone(), 100, None)
  276. .await
  277. .expect("Failed to fund wallet");
  278. let proofs = wallet_alice
  279. .get_unspent_proofs()
  280. .await
  281. .expect("Could not get proofs");
  282. let keyset_id = get_keyset_id(&mint_bob).await;
  283. // Try to swap for less than the input amount (95 < 100)
  284. let preswap = PreMintSecrets::random(keyset_id, 95.into(), &SplitTarget::default())
  285. .expect("Failed to create preswap");
  286. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  287. match mint_bob.process_swap_request(swap_request).await {
  288. Ok(_) => panic!("Swap was allowed unbalanced"),
  289. Err(err) => match err {
  290. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  291. _ => panic!("Wrong error returned"),
  292. },
  293. }
  294. // Try to swap for more than the input amount (101 > 100)
  295. let preswap = PreMintSecrets::random(keyset_id, 101.into(), &SplitTarget::default())
  296. .expect("Failed to create preswap");
  297. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  298. match mint_bob.process_swap_request(swap_request).await {
  299. Ok(_) => panic!("Swap was allowed unbalanced"),
  300. Err(err) => match err {
  301. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  302. _ => panic!("Wrong error returned"),
  303. },
  304. }
  305. }
  306. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  307. pub async fn test_p2pk_swap() {
  308. setup_tracing();
  309. let mint_bob = create_and_start_test_mint()
  310. .await
  311. .expect("Failed to create test mint");
  312. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  313. .await
  314. .expect("Failed to create test wallet");
  315. // Alice gets 100 sats
  316. fund_wallet(wallet_alice.clone(), 100, None)
  317. .await
  318. .expect("Failed to fund wallet");
  319. let proofs = wallet_alice
  320. .get_unspent_proofs()
  321. .await
  322. .expect("Could not get proofs");
  323. let keyset_id = get_keyset_id(&mint_bob).await;
  324. let secret = SecretKey::generate();
  325. let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
  326. let pre_swap = PreMintSecrets::with_conditions(
  327. keyset_id,
  328. 100.into(),
  329. &SplitTarget::default(),
  330. &spending_conditions,
  331. )
  332. .unwrap();
  333. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  334. let keys = mint_bob
  335. .pubkeys()
  336. .await
  337. .unwrap()
  338. .keysets
  339. .first()
  340. .cloned()
  341. .unwrap()
  342. .keys;
  343. let post_swap = mint_bob.process_swap_request(swap_request).await.unwrap();
  344. let mut proofs = construct_proofs(
  345. post_swap.signatures,
  346. pre_swap.rs(),
  347. pre_swap.secrets(),
  348. &keys,
  349. )
  350. .unwrap();
  351. let pre_swap = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default()).unwrap();
  352. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  353. // Listen for status updates on all input proof pks
  354. let public_keys_to_listen: Vec<_> = swap_request
  355. .inputs()
  356. .ys()
  357. .unwrap()
  358. .iter()
  359. .map(|pk| pk.to_string())
  360. .collect();
  361. let mut listener = mint_bob
  362. .pubsub_manager
  363. .try_subscribe::<IndexableParams>(
  364. Params {
  365. kind: cdk::nuts::nut17::Kind::ProofState,
  366. filters: public_keys_to_listen.clone(),
  367. id: "test".into(),
  368. }
  369. .into(),
  370. )
  371. .await
  372. .expect("valid subscription");
  373. match mint_bob.process_swap_request(swap_request).await {
  374. Ok(_) => panic!("Proofs spent without sig"),
  375. Err(err) => match err {
  376. cdk::Error::NUT11(cdk::nuts::nut11::Error::SignaturesNotProvided) => (),
  377. _ => {
  378. println!("{:?}", err);
  379. panic!("Wrong error returned")
  380. }
  381. },
  382. }
  383. for proof in &mut proofs {
  384. proof.sign_p2pk(secret.clone()).unwrap();
  385. }
  386. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  387. let attempt_swap = mint_bob.process_swap_request(swap_request).await;
  388. assert!(attempt_swap.is_ok());
  389. let mut msgs = HashMap::new();
  390. while let Ok((sub_id, msg)) = listener.try_recv() {
  391. assert_eq!(sub_id, "test".into());
  392. match msg {
  393. NotificationPayload::ProofState(ProofState { y, state, .. }) => {
  394. msgs.entry(y.to_string())
  395. .or_insert_with(Vec::new)
  396. .push(state);
  397. }
  398. _ => panic!("Wrong message received"),
  399. }
  400. }
  401. for keys in public_keys_to_listen {
  402. let statuses = msgs.remove(&keys).expect("some events");
  403. // Every input pk receives two state updates, as there are only two state transitions
  404. assert_eq!(statuses, vec![State::Pending, State::Spent]);
  405. }
  406. assert!(listener.try_recv().is_err(), "no other event is happening");
  407. assert!(msgs.is_empty(), "Only expected key events are received");
  408. }
  409. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  410. async fn test_swap_overpay_underpay_fee() {
  411. setup_tracing();
  412. let mint_bob = create_and_start_test_mint()
  413. .await
  414. .expect("Failed to create test mint");
  415. mint_bob
  416. .rotate_keyset(CurrencyUnit::Sat, 1, 32, 1, &HashMap::new())
  417. .await
  418. .unwrap();
  419. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  420. .await
  421. .expect("Failed to create test wallet");
  422. // Alice gets 100 sats
  423. fund_wallet(wallet_alice.clone(), 1000, None)
  424. .await
  425. .expect("Failed to fund wallet");
  426. let proofs = wallet_alice
  427. .get_unspent_proofs()
  428. .await
  429. .expect("Could not get proofs");
  430. let keys = mint_bob
  431. .pubkeys()
  432. .await
  433. .unwrap()
  434. .keysets
  435. .first()
  436. .unwrap()
  437. .clone()
  438. .keys;
  439. let keyset_id = Id::from(&keys);
  440. let preswap = PreMintSecrets::random(keyset_id, 9998.into(), &SplitTarget::default()).unwrap();
  441. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  442. // Attempt to swap overpaying fee
  443. match mint_bob.process_swap_request(swap_request).await {
  444. Ok(_) => panic!("Swap was allowed unbalanced"),
  445. Err(err) => match err {
  446. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  447. _ => {
  448. println!("{:?}", err);
  449. panic!("Wrong error returned")
  450. }
  451. },
  452. }
  453. let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default()).unwrap();
  454. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  455. // Attempt to swap underpaying fee
  456. match mint_bob.process_swap_request(swap_request).await {
  457. Ok(_) => panic!("Swap was allowed unbalanced"),
  458. Err(err) => match err {
  459. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  460. _ => {
  461. println!("{:?}", err);
  462. panic!("Wrong error returned")
  463. }
  464. },
  465. }
  466. }
  467. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  468. async fn test_mint_enforce_fee() {
  469. setup_tracing();
  470. let mint_bob = create_and_start_test_mint()
  471. .await
  472. .expect("Failed to create test mint");
  473. mint_bob
  474. .rotate_keyset(CurrencyUnit::Sat, 1, 32, 1, &HashMap::new())
  475. .await
  476. .unwrap();
  477. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  478. .await
  479. .expect("Failed to create test wallet");
  480. // Alice gets 100 sats
  481. fund_wallet(
  482. wallet_alice.clone(),
  483. 1010,
  484. Some(SplitTarget::Value(Amount::ONE)),
  485. )
  486. .await
  487. .expect("Failed to fund wallet");
  488. let mut proofs = wallet_alice
  489. .get_unspent_proofs()
  490. .await
  491. .expect("Could not get proofs");
  492. let keys = mint_bob
  493. .pubkeys()
  494. .await
  495. .unwrap()
  496. .keysets
  497. .first()
  498. .unwrap()
  499. .clone()
  500. .keys;
  501. let keyset_id = Id::from(&keys);
  502. let five_proofs: Vec<_> = proofs.drain(..5).collect();
  503. let preswap = PreMintSecrets::random(keyset_id, 5.into(), &SplitTarget::default()).unwrap();
  504. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  505. // Attempt to swap underpaying fee
  506. match mint_bob.process_swap_request(swap_request).await {
  507. Ok(_) => panic!("Swap was allowed unbalanced"),
  508. Err(err) => match err {
  509. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  510. _ => {
  511. println!("{:?}", err);
  512. panic!("Wrong error returned")
  513. }
  514. },
  515. }
  516. let preswap = PreMintSecrets::random(keyset_id, 4.into(), &SplitTarget::default()).unwrap();
  517. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  518. let res = mint_bob.process_swap_request(swap_request).await;
  519. assert!(res.is_ok());
  520. let thousnad_proofs: Vec<_> = proofs.drain(..1001).collect();
  521. let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default()).unwrap();
  522. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  523. // Attempt to swap underpaying fee
  524. match mint_bob.process_swap_request(swap_request).await {
  525. Ok(_) => panic!("Swap was allowed unbalanced"),
  526. Err(err) => match err {
  527. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  528. _ => {
  529. println!("{:?}", err);
  530. panic!("Wrong error returned")
  531. }
  532. },
  533. }
  534. let preswap = PreMintSecrets::random(keyset_id, 999.into(), &SplitTarget::default()).unwrap();
  535. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  536. let _ = mint_bob.process_swap_request(swap_request).await.unwrap();
  537. }
  538. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  539. async fn test_mint_change_with_fee_melt() {
  540. setup_tracing();
  541. let mint_bob = create_and_start_test_mint()
  542. .await
  543. .expect("Failed to create test mint");
  544. mint_bob
  545. .rotate_keyset(CurrencyUnit::Sat, 1, 32, 1, &HashMap::new())
  546. .await
  547. .unwrap();
  548. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  549. .await
  550. .expect("Failed to create test wallet");
  551. // Alice gets 100 sats
  552. fund_wallet(
  553. wallet_alice.clone(),
  554. 100,
  555. Some(SplitTarget::Value(Amount::ONE)),
  556. )
  557. .await
  558. .expect("Failed to fund wallet");
  559. let proofs = wallet_alice
  560. .get_unspent_proofs()
  561. .await
  562. .expect("Could not get proofs");
  563. let fake_invoice = create_fake_invoice(1000, "".to_string());
  564. let melt_quote = wallet_alice
  565. .melt_quote(fake_invoice.to_string(), None)
  566. .await
  567. .unwrap();
  568. let w = wallet_alice
  569. .melt_proofs(&melt_quote.id, proofs)
  570. .await
  571. .unwrap();
  572. assert_eq!(w.change.unwrap().total_amount().unwrap(), 97.into());
  573. }
  574. /// Tests concurrent double-spending attempts by trying to use the same proofs
  575. /// in 3 swap transactions simultaneously using tokio tasks
  576. #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
  577. async fn test_concurrent_double_spend_swap() {
  578. setup_tracing();
  579. let mint_bob = create_and_start_test_mint()
  580. .await
  581. .expect("Failed to create test mint");
  582. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  583. .await
  584. .expect("Failed to create test wallet");
  585. // Alice gets 100 sats
  586. fund_wallet(wallet_alice.clone(), 100, None)
  587. .await
  588. .expect("Failed to fund wallet");
  589. let proofs = wallet_alice
  590. .get_unspent_proofs()
  591. .await
  592. .expect("Could not get proofs");
  593. let keyset_id = get_keyset_id(&mint_bob).await;
  594. // Create 3 identical swap requests with the same proofs
  595. let preswap1 = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())
  596. .expect("Failed to create preswap");
  597. let swap_request1 = SwapRequest::new(proofs.clone(), preswap1.blinded_messages());
  598. let preswap2 = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())
  599. .expect("Failed to create preswap");
  600. let swap_request2 = SwapRequest::new(proofs.clone(), preswap2.blinded_messages());
  601. let preswap3 = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())
  602. .expect("Failed to create preswap");
  603. let swap_request3 = SwapRequest::new(proofs.clone(), preswap3.blinded_messages());
  604. // Spawn 3 concurrent tasks to process the swap requests
  605. let mint_clone1 = mint_bob.clone();
  606. let mint_clone2 = mint_bob.clone();
  607. let mint_clone3 = mint_bob.clone();
  608. let task1 = tokio::spawn(async move { mint_clone1.process_swap_request(swap_request1).await });
  609. let task2 = tokio::spawn(async move { mint_clone2.process_swap_request(swap_request2).await });
  610. let task3 = tokio::spawn(async move { mint_clone3.process_swap_request(swap_request3).await });
  611. // Wait for all tasks to complete
  612. let results = tokio::try_join!(task1, task2, task3).expect("Tasks failed to complete");
  613. // Count successes and failures
  614. let mut success_count = 0;
  615. let mut token_already_spent_count = 0;
  616. for result in [results.0, results.1, results.2] {
  617. match result {
  618. Ok(_) => success_count += 1,
  619. Err(err) => match err {
  620. cdk::Error::TokenAlreadySpent | cdk::Error::TokenPending => {
  621. token_already_spent_count += 1
  622. }
  623. other_err => panic!("Unexpected error: {:?}", other_err),
  624. },
  625. }
  626. }
  627. // Only one swap should succeed, the other two should fail with TokenAlreadySpent
  628. assert_eq!(1, success_count, "Expected exactly one successful swap");
  629. assert_eq!(
  630. 2, token_already_spent_count,
  631. "Expected exactly two TokenAlreadySpent errors"
  632. );
  633. // Verify that all proofs are marked as spent in the mint
  634. let states = mint_bob
  635. .localstore
  636. .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
  637. .await
  638. .expect("Failed to get proof state");
  639. for state in states {
  640. assert_eq!(
  641. State::Spent,
  642. state.expect("Known state"),
  643. "Expected proof to be marked as spent, but got {:?}",
  644. state
  645. );
  646. }
  647. }
  648. /// Tests concurrent double-spending attempts by trying to use the same proofs
  649. /// in 3 melt transactions simultaneously using tokio tasks
  650. #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
  651. async fn test_concurrent_double_spend_melt() {
  652. setup_tracing();
  653. let mint_bob = create_and_start_test_mint()
  654. .await
  655. .expect("Failed to create test mint");
  656. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  657. .await
  658. .expect("Failed to create test wallet");
  659. // Alice gets 100 sats
  660. fund_wallet(wallet_alice.clone(), 100, None)
  661. .await
  662. .expect("Failed to fund wallet");
  663. let proofs = wallet_alice
  664. .get_unspent_proofs()
  665. .await
  666. .expect("Could not get proofs");
  667. // Create a Lightning invoice for the melt
  668. let invoice = create_fake_invoice(1000, "".to_string());
  669. // Create a melt quote
  670. let melt_quote = wallet_alice
  671. .melt_quote(invoice.to_string(), None)
  672. .await
  673. .expect("Failed to create melt quote");
  674. // Get the quote ID and payment request
  675. let quote_id = melt_quote.id.clone();
  676. // Create 3 identical melt requests with the same proofs
  677. let mint_clone1 = mint_bob.clone();
  678. let mint_clone2 = mint_bob.clone();
  679. let mint_clone3 = mint_bob.clone();
  680. let melt_request = MeltBolt11Request::new(quote_id.parse().unwrap(), proofs.clone(), None);
  681. let melt_request2 = melt_request.clone();
  682. let melt_request3 = melt_request.clone();
  683. // Spawn 3 concurrent tasks to process the melt requests
  684. let task1 = tokio::spawn(async move { mint_clone1.melt_bolt11(&melt_request).await });
  685. let task2 = tokio::spawn(async move { mint_clone2.melt_bolt11(&melt_request2).await });
  686. let task3 = tokio::spawn(async move { mint_clone3.melt_bolt11(&melt_request3).await });
  687. // Wait for all tasks to complete
  688. let results = tokio::try_join!(task1, task2, task3).expect("Tasks failed to complete");
  689. // Count successes and failures
  690. let mut success_count = 0;
  691. let mut token_already_spent_count = 0;
  692. for result in [results.0, results.1, results.2] {
  693. match result {
  694. Ok(_) => success_count += 1,
  695. Err(err) => match err {
  696. cdk::Error::TokenAlreadySpent | cdk::Error::TokenPending => {
  697. token_already_spent_count += 1;
  698. println!("Got expected error: {:?}", err);
  699. }
  700. other_err => {
  701. println!("Got unexpected error: {:?}", other_err);
  702. token_already_spent_count += 1;
  703. }
  704. },
  705. }
  706. }
  707. // Only one melt should succeed, the other two should fail
  708. assert_eq!(1, success_count, "Expected exactly one successful melt");
  709. assert_eq!(
  710. 2, token_already_spent_count,
  711. "Expected exactly two TokenAlreadySpent errors"
  712. );
  713. // Verify that all proofs are marked as spent in the mint
  714. let states = mint_bob
  715. .localstore
  716. .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
  717. .await
  718. .expect("Failed to get proof state");
  719. for state in states {
  720. assert_eq!(
  721. State::Spent,
  722. state.expect("Known state"),
  723. "Expected proof to be marked as spent, but got {:?}",
  724. state
  725. );
  726. }
  727. }
  728. async fn get_keyset_id(mint: &Mint) -> Id {
  729. let keys = mint
  730. .pubkeys()
  731. .await
  732. .unwrap()
  733. .keysets
  734. .first()
  735. .unwrap()
  736. .clone()
  737. .keys;
  738. Id::from(&keys)
  739. }