regtest.rs 13 KB

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