regtest.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. //! Regtest Integration Tests
  2. //!
  3. //! This file contains tests that run against actual Lightning Network nodes in regtest mode.
  4. //! These tests require a local development environment with LND nodes configured for regtest.
  5. //!
  6. //! Test Environment Setup:
  7. //! - Uses actual LND nodes connected to a regtest Bitcoin network
  8. //! - Tests real Lightning payment flows including invoice creation and payment
  9. //! - Verifies mint behavior with actual Lightning Network interactions
  10. //!
  11. //! Running Tests:
  12. //! - Requires CDK_TEST_REGTEST=1 environment variable to be set
  13. //! - Requires properly configured LND nodes with TLS certificates and macaroons
  14. //! - Uses real Bitcoin transactions in regtest mode
  15. use std::sync::Arc;
  16. use std::time::Duration;
  17. use bip39::Mnemonic;
  18. use cashu::ProofsMethods;
  19. use cdk::amount::{Amount, SplitTarget};
  20. use cdk::nuts::nut00::KnownMethod;
  21. use cdk::nuts::{
  22. CurrencyUnit, MeltOptions, MeltQuoteState, MintQuoteState, MintRequest, Mpp,
  23. NotificationPayload, PaymentMethod, PreMintSecrets,
  24. };
  25. use cdk::wallet::{HttpClient, MintConnector, Wallet, WalletSubscription};
  26. use cdk_integration_tests::{
  27. attempt_manual_mint, get_mint_url_from_env, get_second_mint_url_from_env, get_test_client,
  28. };
  29. use cdk_sqlite::wallet::{self, memory};
  30. use futures::join;
  31. use tokio::time::timeout;
  32. const LDK_URL: &str = "http://127.0.0.1:8089";
  33. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  34. async fn test_internal_payment() {
  35. let ln_client = get_test_client().await;
  36. let wallet = Wallet::new(
  37. &get_mint_url_from_env(),
  38. CurrencyUnit::Sat,
  39. Arc::new(memory::empty().await.unwrap()),
  40. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  41. None,
  42. )
  43. .expect("failed to create new wallet");
  44. let mint_quote = wallet.mint_bolt11_quote(100.into(), None).await.unwrap();
  45. ln_client
  46. .pay_invoice(mint_quote.request.clone())
  47. .await
  48. .expect("failed to pay invoice");
  49. let _proofs = wallet
  50. .wait_and_mint_quote(
  51. mint_quote.clone(),
  52. SplitTarget::default(),
  53. None,
  54. tokio::time::Duration::from_secs(60),
  55. )
  56. .await
  57. .expect("payment");
  58. assert!(wallet.total_balance().await.unwrap() == 100.into());
  59. let wallet_2 = Wallet::new(
  60. &get_mint_url_from_env(),
  61. CurrencyUnit::Sat,
  62. Arc::new(memory::empty().await.unwrap()),
  63. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  64. None,
  65. )
  66. .expect("failed to create new wallet");
  67. let mint_quote = wallet_2.mint_bolt11_quote(10.into(), None).await.unwrap();
  68. let melt = wallet
  69. .melt_quote(
  70. PaymentMethod::BOLT11,
  71. mint_quote.request.clone(),
  72. None,
  73. None,
  74. )
  75. .await
  76. .unwrap();
  77. assert_eq!(melt.amount, 10.into());
  78. let prepared = wallet
  79. .prepare_melt(&melt.id, std::collections::HashMap::new())
  80. .await
  81. .unwrap();
  82. let _melted = prepared.confirm().await.unwrap();
  83. let _proofs = wallet_2
  84. .wait_and_mint_quote(
  85. mint_quote.clone(),
  86. SplitTarget::default(),
  87. None,
  88. tokio::time::Duration::from_secs(60),
  89. )
  90. .await
  91. .expect("payment");
  92. // let check_paid = match get_mint_port("0") {
  93. // 8085 => {
  94. // let cln_one_dir = get_cln_dir(&get_temp_dir(), "one");
  95. // let cln_client = ClnClient::new(cln_one_dir.clone(), None).await.unwrap();
  96. // let payment_hash = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
  97. // cln_client
  98. // .check_incoming_payment_status(&payment_hash.payment_hash().to_string())
  99. // .await
  100. // .expect("Could not check invoice")
  101. // }
  102. // 8087 => {
  103. // let lnd_two_dir = get_lnd_dir(&get_temp_dir(), "two");
  104. // let lnd_client = LndClient::new(
  105. // format!("https://{}", LND_TWO_RPC_ADDR),
  106. // get_lnd_cert_file_path(&lnd_two_dir),
  107. // get_lnd_macaroon_path(&lnd_two_dir),
  108. // )
  109. // .await
  110. // .unwrap();
  111. // let payment_hash = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
  112. // lnd_client
  113. // .check_incoming_payment_status(&payment_hash.payment_hash().to_string())
  114. // .await
  115. // .expect("Could not check invoice")
  116. // }
  117. // _ => panic!("Unknown mint port"),
  118. // };
  119. // match check_paid {
  120. // InvoiceStatus::Unpaid => (),
  121. // _ => {
  122. // panic!("Invoice has incorrect status: {:?}", check_paid);
  123. // }
  124. // }
  125. let wallet_2_balance = wallet_2.total_balance().await.unwrap();
  126. assert!(wallet_2_balance == 10.into());
  127. let wallet_1_balance = wallet.total_balance().await.unwrap();
  128. assert!(wallet_1_balance == 90.into());
  129. }
  130. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  131. async fn test_websocket_connection() {
  132. let wallet = Wallet::new(
  133. &get_mint_url_from_env(),
  134. CurrencyUnit::Sat,
  135. Arc::new(wallet::memory::empty().await.unwrap()),
  136. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  137. None,
  138. )
  139. .expect("failed to create new wallet");
  140. // Create a small mint quote to test notifications
  141. let mint_quote = wallet.mint_bolt11_quote(10.into(), None).await.unwrap();
  142. // Subscribe to notifications for this quote
  143. let mut subscription = wallet
  144. .subscribe(WalletSubscription::Bolt11MintQuoteState(vec![mint_quote
  145. .id
  146. .clone()]))
  147. .await
  148. .expect("failed to subscribe");
  149. // First check we get the unpaid state
  150. let msg = timeout(Duration::from_secs(10), subscription.recv())
  151. .await
  152. .expect("timeout waiting for unpaid notification")
  153. .expect("No paid notification received");
  154. match msg.into_inner() {
  155. NotificationPayload::MintQuoteBolt11Response(response) => {
  156. assert_eq!(response.quote.to_string(), mint_quote.id);
  157. assert_eq!(response.state, MintQuoteState::Unpaid);
  158. }
  159. _ => panic!("Unexpected notification type"),
  160. }
  161. let ln_client = get_test_client().await;
  162. ln_client
  163. .pay_invoice(mint_quote.request)
  164. .await
  165. .expect("failed to pay invoice");
  166. // Wait for paid notification with 10 second timeout
  167. let msg = timeout(Duration::from_secs(10), subscription.recv())
  168. .await
  169. .expect("timeout waiting for paid notification")
  170. .expect("No paid notification received");
  171. match msg.into_inner() {
  172. NotificationPayload::MintQuoteBolt11Response(response) => {
  173. assert_eq!(response.quote.to_string(), mint_quote.id);
  174. assert_eq!(response.state, MintQuoteState::Paid);
  175. }
  176. _ => panic!("Unexpected notification type"),
  177. }
  178. }
  179. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  180. async fn test_multimint_melt() {
  181. if get_mint_url_from_env() == LDK_URL {
  182. return;
  183. }
  184. let ln_client = get_test_client().await;
  185. let db = Arc::new(memory::empty().await.unwrap());
  186. let wallet1 = Wallet::new(
  187. &get_mint_url_from_env(),
  188. CurrencyUnit::Sat,
  189. db,
  190. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  191. None,
  192. )
  193. .expect("failed to create new wallet");
  194. let db = Arc::new(memory::empty().await.unwrap());
  195. let wallet2 = Wallet::new(
  196. &get_second_mint_url_from_env(),
  197. CurrencyUnit::Sat,
  198. db,
  199. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  200. None,
  201. )
  202. .expect("failed to create new wallet");
  203. let mint_amount = Amount::from(100);
  204. // Fund the wallets
  205. let quote = wallet1.mint_bolt11_quote(mint_amount, None).await.unwrap();
  206. ln_client
  207. .pay_invoice(quote.request.clone())
  208. .await
  209. .expect("failed to pay invoice");
  210. let _proofs = wallet1
  211. .wait_and_mint_quote(
  212. quote.clone(),
  213. SplitTarget::default(),
  214. None,
  215. tokio::time::Duration::from_secs(60),
  216. )
  217. .await
  218. .expect("payment");
  219. let quote = wallet2.mint_bolt11_quote(mint_amount, None).await.unwrap();
  220. ln_client
  221. .pay_invoice(quote.request.clone())
  222. .await
  223. .expect("failed to pay invoice");
  224. let _proofs = wallet2
  225. .wait_and_mint_quote(
  226. quote.clone(),
  227. SplitTarget::default(),
  228. None,
  229. tokio::time::Duration::from_secs(60),
  230. )
  231. .await
  232. .expect("payment");
  233. // Get an invoice
  234. let invoice = ln_client.create_invoice(Some(50)).await.unwrap();
  235. // Get multi-part melt quotes
  236. let melt_options = MeltOptions::Mpp {
  237. mpp: Mpp {
  238. amount: Amount::from(25000),
  239. },
  240. };
  241. let quote_1 = wallet1
  242. .melt_quote(
  243. PaymentMethod::BOLT11,
  244. invoice.clone(),
  245. Some(melt_options),
  246. None,
  247. )
  248. .await
  249. .expect("Could not get melt quote");
  250. let quote_2 = wallet2
  251. .melt_quote(
  252. PaymentMethod::BOLT11,
  253. invoice.clone(),
  254. Some(melt_options),
  255. None,
  256. )
  257. .await
  258. .expect("Could not get melt quote");
  259. // Multimint pay invoice - prepare both melts
  260. let prepared1 = wallet1
  261. .prepare_melt(&quote_1.id, std::collections::HashMap::new())
  262. .await
  263. .expect("Could not prepare melt 1");
  264. let prepared2 = wallet2
  265. .prepare_melt(&quote_2.id, std::collections::HashMap::new())
  266. .await
  267. .expect("Could not prepare melt 2");
  268. // Confirm both in parallel
  269. let result = join!(prepared1.confirm(), prepared2.confirm());
  270. // Unpack results
  271. let result1 = result.0.unwrap();
  272. let result2 = result.1.unwrap();
  273. // Check
  274. assert!(result1.state() == result2.state());
  275. assert!(result1.state() == MeltQuoteState::Paid);
  276. }
  277. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  278. async fn test_cached_mint() {
  279. let ln_client = get_test_client().await;
  280. let wallet = Wallet::new(
  281. &get_mint_url_from_env(),
  282. CurrencyUnit::Sat,
  283. Arc::new(memory::empty().await.unwrap()),
  284. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  285. None,
  286. )
  287. .expect("failed to create new wallet");
  288. let mint_amount = Amount::from(100);
  289. let quote = wallet.mint_bolt11_quote(mint_amount, None).await.unwrap();
  290. ln_client
  291. .pay_invoice(quote.request.clone())
  292. .await
  293. .expect("failed to pay invoice");
  294. let _proofs = wallet
  295. .wait_for_payment(&quote, tokio::time::Duration::from_secs(15))
  296. .await
  297. .expect("payment");
  298. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  299. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  300. let http_client = HttpClient::new(get_mint_url_from_env().parse().unwrap(), None);
  301. // Fetch mint info to populate cache support (NUT-19)
  302. http_client.get_mint_info().await.unwrap();
  303. let premint_secrets = PreMintSecrets::random(
  304. active_keyset_id,
  305. 100.into(),
  306. &SplitTarget::default().to_owned(),
  307. &fee_and_amounts,
  308. )
  309. .unwrap();
  310. let mut request = MintRequest {
  311. quote: quote.id,
  312. outputs: premint_secrets.blinded_messages(),
  313. signature: None,
  314. };
  315. let secret_key = quote.secret_key;
  316. request
  317. .sign(secret_key.expect("Secret key on quote"))
  318. .unwrap();
  319. let response = http_client
  320. .post_mint(&PaymentMethod::Known(KnownMethod::Bolt11), request.clone())
  321. .await
  322. .unwrap();
  323. let response1 = http_client
  324. .post_mint(&PaymentMethod::Known(KnownMethod::Bolt11), request)
  325. .await
  326. .unwrap();
  327. assert!(response == response1);
  328. }
  329. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  330. async fn test_regtest_melt_amountless() {
  331. let ln_client = get_test_client().await;
  332. let wallet = Wallet::new(
  333. &get_mint_url_from_env(),
  334. CurrencyUnit::Sat,
  335. Arc::new(memory::empty().await.unwrap()),
  336. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  337. None,
  338. )
  339. .expect("failed to create new wallet");
  340. let mint_amount = Amount::from(100);
  341. let mint_quote = wallet.mint_bolt11_quote(mint_amount, None).await.unwrap();
  342. assert_eq!(mint_quote.amount, Some(mint_amount));
  343. ln_client
  344. .pay_invoice(mint_quote.request)
  345. .await
  346. .expect("failed to pay invoice");
  347. let proofs = wallet
  348. .mint(&mint_quote.id, SplitTarget::default(), None)
  349. .await
  350. .unwrap();
  351. let amount = proofs.total_amount().unwrap();
  352. assert!(mint_amount == amount);
  353. let invoice = ln_client.create_invoice(None).await.unwrap();
  354. let options = MeltOptions::new_amountless(5_000);
  355. let melt_quote = wallet
  356. .melt_quote(PaymentMethod::BOLT11, invoice.clone(), Some(options), None)
  357. .await
  358. .unwrap();
  359. let prepared = wallet
  360. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  361. .await
  362. .unwrap();
  363. let melt = prepared.confirm().await.unwrap();
  364. assert!(melt.amount() == 5.into());
  365. }
  366. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  367. async fn test_attempt_to_mint_unpaid() {
  368. let wallet = Wallet::new(
  369. &get_mint_url_from_env(),
  370. CurrencyUnit::Sat,
  371. Arc::new(memory::empty().await.unwrap()),
  372. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  373. None,
  374. )
  375. .expect("failed to create new wallet");
  376. let mint_amount = Amount::from(100);
  377. let mint_quote = wallet.mint_bolt11_quote(mint_amount, None).await.unwrap();
  378. assert_eq!(mint_quote.amount, Some(mint_amount));
  379. let response = attempt_manual_mint(
  380. &wallet,
  381. &get_mint_url_from_env(),
  382. &mint_quote,
  383. mint_amount,
  384. PaymentMethod::Known(KnownMethod::Bolt11),
  385. )
  386. .await;
  387. match response {
  388. Err(err) => {
  389. if !matches!(err, cdk::Error::UnpaidQuote) {
  390. panic!("Wrong error quote should be unpaid: {}", err);
  391. }
  392. }
  393. Ok(_) => {
  394. panic!("Minting should not be allowed");
  395. }
  396. }
  397. let mint_quote = wallet.mint_bolt11_quote(mint_amount, None).await.unwrap();
  398. let state = wallet
  399. .refresh_mint_quote_status(&mint_quote.id)
  400. .await
  401. .unwrap();
  402. assert!(state.state == MintQuoteState::Unpaid);
  403. let response = attempt_manual_mint(
  404. &wallet,
  405. &get_mint_url_from_env(),
  406. &mint_quote,
  407. mint_amount,
  408. PaymentMethod::Known(KnownMethod::Bolt11),
  409. )
  410. .await;
  411. match response {
  412. Err(err) => {
  413. if !matches!(err, cdk::Error::UnpaidQuote) {
  414. panic!("Wrong error quote should be unpaid: {}", err);
  415. }
  416. }
  417. Ok(_) => {
  418. panic!("Minting should not be allowed");
  419. }
  420. }
  421. }