regtest.rs 14 KB

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