regtest.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  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 http_client = HttpClient::new(get_mint_url_from_env().parse().unwrap(), None);
  270. let premint_secrets =
  271. PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::default()).unwrap();
  272. let mut request = MintRequest {
  273. quote: quote.id,
  274. outputs: premint_secrets.blinded_messages(),
  275. signature: None,
  276. };
  277. let secret_key = quote.secret_key;
  278. request
  279. .sign(secret_key.expect("Secret key on quote"))
  280. .unwrap();
  281. let response = http_client.post_mint(request.clone()).await.unwrap();
  282. let response1 = http_client.post_mint(request).await.unwrap();
  283. assert!(response == response1);
  284. }
  285. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  286. async fn test_regtest_melt_amountless() {
  287. let ln_client = get_test_client().await;
  288. let wallet = Wallet::new(
  289. &get_mint_url_from_env(),
  290. CurrencyUnit::Sat,
  291. Arc::new(memory::empty().await.unwrap()),
  292. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  293. None,
  294. )
  295. .expect("failed to create new wallet");
  296. let mint_amount = Amount::from(100);
  297. let mint_quote = wallet.mint_quote(mint_amount, None).await.unwrap();
  298. assert_eq!(mint_quote.amount, Some(mint_amount));
  299. ln_client
  300. .pay_invoice(mint_quote.request)
  301. .await
  302. .expect("failed to pay invoice");
  303. let proofs = wallet
  304. .mint(&mint_quote.id, SplitTarget::default(), None)
  305. .await
  306. .unwrap();
  307. let amount = proofs.total_amount().unwrap();
  308. assert!(mint_amount == amount);
  309. let invoice = ln_client.create_invoice(None).await.unwrap();
  310. let options = MeltOptions::new_amountless(5_000);
  311. let melt_quote = wallet
  312. .melt_quote(invoice.clone(), Some(options))
  313. .await
  314. .unwrap();
  315. let melt = wallet.melt(&melt_quote.id).await.unwrap();
  316. assert!(melt.amount == 5.into());
  317. }
  318. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  319. async fn test_attempt_to_mint_unpaid() {
  320. let wallet = Wallet::new(
  321. &get_mint_url_from_env(),
  322. CurrencyUnit::Sat,
  323. Arc::new(memory::empty().await.unwrap()),
  324. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  325. None,
  326. )
  327. .expect("failed to create new wallet");
  328. let mint_amount = Amount::from(100);
  329. let mint_quote = wallet.mint_quote(mint_amount, None).await.unwrap();
  330. assert_eq!(mint_quote.amount, Some(mint_amount));
  331. let proofs = wallet
  332. .mint(&mint_quote.id, SplitTarget::default(), None)
  333. .await;
  334. match proofs {
  335. Err(err) => {
  336. if !matches!(err, cdk::Error::UnpaidQuote) {
  337. panic!("Wrong error quote should be unpaid: {}", err);
  338. }
  339. }
  340. Ok(_) => {
  341. panic!("Minting should not be allowed");
  342. }
  343. }
  344. let mint_quote = wallet.mint_quote(mint_amount, None).await.unwrap();
  345. let state = wallet.mint_quote_state(&mint_quote.id).await.unwrap();
  346. assert!(state.state == MintQuoteState::Unpaid);
  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. }