regtest.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  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. // First check we get the unpaid state
  137. let msg = timeout(Duration::from_secs(10), subscription.recv())
  138. .await
  139. .expect("timeout waiting for unpaid notification")
  140. .expect("No paid notification received");
  141. match msg {
  142. NotificationPayload::MintQuoteBolt11Response(response) => {
  143. assert_eq!(response.quote.to_string(), mint_quote.id);
  144. assert_eq!(response.state, MintQuoteState::Unpaid);
  145. }
  146. _ => panic!("Unexpected notification type"),
  147. }
  148. let ln_client = get_test_client().await;
  149. ln_client
  150. .pay_invoice(mint_quote.request)
  151. .await
  152. .expect("failed to pay invoice");
  153. // Wait for paid notification with 10 second timeout
  154. let msg = timeout(Duration::from_secs(10), subscription.recv())
  155. .await
  156. .expect("timeout waiting for paid notification")
  157. .expect("No paid notification received");
  158. match msg {
  159. NotificationPayload::MintQuoteBolt11Response(response) => {
  160. assert_eq!(response.quote.to_string(), mint_quote.id);
  161. assert_eq!(response.state, MintQuoteState::Paid);
  162. }
  163. _ => panic!("Unexpected notification type"),
  164. }
  165. }
  166. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  167. async fn test_multimint_melt() {
  168. if get_mint_url_from_env() == LDK_URL {
  169. return;
  170. }
  171. let ln_client = get_test_client().await;
  172. let db = Arc::new(memory::empty().await.unwrap());
  173. let wallet1 = Wallet::new(
  174. &get_mint_url_from_env(),
  175. CurrencyUnit::Sat,
  176. db,
  177. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  178. None,
  179. )
  180. .expect("failed to create new wallet");
  181. let db = Arc::new(memory::empty().await.unwrap());
  182. let wallet2 = Wallet::new(
  183. &get_second_mint_url_from_env(),
  184. CurrencyUnit::Sat,
  185. db,
  186. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  187. None,
  188. )
  189. .expect("failed to create new wallet");
  190. let mint_amount = Amount::from(100);
  191. // Fund the wallets
  192. let quote = wallet1.mint_quote(mint_amount, None).await.unwrap();
  193. ln_client
  194. .pay_invoice(quote.request.clone())
  195. .await
  196. .expect("failed to pay invoice");
  197. let _proofs = wallet1
  198. .wait_and_mint_quote(
  199. quote.clone(),
  200. SplitTarget::default(),
  201. None,
  202. tokio::time::Duration::from_secs(60),
  203. )
  204. .await
  205. .expect("payment");
  206. let quote = wallet2.mint_quote(mint_amount, None).await.unwrap();
  207. ln_client
  208. .pay_invoice(quote.request.clone())
  209. .await
  210. .expect("failed to pay invoice");
  211. let _proofs = wallet2
  212. .wait_and_mint_quote(
  213. quote.clone(),
  214. SplitTarget::default(),
  215. None,
  216. tokio::time::Duration::from_secs(60),
  217. )
  218. .await
  219. .expect("payment");
  220. // Get an invoice
  221. let invoice = ln_client.create_invoice(Some(50)).await.unwrap();
  222. // Get multi-part melt quotes
  223. let melt_options = MeltOptions::Mpp {
  224. mpp: Mpp {
  225. amount: Amount::from(25000),
  226. },
  227. };
  228. let quote_1 = wallet1
  229. .melt_quote(invoice.clone(), Some(melt_options))
  230. .await
  231. .expect("Could not get melt quote");
  232. let quote_2 = wallet2
  233. .melt_quote(invoice.clone(), Some(melt_options))
  234. .await
  235. .expect("Could not get melt quote");
  236. // Multimint pay invoice
  237. let result1 = wallet1.melt(&quote_1.id);
  238. let result2 = wallet2.melt(&quote_2.id);
  239. let result = join!(result1, result2);
  240. // Unpack results
  241. let result1 = result.0.unwrap();
  242. let result2 = result.1.unwrap();
  243. // Check
  244. assert!(result1.state == result2.state);
  245. assert!(result1.state == MeltQuoteState::Paid);
  246. }
  247. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  248. async fn test_cached_mint() {
  249. let ln_client = get_test_client().await;
  250. let wallet = Wallet::new(
  251. &get_mint_url_from_env(),
  252. CurrencyUnit::Sat,
  253. Arc::new(memory::empty().await.unwrap()),
  254. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  255. None,
  256. )
  257. .expect("failed to create new wallet");
  258. let mint_amount = Amount::from(100);
  259. let quote = wallet.mint_quote(mint_amount, None).await.unwrap();
  260. ln_client
  261. .pay_invoice(quote.request.clone())
  262. .await
  263. .expect("failed to pay invoice");
  264. let _proofs = wallet
  265. .wait_for_payment(&quote, tokio::time::Duration::from_secs(15))
  266. .await
  267. .expect("payment");
  268. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  269. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  270. let http_client = HttpClient::new(get_mint_url_from_env().parse().unwrap(), None);
  271. let premint_secrets = PreMintSecrets::random(
  272. active_keyset_id,
  273. 100.into(),
  274. &SplitTarget::default().to_owned(),
  275. &fee_and_amounts,
  276. )
  277. .unwrap();
  278. let mut request = MintRequest {
  279. quote: quote.id,
  280. outputs: premint_secrets.blinded_messages(),
  281. signature: None,
  282. };
  283. let secret_key = quote.secret_key;
  284. request
  285. .sign(secret_key.expect("Secret key on quote"))
  286. .unwrap();
  287. let response = http_client.post_mint(request.clone()).await.unwrap();
  288. let response1 = http_client.post_mint(request).await.unwrap();
  289. assert!(response == response1);
  290. }
  291. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  292. async fn test_regtest_melt_amountless() {
  293. let ln_client = get_test_client().await;
  294. let wallet = Wallet::new(
  295. &get_mint_url_from_env(),
  296. CurrencyUnit::Sat,
  297. Arc::new(memory::empty().await.unwrap()),
  298. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  299. None,
  300. )
  301. .expect("failed to create new wallet");
  302. let mint_amount = Amount::from(100);
  303. let mint_quote = wallet.mint_quote(mint_amount, None).await.unwrap();
  304. assert_eq!(mint_quote.amount, Some(mint_amount));
  305. ln_client
  306. .pay_invoice(mint_quote.request)
  307. .await
  308. .expect("failed to pay invoice");
  309. let proofs = wallet
  310. .mint(&mint_quote.id, SplitTarget::default(), None)
  311. .await
  312. .unwrap();
  313. let amount = proofs.total_amount().unwrap();
  314. assert!(mint_amount == amount);
  315. let invoice = ln_client.create_invoice(None).await.unwrap();
  316. let options = MeltOptions::new_amountless(5_000);
  317. let melt_quote = wallet
  318. .melt_quote(invoice.clone(), Some(options))
  319. .await
  320. .unwrap();
  321. let melt = wallet.melt(&melt_quote.id).await.unwrap();
  322. assert!(melt.amount == 5.into());
  323. }
  324. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  325. async fn test_attempt_to_mint_unpaid() {
  326. let wallet = Wallet::new(
  327. &get_mint_url_from_env(),
  328. CurrencyUnit::Sat,
  329. Arc::new(memory::empty().await.unwrap()),
  330. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  331. None,
  332. )
  333. .expect("failed to create new wallet");
  334. let mint_amount = Amount::from(100);
  335. let mint_quote = wallet.mint_quote(mint_amount, None).await.unwrap();
  336. assert_eq!(mint_quote.amount, Some(mint_amount));
  337. let proofs = wallet
  338. .mint(&mint_quote.id, SplitTarget::default(), None)
  339. .await;
  340. match proofs {
  341. Err(err) => {
  342. if !matches!(err, cdk::Error::UnpaidQuote) {
  343. panic!("Wrong error quote should be unpaid: {}", err);
  344. }
  345. }
  346. Ok(_) => {
  347. panic!("Minting should not be allowed");
  348. }
  349. }
  350. let mint_quote = wallet.mint_quote(mint_amount, None).await.unwrap();
  351. let state = wallet.mint_quote_state(&mint_quote.id).await.unwrap();
  352. assert!(state.state == MintQuoteState::Unpaid);
  353. let proofs = wallet
  354. .mint(&mint_quote.id, SplitTarget::default(), None)
  355. .await;
  356. match proofs {
  357. Err(err) => {
  358. if !matches!(err, cdk::Error::UnpaidQuote) {
  359. panic!("Wrong error quote should be unpaid: {}", err);
  360. }
  361. }
  362. Ok(_) => {
  363. panic!("Minting should not be allowed");
  364. }
  365. }
  366. }