integration_tests_pure.rs 31 KB

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