integration_tests_pure.rs 31 KB

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