regtest.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  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::env;
  16. use std::path::PathBuf;
  17. use std::sync::Arc;
  18. use std::time::Duration;
  19. use bip39::Mnemonic;
  20. use cashu::ProofsMethods;
  21. use cdk::amount::{Amount, SplitTarget};
  22. use cdk::nuts::{
  23. CurrencyUnit, MeltOptions, MeltQuoteState, MintQuoteState, MintRequest, Mpp,
  24. NotificationPayload, PreMintSecrets,
  25. };
  26. use cdk::wallet::{HttpClient, MintConnector, Wallet, WalletSubscription};
  27. use cdk::StreamExt;
  28. use cdk_integration_tests::init_regtest::{get_lnd_dir, LND_RPC_ADDR};
  29. use cdk_integration_tests::{get_mint_url_from_env, get_second_mint_url_from_env};
  30. use cdk_sqlite::wallet::{self, memory};
  31. use futures::join;
  32. use ln_regtest_rs::ln_client::{LightningClient, LndClient};
  33. use tokio::time::timeout;
  34. // This is the ln wallet we use to send/receive ln payements as the wallet
  35. async fn init_lnd_client() -> LndClient {
  36. // Try to get the temp directory from environment variable first (from .env file)
  37. let temp_dir = match env::var("CDK_ITESTS_DIR") {
  38. Ok(dir) => {
  39. let path = PathBuf::from(dir);
  40. println!("Using temp directory from CDK_ITESTS_DIR: {:?}", path);
  41. path
  42. }
  43. Err(_) => {
  44. panic!("Unknown temp dir");
  45. }
  46. };
  47. // The LND mint uses the second LND node (LND_TWO_RPC_ADDR = localhost:10010)
  48. let lnd_dir = get_lnd_dir(&temp_dir, "one");
  49. let cert_file = lnd_dir.join("tls.cert");
  50. let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
  51. println!("Looking for LND cert file: {:?}", cert_file);
  52. println!("Looking for LND macaroon file: {:?}", macaroon_file);
  53. println!("Connecting to LND at: https://{}", LND_RPC_ADDR);
  54. // Connect to LND
  55. LndClient::new(
  56. format!("https://{}", LND_RPC_ADDR),
  57. cert_file.clone(),
  58. macaroon_file.clone(),
  59. )
  60. .await
  61. .expect("Could not connect to lnd rpc")
  62. }
  63. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  64. async fn test_internal_payment() {
  65. let lnd_client = init_lnd_client().await;
  66. let wallet = Wallet::new(
  67. &get_mint_url_from_env(),
  68. CurrencyUnit::Sat,
  69. Arc::new(memory::empty().await.unwrap()),
  70. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  71. None,
  72. )
  73. .expect("failed to create new wallet");
  74. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  75. lnd_client
  76. .pay_invoice(mint_quote.request.clone())
  77. .await
  78. .expect("failed to pay invoice");
  79. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  80. let _proofs = proof_streams
  81. .next()
  82. .await
  83. .expect("payment")
  84. .expect("no error");
  85. assert!(wallet.total_balance().await.unwrap() == 100.into());
  86. let wallet_2 = Wallet::new(
  87. &get_mint_url_from_env(),
  88. CurrencyUnit::Sat,
  89. Arc::new(memory::empty().await.unwrap()),
  90. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  91. None,
  92. )
  93. .expect("failed to create new wallet");
  94. let mint_quote = wallet_2.mint_quote(10.into(), None).await.unwrap();
  95. let melt = wallet
  96. .melt_quote(mint_quote.request.clone(), None)
  97. .await
  98. .unwrap();
  99. assert_eq!(melt.amount, 10.into());
  100. let _melted = wallet.melt(&melt.id).await.unwrap();
  101. let mut proof_streams = wallet_2.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  102. let _proofs = proof_streams
  103. .next()
  104. .await
  105. .expect("payment")
  106. .expect("no error");
  107. // let check_paid = match get_mint_port("0") {
  108. // 8085 => {
  109. // let cln_one_dir = get_cln_dir(&get_temp_dir(), "one");
  110. // let cln_client = ClnClient::new(cln_one_dir.clone(), None).await.unwrap();
  111. // let payment_hash = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
  112. // cln_client
  113. // .check_incoming_payment_status(&payment_hash.payment_hash().to_string())
  114. // .await
  115. // .expect("Could not check invoice")
  116. // }
  117. // 8087 => {
  118. // let lnd_two_dir = get_lnd_dir(&get_temp_dir(), "two");
  119. // let lnd_client = LndClient::new(
  120. // format!("https://{}", LND_TWO_RPC_ADDR),
  121. // get_lnd_cert_file_path(&lnd_two_dir),
  122. // get_lnd_macaroon_path(&lnd_two_dir),
  123. // )
  124. // .await
  125. // .unwrap();
  126. // let payment_hash = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
  127. // lnd_client
  128. // .check_incoming_payment_status(&payment_hash.payment_hash().to_string())
  129. // .await
  130. // .expect("Could not check invoice")
  131. // }
  132. // _ => panic!("Unknown mint port"),
  133. // };
  134. // match check_paid {
  135. // InvoiceStatus::Unpaid => (),
  136. // _ => {
  137. // panic!("Invoice has incorrect status: {:?}", check_paid);
  138. // }
  139. // }
  140. let wallet_2_balance = wallet_2.total_balance().await.unwrap();
  141. assert!(wallet_2_balance == 10.into());
  142. let wallet_1_balance = wallet.total_balance().await.unwrap();
  143. assert!(wallet_1_balance == 90.into());
  144. }
  145. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  146. async fn test_websocket_connection() {
  147. let wallet = Wallet::new(
  148. &get_mint_url_from_env(),
  149. CurrencyUnit::Sat,
  150. Arc::new(wallet::memory::empty().await.unwrap()),
  151. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  152. None,
  153. )
  154. .expect("failed to create new wallet");
  155. // Create a small mint quote to test notifications
  156. let mint_quote = wallet.mint_quote(10.into(), None).await.unwrap();
  157. // Subscribe to notifications for this quote
  158. let mut subscription = wallet
  159. .subscribe(WalletSubscription::Bolt11MintQuoteState(vec![mint_quote
  160. .id
  161. .clone()]))
  162. .await;
  163. // First check we get the unpaid state
  164. let msg = timeout(Duration::from_secs(10), subscription.recv())
  165. .await
  166. .expect("timeout waiting for unpaid notification")
  167. .expect("No paid notification received");
  168. match msg {
  169. NotificationPayload::MintQuoteBolt11Response(response) => {
  170. assert_eq!(response.quote.to_string(), mint_quote.id);
  171. assert_eq!(response.state, MintQuoteState::Unpaid);
  172. }
  173. _ => panic!("Unexpected notification type"),
  174. }
  175. let lnd_client = init_lnd_client().await;
  176. lnd_client
  177. .pay_invoice(mint_quote.request)
  178. .await
  179. .expect("failed to pay invoice");
  180. // Wait for paid notification with 10 second timeout
  181. let msg = timeout(Duration::from_secs(10), subscription.recv())
  182. .await
  183. .expect("timeout waiting for paid notification")
  184. .expect("No paid notification received");
  185. match msg {
  186. NotificationPayload::MintQuoteBolt11Response(response) => {
  187. assert_eq!(response.quote.to_string(), mint_quote.id);
  188. assert_eq!(response.state, MintQuoteState::Paid);
  189. }
  190. _ => panic!("Unexpected notification type"),
  191. }
  192. }
  193. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  194. async fn test_multimint_melt() {
  195. let lnd_client = init_lnd_client().await;
  196. let db = Arc::new(memory::empty().await.unwrap());
  197. let wallet1 = Wallet::new(
  198. &get_mint_url_from_env(),
  199. CurrencyUnit::Sat,
  200. db,
  201. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  202. None,
  203. )
  204. .expect("failed to create new wallet");
  205. let db = Arc::new(memory::empty().await.unwrap());
  206. let wallet2 = Wallet::new(
  207. &get_second_mint_url_from_env(),
  208. CurrencyUnit::Sat,
  209. db,
  210. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  211. None,
  212. )
  213. .expect("failed to create new wallet");
  214. let mint_amount = Amount::from(100);
  215. // Fund the wallets
  216. let quote = wallet1.mint_quote(mint_amount, None).await.unwrap();
  217. lnd_client
  218. .pay_invoice(quote.request.clone())
  219. .await
  220. .expect("failed to pay invoice");
  221. let mut proof_streams = wallet1.proof_stream(quote.clone(), SplitTarget::default(), None);
  222. let _proofs = proof_streams
  223. .next()
  224. .await
  225. .expect("payment")
  226. .expect("no error");
  227. let quote = wallet2.mint_quote(mint_amount, None).await.unwrap();
  228. lnd_client
  229. .pay_invoice(quote.request.clone())
  230. .await
  231. .expect("failed to pay invoice");
  232. let mut proof_streams = wallet2.proof_stream(quote.clone(), SplitTarget::default(), None);
  233. let _proofs = proof_streams
  234. .next()
  235. .await
  236. .expect("payment")
  237. .expect("no error");
  238. // Get an invoice
  239. let invoice = lnd_client.create_invoice(Some(50)).await.unwrap();
  240. // Get multi-part melt quotes
  241. let melt_options = MeltOptions::Mpp {
  242. mpp: Mpp {
  243. amount: Amount::from(25000),
  244. },
  245. };
  246. let quote_1 = wallet1
  247. .melt_quote(invoice.clone(), Some(melt_options))
  248. .await
  249. .expect("Could not get melt quote");
  250. let quote_2 = wallet2
  251. .melt_quote(invoice.clone(), Some(melt_options))
  252. .await
  253. .expect("Could not get melt quote");
  254. // Multimint pay invoice
  255. let result1 = wallet1.melt(&quote_1.id);
  256. let result2 = wallet2.melt(&quote_2.id);
  257. let result = join!(result1, result2);
  258. // Unpack results
  259. let result1 = result.0.unwrap();
  260. let result2 = result.1.unwrap();
  261. // Check
  262. assert!(result1.state == result2.state);
  263. assert!(result1.state == MeltQuoteState::Paid);
  264. }
  265. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  266. async fn test_cached_mint() {
  267. let lnd_client = init_lnd_client().await;
  268. let wallet = Wallet::new(
  269. &get_mint_url_from_env(),
  270. CurrencyUnit::Sat,
  271. Arc::new(memory::empty().await.unwrap()),
  272. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  273. None,
  274. )
  275. .expect("failed to create new wallet");
  276. let mint_amount = Amount::from(100);
  277. let quote = wallet.mint_quote(mint_amount, None).await.unwrap();
  278. lnd_client
  279. .pay_invoice(quote.request.clone())
  280. .await
  281. .expect("failed to pay invoice");
  282. let mut payment_streams = wallet.payment_stream(&quote);
  283. let _ = payment_streams.next().await;
  284. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  285. let http_client = HttpClient::new(get_mint_url_from_env().parse().unwrap(), None);
  286. let premint_secrets =
  287. PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::default()).unwrap();
  288. let mut request = MintRequest {
  289. quote: quote.id,
  290. outputs: premint_secrets.blinded_messages(),
  291. signature: None,
  292. };
  293. let secret_key = quote.secret_key;
  294. request
  295. .sign(secret_key.expect("Secret key on quote"))
  296. .unwrap();
  297. let response = http_client.post_mint(request.clone()).await.unwrap();
  298. let response1 = http_client.post_mint(request).await.unwrap();
  299. assert!(response == response1);
  300. }
  301. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  302. async fn test_regtest_melt_amountless() {
  303. let lnd_client = init_lnd_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. lnd_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 = lnd_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. }