start_regtest_mints.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. //! Binary for starting regtest mints
  2. //!
  3. //! This binary provides a programmatic way to start regtest mints for testing purposes:
  4. //! 1. Sets up a regtest environment with CLN and LND nodes
  5. //! 2. Starts CLN and LND mint instances using the cdk-mintd library
  6. //! 3. Configures the mints to connect to the respective Lightning Network backends
  7. //! 4. Waits for both mints to be ready and responsive
  8. //! 5. Keeps them running until interrupted (Ctrl+C)
  9. //! 6. Gracefully shuts down all services on receiving shutdown signal
  10. //!
  11. //! This approach offers better control and integration compared to external scripts,
  12. //! making it easier to run integration tests with consistent configuration.
  13. use std::fs;
  14. use std::path::Path;
  15. use std::sync::Arc;
  16. use std::time::Duration;
  17. use anyhow::{bail, Result};
  18. use bip39::Mnemonic;
  19. use cashu::Amount;
  20. use cdk_integration_tests::cli::CommonArgs;
  21. use cdk_integration_tests::init_regtest::start_regtest_end;
  22. use cdk_integration_tests::shared;
  23. use cdk_ldk_node::{CdkLdkNode, CdkLdkNodeBuilder};
  24. use cdk_mintd::config::LoggingConfig;
  25. use clap::Parser;
  26. use ldk_node::lightning::ln::msgs::SocketAddress;
  27. use tokio::runtime::Runtime;
  28. use tokio::signal;
  29. use tokio::signal::unix::SignalKind;
  30. use tokio::sync::{oneshot, Notify};
  31. use tokio::time::timeout;
  32. use tokio_util::sync::CancellationToken;
  33. #[derive(Parser)]
  34. #[command(name = "start-regtest-mints")]
  35. #[command(about = "Start regtest mints", long_about = None)]
  36. struct Args {
  37. #[command(flatten)]
  38. common: CommonArgs,
  39. /// Database type (sqlite)
  40. database_type: String,
  41. /// Working directory path
  42. work_dir: String,
  43. /// Mint address (default: 127.0.0.1)
  44. #[arg(default_value = "127.0.0.1")]
  45. mint_addr: String,
  46. /// CLN port (default: 8085)
  47. #[arg(default_value_t = 8085)]
  48. cln_port: u16,
  49. /// LND port (default: 8087)
  50. #[arg(default_value_t = 8087)]
  51. lnd_port: u16,
  52. /// LDK port (default: 8089)
  53. #[arg(default_value_t = 8089)]
  54. ldk_port: u16,
  55. }
  56. /// Start regtest CLN mint using the library
  57. async fn start_cln_mint(
  58. temp_dir: &Path,
  59. port: u16,
  60. shutdown: Arc<Notify>,
  61. ) -> Result<tokio::task::JoinHandle<()>> {
  62. let cln_rpc_path = temp_dir
  63. .join("cln")
  64. .join("one")
  65. .join("regtest")
  66. .join("lightning-rpc");
  67. let cln_config = cdk_mintd::config::Cln {
  68. rpc_path: cln_rpc_path,
  69. bolt12: false,
  70. fee_percent: 0.0,
  71. reserve_fee_min: 0.into(),
  72. };
  73. // Create settings struct for CLN mint using shared function
  74. let settings = shared::create_cln_settings(
  75. port,
  76. temp_dir
  77. .join("cln")
  78. .join("one")
  79. .join("regtest")
  80. .join("lightning-rpc"),
  81. "eye survey guilt napkin crystal cup whisper salt luggage manage unveil loyal".to_string(),
  82. cln_config,
  83. );
  84. println!("Starting CLN mintd on port {port}");
  85. let temp_dir = temp_dir.to_path_buf();
  86. let shutdown_clone = shutdown.clone();
  87. // Run the mint in a separate task
  88. let handle = tokio::spawn(async move {
  89. // Create a future that resolves when the shutdown signal is received
  90. let shutdown_future = async move {
  91. shutdown_clone.notified().await;
  92. println!("CLN mint shutdown signal received");
  93. };
  94. match cdk_mintd::run_mintd_with_shutdown(
  95. &temp_dir,
  96. &settings,
  97. shutdown_future,
  98. None,
  99. None,
  100. vec![],
  101. )
  102. .await
  103. {
  104. Ok(_) => println!("CLN mint exited normally"),
  105. Err(e) => eprintln!("CLN mint exited with error: {e}"),
  106. }
  107. });
  108. Ok(handle)
  109. }
  110. /// Start regtest LND mint using the library
  111. async fn start_lnd_mint(
  112. temp_dir: &Path,
  113. port: u16,
  114. shutdown: Arc<Notify>,
  115. ) -> Result<tokio::task::JoinHandle<()>> {
  116. let lnd_cert_file = temp_dir.join("lnd").join("two").join("tls.cert");
  117. let lnd_macaroon_file = temp_dir
  118. .join("lnd")
  119. .join("two")
  120. .join("data")
  121. .join("chain")
  122. .join("bitcoin")
  123. .join("regtest")
  124. .join("admin.macaroon");
  125. let lnd_work_dir = temp_dir.join("lnd_mint");
  126. // Create work directory for LND mint
  127. fs::create_dir_all(&lnd_work_dir)?;
  128. let lnd_config = cdk_mintd::config::Lnd {
  129. address: "https://localhost:10010".to_string(),
  130. cert_file: lnd_cert_file,
  131. macaroon_file: lnd_macaroon_file,
  132. fee_percent: 0.0,
  133. reserve_fee_min: 0.into(),
  134. };
  135. // Create settings struct for LND mint using shared function
  136. let settings = shared::create_lnd_settings(
  137. port,
  138. lnd_config,
  139. "cattle gold bind busy sound reduce tone addict baby spend february strategy".to_string(),
  140. );
  141. println!("Starting LND mintd on port {port}");
  142. let lnd_work_dir = lnd_work_dir.clone();
  143. let shutdown_clone = shutdown.clone();
  144. // Run the mint in a separate task
  145. let handle = tokio::spawn(async move {
  146. // Create a future that resolves when the shutdown signal is received
  147. let shutdown_future = async move {
  148. shutdown_clone.notified().await;
  149. println!("LND mint shutdown signal received");
  150. };
  151. match cdk_mintd::run_mintd_with_shutdown(
  152. &lnd_work_dir,
  153. &settings,
  154. shutdown_future,
  155. None,
  156. None,
  157. vec![],
  158. )
  159. .await
  160. {
  161. Ok(_) => println!("LND mint exited normally"),
  162. Err(e) => eprintln!("LND mint exited with error: {e}"),
  163. }
  164. });
  165. Ok(handle)
  166. }
  167. /// Start regtest LDK mint using the library
  168. /// If `existing_node` is provided, it will be used instead of creating a new one.
  169. /// This allows the mint to use a node that was already set up (e.g., with channels).
  170. async fn start_ldk_mint(
  171. temp_dir: &Path,
  172. port: u16,
  173. shutdown: Arc<Notify>,
  174. runtime: Option<std::sync::Arc<tokio::runtime::Runtime>>,
  175. existing_node: Option<CdkLdkNode>,
  176. ) -> Result<tokio::task::JoinHandle<()>> {
  177. let ldk_work_dir = temp_dir.join("ldk_mint");
  178. // Create work directory for LDK mint
  179. fs::create_dir_all(&ldk_work_dir)?;
  180. // Configure LDK node for regtest
  181. let ldk_config = cdk_mintd::config::LdkNode {
  182. fee_percent: 0.0,
  183. reserve_fee_min: 0.into(),
  184. bitcoin_network: Some("regtest".to_string()),
  185. // Use bitcoind RPC for regtest
  186. chain_source_type: Some("bitcoinrpc".to_string()),
  187. bitcoind_rpc_host: Some("127.0.0.1".to_string()),
  188. bitcoind_rpc_port: Some(18443),
  189. bitcoind_rpc_user: Some("testuser".to_string()),
  190. bitcoind_rpc_password: Some("testpass".to_string()),
  191. esplora_url: None,
  192. log_dir_path: None,
  193. ldk_node_announce_addresses: None,
  194. storage_dir_path: Some(ldk_work_dir.to_string_lossy().to_string()),
  195. ldk_node_host: Some("127.0.0.1".to_string()),
  196. ldk_node_port: Some(port + 10), // Use a different port for the LDK node P2P connections
  197. gossip_source_type: None,
  198. rgs_url: None,
  199. webserver_host: Some("127.0.0.1".to_string()),
  200. webserver_port: Some(port + 1), // Use next port for web interface
  201. ldk_node_mnemonic: Some(
  202. "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
  203. .to_string(),
  204. ),
  205. };
  206. // Create settings struct for LDK mint using a new shared function
  207. let settings = create_ldk_settings(port, ldk_config);
  208. println!("Starting LDK mintd on port {port}");
  209. let ldk_work_dir = ldk_work_dir.clone();
  210. let shutdown_clone = shutdown.clone();
  211. // Run the mint in a separate task
  212. let handle = tokio::spawn(async move {
  213. // Create a future that resolves when the shutdown signal is received
  214. let shutdown_future = async move {
  215. shutdown_clone.notified().await;
  216. println!("LDK mint shutdown signal received");
  217. };
  218. // Both nodes now use the same seed, so the standard flow should work.
  219. // The existing_node parameter is kept for API compatibility but not used
  220. // since run_mintd_with_shutdown will create its own node with the same seed.
  221. if existing_node.is_some() {
  222. println!("Using existing LDK node configuration (same seed as mint)");
  223. }
  224. match cdk_mintd::run_mintd_with_shutdown(
  225. &ldk_work_dir,
  226. &settings,
  227. shutdown_future,
  228. None,
  229. runtime,
  230. vec![],
  231. )
  232. .await
  233. {
  234. Ok(_) => println!("LDK mint exited normally"),
  235. Err(e) => eprintln!("LDK mint exited with error: {e}"),
  236. }
  237. });
  238. Ok(handle)
  239. }
  240. /// Create settings for an LDK mint
  241. fn create_ldk_settings(
  242. port: u16,
  243. ldk_config: cdk_mintd::config::LdkNode,
  244. ) -> cdk_mintd::config::Settings {
  245. cdk_mintd::config::Settings {
  246. info: cdk_mintd::config::Info {
  247. quote_ttl: None,
  248. url: format!("http://127.0.0.1:{port}"),
  249. listen_host: "127.0.0.1".to_string(),
  250. listen_port: port,
  251. seed: None,
  252. mnemonic: Some(
  253. "eye survey guilt napkin crystal cup whisper salt luggage manage unveil loyal"
  254. .to_string(),
  255. ),
  256. signatory_url: None,
  257. signatory_certs: None,
  258. input_fee_ppk: None,
  259. use_keyset_v2: None,
  260. http_cache: cdk_axum::cache::Config::default(),
  261. enable_swagger_ui: None,
  262. logging: LoggingConfig::default(),
  263. },
  264. mint_info: cdk_mintd::config::MintInfo::default(),
  265. limits: cdk_mintd::config::Limits::default(),
  266. ln: cdk_mintd::config::Ln {
  267. ln_backend: cdk_mintd::config::LnBackend::LdkNode,
  268. invoice_description: None,
  269. min_mint: 1.into(),
  270. max_mint: 500_000.into(),
  271. min_melt: 1.into(),
  272. max_melt: 500_000.into(),
  273. },
  274. cln: None,
  275. lnbits: None,
  276. lnd: None,
  277. ldk_node: Some(ldk_config),
  278. fake_wallet: None,
  279. grpc_processor: None,
  280. database: cdk_mintd::config::Database::default(),
  281. auth_database: None,
  282. mint_management_rpc: None,
  283. prometheus: None,
  284. auth: None,
  285. }
  286. }
  287. fn main() -> Result<()> {
  288. let rt = Arc::new(Runtime::new()?);
  289. let rt_clone = Arc::clone(&rt);
  290. rt.block_on(async {
  291. let args = Args::parse();
  292. // Initialize logging based on CLI arguments
  293. shared::setup_logging(&args.common);
  294. let temp_dir = shared::init_working_directory(&args.work_dir)?;
  295. // Write environment variables to a .env file in the temp_dir
  296. let mint_url_1 = format!("http://{}:{}", args.mint_addr, args.cln_port);
  297. let mint_url_2 = format!("http://{}:{}", args.mint_addr, args.lnd_port);
  298. let mint_url_3 = format!("http://{}:{}", args.mint_addr, args.ldk_port);
  299. let env_vars: Vec<(&str, &str)> = vec![
  300. ("CDK_TEST_MINT_URL", &mint_url_1),
  301. ("CDK_TEST_MINT_URL_2", &mint_url_2),
  302. ("CDK_TEST_MINT_URL_3", &mint_url_3),
  303. ];
  304. shared::write_env_file(&temp_dir, &env_vars)?;
  305. // Start regtest
  306. println!("Starting regtest...");
  307. let shutdown_regtest = shared::create_shutdown_handler();
  308. let shutdown_clone = shutdown_regtest.clone();
  309. let (tx, rx) = oneshot::channel();
  310. let shutdown_clone_one = Arc::clone(&shutdown_clone);
  311. let ldk_work_dir = temp_dir.join("ldk_mint");
  312. fs::create_dir_all(ldk_work_dir.join("logs"))?;
  313. let test_mnemonic: Mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
  314. .parse()
  315. .expect("Failed to parse test mnemonic");
  316. let node_builder = CdkLdkNodeBuilder::new(
  317. bitcoin::Network::Regtest,
  318. cdk_ldk_node::ChainSource::BitcoinRpc(cdk_ldk_node::BitcoinRpcConfig {
  319. host: "127.0.0.1".to_string(),
  320. port: 18443,
  321. user: "testuser".to_string(),
  322. password: "testpass".to_string(),
  323. }),
  324. cdk_ldk_node::GossipSource::P2P,
  325. ldk_work_dir.to_string_lossy().to_string(),
  326. cdk_common::common::FeeReserve {
  327. min_fee_reserve: Amount::ZERO,
  328. percent_fee_reserve: 0.0,
  329. },
  330. vec![SocketAddress::TcpIpV4 {
  331. addr: [127, 0, 0, 1],
  332. port: 8092,
  333. }],
  334. )
  335. .with_seed(test_mnemonic.clone());
  336. let cdk_ldk = match node_builder.build() {
  337. Ok(node) => node,
  338. Err(e) => {
  339. tracing::warn!(
  340. "Failed to start LDK node: {}. Attempting to wipe data and restart...",
  341. e
  342. );
  343. // Clean up the storage directory
  344. if ldk_work_dir.exists() {
  345. fs::remove_dir_all(&ldk_work_dir)?;
  346. fs::create_dir_all(ldk_work_dir.join("logs"))?;
  347. }
  348. // Recreate builder since it was consumed
  349. let node_builder = CdkLdkNodeBuilder::new(
  350. bitcoin::Network::Regtest,
  351. cdk_ldk_node::ChainSource::BitcoinRpc(cdk_ldk_node::BitcoinRpcConfig {
  352. host: "127.0.0.1".to_string(),
  353. port: 18443,
  354. user: "testuser".to_string(),
  355. password: "testpass".to_string(),
  356. }),
  357. cdk_ldk_node::GossipSource::P2P,
  358. ldk_work_dir.to_string_lossy().to_string(),
  359. cdk_common::common::FeeReserve {
  360. min_fee_reserve: Amount::ZERO,
  361. percent_fee_reserve: 0.0,
  362. },
  363. vec![SocketAddress::TcpIpV4 {
  364. addr: [127, 0, 0, 1],
  365. port: 8092,
  366. }],
  367. )
  368. .with_seed(test_mnemonic);
  369. node_builder.build()?
  370. }
  371. };
  372. let inner_node = cdk_ldk.node();
  373. let temp_dir_clone = temp_dir.clone();
  374. let shutdown_clone_two = Arc::clone(&shutdown_clone);
  375. tokio::spawn(async move {
  376. start_regtest_end(&temp_dir_clone, tx, shutdown_clone_two, Some(inner_node))
  377. .await
  378. .expect("Error starting regtest");
  379. });
  380. match timeout(Duration::from_secs(300), rx).await {
  381. Ok(k) => {
  382. k?;
  383. tracing::info!("Regtest set up");
  384. }
  385. Err(_) => {
  386. tracing::error!("regtest setup timed out after 5 minutes");
  387. anyhow::bail!("Could not set up regtest");
  388. }
  389. }
  390. println!("lnd port: {}", args.ldk_port);
  391. // Start LND mint
  392. let lnd_handle = start_lnd_mint(&temp_dir, args.lnd_port, shutdown_clone.clone()).await?;
  393. // Start LDK mint (using the existing node that was already set up with channels)
  394. let ldk_handle = start_ldk_mint(
  395. &temp_dir,
  396. args.ldk_port,
  397. shutdown_clone.clone(),
  398. Some(rt_clone),
  399. Some(cdk_ldk),
  400. )
  401. .await?;
  402. // Start CLN mint
  403. let cln_handle = start_cln_mint(&temp_dir, args.cln_port, shutdown_clone.clone()).await?;
  404. let cancel_token = Arc::new(CancellationToken::new());
  405. // Set up Ctrl+C handler before waiting for mints to be ready
  406. let ctrl_c_token = Arc::clone(&cancel_token);
  407. let s_u = shutdown_clone.clone();
  408. tokio::spawn(async move {
  409. signal::ctrl_c()
  410. .await
  411. .expect("failed to install CTRL+C handler");
  412. tracing::info!("Shutdown signal received during mint setup");
  413. println!("\nReceived Ctrl+C, shutting down...");
  414. ctrl_c_token.cancel();
  415. s_u.notify_waiters();
  416. });
  417. match tokio::try_join!(
  418. shared::wait_for_mint_ready_with_shutdown(
  419. args.lnd_port,
  420. 100,
  421. Arc::clone(&cancel_token)
  422. ),
  423. shared::wait_for_mint_ready_with_shutdown(
  424. args.ldk_port,
  425. 100,
  426. Arc::clone(&cancel_token)
  427. ),
  428. shared::wait_for_mint_ready_with_shutdown(
  429. args.cln_port,
  430. 100,
  431. Arc::clone(&cancel_token)
  432. ),
  433. ) {
  434. Ok(_) => println!("All mints are ready!"),
  435. Err(e) => {
  436. if cancel_token.is_cancelled() {
  437. bail!("Startup canceled by user");
  438. }
  439. eprintln!("Error waiting for mints to be ready: {e}");
  440. return Err(e);
  441. }
  442. }
  443. if cancel_token.is_cancelled() {
  444. bail!("Token canceled");
  445. }
  446. println!("All regtest mints started successfully!");
  447. println!("CLN mint: http://{}:{}", args.mint_addr, args.cln_port);
  448. println!("LND mint: http://{}:{}", args.mint_addr, args.lnd_port);
  449. println!("LDK mint: http://{}:{}", args.mint_addr, args.ldk_port);
  450. shared::display_mint_info(args.cln_port, &temp_dir, &args.database_type); // Using CLN port for display
  451. println!();
  452. println!("Environment variables set:");
  453. println!(
  454. " CDK_TEST_MINT_URL=http://{}:{}",
  455. args.mint_addr, args.cln_port
  456. );
  457. println!(
  458. " CDK_TEST_MINT_URL_2=http://{}:{}",
  459. args.mint_addr, args.lnd_port
  460. );
  461. println!(
  462. " CDK_TEST_MINT_URL_3=http://{}:{}",
  463. args.mint_addr, args.ldk_port
  464. );
  465. println!(" CDK_ITESTS_DIR={}", temp_dir.display());
  466. println!();
  467. println!("You can now run integration tests with:");
  468. println!(" cargo test -p cdk-integration-tests --test regtest");
  469. println!(" cargo test -p cdk-integration-tests --test happy_path_mint_wallet");
  470. println!(" etc.");
  471. println!();
  472. println!("Press Ctrl+C to stop the mints...");
  473. // Create a future to wait for either Ctrl+C signal or unexpected mint termination
  474. let shutdown_future = async {
  475. // Wait for either SIGINT (Ctrl+C) or SIGTERM
  476. let mut sigterm = signal::unix::signal(SignalKind::terminate())
  477. .expect("Failed to create SIGTERM signal handler");
  478. tokio::select! {
  479. _ = signal::ctrl_c() => {
  480. tracing::info!("Received SIGINT (Ctrl+C), shutting down mints...");
  481. }
  482. _ = sigterm.recv() => {
  483. tracing::info!("Received SIGTERM, shutting down mints...");
  484. }
  485. }
  486. println!("\nShutdown signal received, shutting down mints...");
  487. shutdown_clone.notify_waiters();
  488. };
  489. // Monitor mint handles for unexpected termination
  490. let monitor_mints = async {
  491. loop {
  492. if cln_handle.is_finished() {
  493. println!("CLN mint finished unexpectedly");
  494. return;
  495. }
  496. if lnd_handle.is_finished() {
  497. println!("LND mint finished unexpectedly");
  498. return;
  499. }
  500. if ldk_handle.is_finished() {
  501. println!("LDK mint finished unexpectedly");
  502. return;
  503. }
  504. tokio::time::sleep(Duration::from_millis(100)).await;
  505. }
  506. };
  507. // Wait for either shutdown signal or mint termination
  508. tokio::select! {
  509. _ = shutdown_clone_one.notified() => {
  510. println!("Shutdown signal received, waiting for mints to stop...");
  511. }
  512. _ = monitor_mints => {
  513. println!("One or more mints terminated unexpectedly");
  514. }
  515. _ = shutdown_future => ()
  516. }
  517. // Wait for mints to finish gracefully
  518. if let Err(e) = tokio::try_join!(ldk_handle, cln_handle, lnd_handle) {
  519. eprintln!("Error waiting for mints to shut down: {e}");
  520. }
  521. println!("All services shut down successfully");
  522. Ok(())
  523. })
  524. }