integration_tests_pure.rs 31 KB

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