integration_tests_pure.rs 33 KB

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