integration_tests_pure.rs 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052
  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. )
  496. .await
  497. .unwrap();
  498. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  499. .await
  500. .expect("Failed to create test wallet");
  501. // Alice gets 100 sats
  502. fund_wallet(wallet_alice.clone(), 1000, None)
  503. .await
  504. .expect("Failed to fund wallet");
  505. let proofs = wallet_alice
  506. .get_unspent_proofs()
  507. .await
  508. .expect("Could not get proofs");
  509. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone().keys;
  510. let keyset_id = Id::v1_from_keys(&keys);
  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. )
  563. .await
  564. .unwrap();
  565. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  566. .await
  567. .expect("Failed to create test wallet");
  568. // Alice gets 100 sats
  569. fund_wallet(
  570. wallet_alice.clone(),
  571. 1010,
  572. Some(SplitTarget::Value(Amount::ONE)),
  573. )
  574. .await
  575. .expect("Failed to fund wallet");
  576. let mut proofs = wallet_alice
  577. .get_unspent_proofs()
  578. .await
  579. .expect("Could not get proofs");
  580. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
  581. let keyset_id = keys.id;
  582. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  583. let five_proofs: Vec<_> = proofs.drain(..5).collect();
  584. let preswap = PreMintSecrets::random(
  585. keyset_id,
  586. 5.into(),
  587. &SplitTarget::default(),
  588. &fee_and_amounts,
  589. )
  590. .unwrap();
  591. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  592. // Attempt to swap underpaying fee
  593. match mint_bob.process_swap_request(swap_request).await {
  594. Ok(_) => panic!("Swap was allowed unbalanced"),
  595. Err(err) => match err {
  596. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  597. _ => {
  598. println!("{:?}", err);
  599. panic!("Wrong error returned")
  600. }
  601. },
  602. }
  603. let preswap = PreMintSecrets::random(
  604. keyset_id,
  605. 4.into(),
  606. &SplitTarget::default(),
  607. &fee_and_amounts,
  608. )
  609. .unwrap();
  610. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  611. let res = mint_bob.process_swap_request(swap_request).await;
  612. assert!(res.is_ok());
  613. let thousnad_proofs: Vec<_> = proofs.drain(..1001).collect();
  614. let preswap = PreMintSecrets::random(
  615. keyset_id,
  616. 1000.into(),
  617. &SplitTarget::default(),
  618. &fee_and_amounts,
  619. )
  620. .unwrap();
  621. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  622. // Attempt to swap underpaying fee
  623. match mint_bob.process_swap_request(swap_request).await {
  624. Ok(_) => panic!("Swap was allowed unbalanced"),
  625. Err(err) => match err {
  626. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  627. _ => {
  628. println!("{:?}", err);
  629. panic!("Wrong error returned")
  630. }
  631. },
  632. }
  633. let preswap = PreMintSecrets::random(
  634. keyset_id,
  635. 999.into(),
  636. &SplitTarget::default(),
  637. &fee_and_amounts,
  638. )
  639. .unwrap();
  640. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  641. let _ = mint_bob.process_swap_request(swap_request).await.unwrap();
  642. }
  643. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  644. async fn test_mint_change_with_fee_melt() {
  645. setup_tracing();
  646. let mint_bob = create_and_start_test_mint()
  647. .await
  648. .expect("Failed to create test mint");
  649. mint_bob
  650. .rotate_keyset(
  651. CurrencyUnit::Sat,
  652. cdk_integration_tests::standard_keyset_amounts(32),
  653. 1,
  654. )
  655. .await
  656. .unwrap();
  657. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  658. .await
  659. .expect("Failed to create test wallet");
  660. // Alice gets 100 sats
  661. fund_wallet(
  662. wallet_alice.clone(),
  663. 100,
  664. Some(SplitTarget::Value(Amount::ONE)),
  665. )
  666. .await
  667. .expect("Failed to fund wallet");
  668. let keyset_id = mint_bob.pubkeys().keysets.first().unwrap().id;
  669. // Check amounts after minting
  670. let total_issued = mint_bob.total_issued().await.unwrap();
  671. let total_redeemed = mint_bob.total_redeemed().await.unwrap();
  672. let initial_issued = total_issued.get(&keyset_id).copied().unwrap_or_default();
  673. let initial_redeemed = total_redeemed
  674. .get(&keyset_id)
  675. .copied()
  676. .unwrap_or(Amount::ZERO);
  677. assert_eq!(
  678. initial_issued,
  679. Amount::from(100),
  680. "Should have issued 100 sats, got {:?}",
  681. total_issued
  682. );
  683. assert_eq!(
  684. initial_redeemed,
  685. Amount::ZERO,
  686. "Should have redeemed 0 sats initially, "
  687. );
  688. let proofs = wallet_alice
  689. .get_unspent_proofs()
  690. .await
  691. .expect("Could not get proofs");
  692. let fake_invoice = create_fake_invoice(1000, "".to_string());
  693. let melt_quote = wallet_alice
  694. .melt_quote(PaymentMethod::BOLT11, fake_invoice.to_string(), None, None)
  695. .await
  696. .unwrap();
  697. let prepared = wallet_alice
  698. .prepare_melt_proofs(&melt_quote.id, proofs, std::collections::HashMap::new())
  699. .await
  700. .unwrap();
  701. let w = prepared.confirm().await.unwrap();
  702. assert_eq!(w.change().unwrap().total_amount().unwrap(), 97.into());
  703. // Check amounts after melting
  704. // Melting redeems 100 sats and issues 97 sats as change
  705. let total_issued = mint_bob.total_issued().await.unwrap();
  706. let total_redeemed = mint_bob.total_redeemed().await.unwrap();
  707. let after_issued = total_issued
  708. .get(&keyset_id)
  709. .copied()
  710. .unwrap_or(Amount::ZERO);
  711. let after_redeemed = total_redeemed
  712. .get(&keyset_id)
  713. .copied()
  714. .unwrap_or(Amount::ZERO);
  715. assert_eq!(
  716. after_issued,
  717. Amount::from(197),
  718. "Should have issued 197 sats total (100 initial + 97 change)"
  719. );
  720. assert_eq!(
  721. after_redeemed,
  722. Amount::from(100),
  723. "Should have redeemed 100 sats from the melt"
  724. );
  725. }
  726. /// Tests concurrent double-spending attempts by trying to use the same proofs
  727. /// in 3 swap transactions simultaneously using tokio tasks
  728. #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
  729. async fn test_concurrent_double_spend_swap() {
  730. setup_tracing();
  731. let mint_bob = create_and_start_test_mint()
  732. .await
  733. .expect("Failed to create test mint");
  734. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  735. .await
  736. .expect("Failed to create test wallet");
  737. // Alice gets 100 sats
  738. fund_wallet(wallet_alice.clone(), 100, None)
  739. .await
  740. .expect("Failed to fund wallet");
  741. let proofs = wallet_alice
  742. .get_unspent_proofs()
  743. .await
  744. .expect("Could not get proofs");
  745. let keyset_id = get_keyset_id(&mint_bob).await;
  746. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  747. // Create 3 identical swap requests with the same proofs
  748. let preswap1 = PreMintSecrets::random(
  749. keyset_id,
  750. 100.into(),
  751. &SplitTarget::default(),
  752. &fee_and_amounts,
  753. )
  754. .expect("Failed to create preswap");
  755. let swap_request1 = SwapRequest::new(proofs.clone(), preswap1.blinded_messages());
  756. let preswap2 = PreMintSecrets::random(
  757. keyset_id,
  758. 100.into(),
  759. &SplitTarget::default(),
  760. &fee_and_amounts,
  761. )
  762. .expect("Failed to create preswap");
  763. let swap_request2 = SwapRequest::new(proofs.clone(), preswap2.blinded_messages());
  764. let preswap3 = PreMintSecrets::random(
  765. keyset_id,
  766. 100.into(),
  767. &SplitTarget::default(),
  768. &fee_and_amounts,
  769. )
  770. .expect("Failed to create preswap");
  771. let swap_request3 = SwapRequest::new(proofs.clone(), preswap3.blinded_messages());
  772. // Spawn 3 concurrent tasks to process the swap requests
  773. let mint_clone1 = mint_bob.clone();
  774. let mint_clone2 = mint_bob.clone();
  775. let mint_clone3 = mint_bob.clone();
  776. let task1 = tokio::spawn(async move { mint_clone1.process_swap_request(swap_request1).await });
  777. let task2 = tokio::spawn(async move { mint_clone2.process_swap_request(swap_request2).await });
  778. let task3 = tokio::spawn(async move { mint_clone3.process_swap_request(swap_request3).await });
  779. // Wait for all tasks to complete
  780. let results = tokio::try_join!(task1, task2, task3).expect("Tasks failed to complete");
  781. // Count successes and failures
  782. let mut success_count = 0;
  783. let mut token_already_spent_count = 0;
  784. for result in [results.0, results.1, results.2] {
  785. match result {
  786. Ok(_) => success_count += 1,
  787. Err(err) => match err {
  788. cdk::Error::TokenAlreadySpent | cdk::Error::TokenPending => {
  789. token_already_spent_count += 1
  790. }
  791. other_err => panic!("Unexpected error: {:?}", other_err),
  792. },
  793. }
  794. }
  795. // Only one swap should succeed, the other two should fail with TokenAlreadySpent
  796. assert_eq!(1, success_count, "Expected exactly one successful swap");
  797. assert_eq!(
  798. 2, token_already_spent_count,
  799. "Expected exactly two TokenAlreadySpent errors"
  800. );
  801. // Verify that all proofs are marked as spent in the mint
  802. let states = mint_bob
  803. .localstore()
  804. .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
  805. .await
  806. .expect("Failed to get proof state");
  807. for state in states {
  808. assert_eq!(
  809. State::Spent,
  810. state.expect("Known state"),
  811. "Expected proof to be marked as spent, but got {:?}",
  812. state
  813. );
  814. }
  815. }
  816. /// Tests concurrent double-spending attempts by trying to use the same proofs
  817. /// in 3 melt transactions simultaneously using tokio tasks
  818. #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
  819. async fn test_concurrent_double_spend_melt() {
  820. setup_tracing();
  821. let mint_bob = create_and_start_test_mint()
  822. .await
  823. .expect("Failed to create test mint");
  824. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  825. .await
  826. .expect("Failed to create test wallet");
  827. // Alice gets 100 sats
  828. fund_wallet(wallet_alice.clone(), 100, None)
  829. .await
  830. .expect("Failed to fund wallet");
  831. let proofs = wallet_alice
  832. .get_unspent_proofs()
  833. .await
  834. .expect("Could not get proofs");
  835. // Create a Lightning invoice for the melt
  836. let invoice = create_fake_invoice(1000, "".to_string());
  837. // Create a melt quote
  838. let melt_quote = wallet_alice
  839. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  840. .await
  841. .expect("Failed to create melt quote");
  842. // Get the quote ID and payment request
  843. let quote_id = melt_quote.id.clone();
  844. // Create 3 identical melt requests with the same proofs
  845. let mint_clone1 = mint_bob.clone();
  846. let mint_clone2 = mint_bob.clone();
  847. let mint_clone3 = mint_bob.clone();
  848. let melt_request = MeltRequest::new(quote_id.parse().unwrap(), proofs.clone(), None);
  849. let melt_request2 = melt_request.clone();
  850. let melt_request3 = melt_request.clone();
  851. // Spawn 3 concurrent tasks to process the melt requests
  852. let task1 = tokio::spawn(async move { mint_clone1.melt(&melt_request).await });
  853. let task2 = tokio::spawn(async move { mint_clone2.melt(&melt_request2).await });
  854. let task3 = tokio::spawn(async move { mint_clone3.melt(&melt_request3).await });
  855. // Wait for all tasks to complete
  856. let results = tokio::try_join!(task1, task2, task3).expect("Tasks failed to complete");
  857. // Count successes and failures
  858. let mut success_count = 0;
  859. let mut token_already_spent_count = 0;
  860. for result in [results.0, results.1, results.2] {
  861. match result {
  862. Ok(_) => success_count += 1,
  863. Err(err) => match err {
  864. cdk::Error::TokenAlreadySpent | cdk::Error::TokenPending => {
  865. token_already_spent_count += 1;
  866. println!("Got expected error: {:?}", err);
  867. }
  868. other_err => {
  869. println!("Got unexpected error: {:?}", other_err);
  870. token_already_spent_count += 1;
  871. }
  872. },
  873. }
  874. }
  875. // Only one melt should succeed, the other two should fail
  876. assert_eq!(1, success_count, "Expected exactly one successful melt");
  877. assert_eq!(
  878. 2, token_already_spent_count,
  879. "Expected exactly two TokenAlreadySpent errors"
  880. );
  881. // Verify that all proofs are marked as spent in the mint
  882. let states = mint_bob
  883. .localstore()
  884. .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
  885. .await
  886. .expect("Failed to get proof state");
  887. for state in states {
  888. assert_eq!(
  889. State::Spent,
  890. state.expect("Known state"),
  891. "Expected proof to be marked as spent, but got {:?}",
  892. state
  893. );
  894. }
  895. }
  896. async fn get_keyset_id(mint: &Mint) -> Id {
  897. let keys = mint.pubkeys().keysets.first().unwrap().clone();
  898. keys.verify_id()
  899. .expect("Keyset ID generation is successful");
  900. keys.id
  901. }