diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index e6c48970..33a131d2 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -255,6 +255,12 @@ impl TestConfigBuilder { self } + /// Set the gRPC service address. + pub fn grpc_service_address(mut self, address: &str) -> Self { + self.grpc_service_address = address.to_string(); + self + } + /// Set the node alias, or `None` to omit it entirely. pub fn alias(mut self, alias: Option<&str>) -> Self { self.alias = alias.map(str::to_string); @@ -596,7 +602,7 @@ pub fn start_expect_failure( } /// Allocate distinct ports outside the default Linux/macOS ephemeral ranges. /// Allocate each port only once per test process, so stopped servers can restart. -fn reserve_port() -> TcpListener { +pub fn reserve_port() -> TcpListener { static NEXT_PORT: AtomicU32 = AtomicU32::new(10_000); loop { let port = NEXT_PORT.fetch_add(1, Ordering::Relaxed); diff --git a/e2e-tests/tests/config.rs b/e2e-tests/tests/config.rs index 412766d4..3b6dd4b5 100644 --- a/e2e-tests/tests/config.rs +++ b/e2e-tests/tests/config.rs @@ -8,7 +8,8 @@ // licenses. use e2e_tests::{ - start_expect_failure, ChainSource, LdkServerHandle, TestBitcoind, TestConfigBuilder, + reserve_port, start_expect_failure, ChainSource, LdkServerHandle, TestBitcoind, + TestConfigBuilder, }; use ldk_server_grpc::api::GetNodeInfoRequest; @@ -98,6 +99,17 @@ async fn test_config_with_tls_hosts() { assert!(info.current_best_block.is_some()); } +#[test] +fn test_config_fail_grpc_address_in_use() { + let bitcoind = TestBitcoind::new(); + let blocker = reserve_port(); + let address = blocker.local_addr().unwrap().to_string(); + let stderr = start_expect_failure(&bitcoind, |params| { + TestConfigBuilder::new(params).grpc_service_address(&address).build() + }); + assert!(stderr.contains("Failed to bind gRPC service address"), "Unexpected stderr: {stderr}"); +} + #[test] fn test_config_fail_log_file_matches_storage_dir() { let bitcoind = TestBitcoind::new(); diff --git a/ldk-server/src/main.rs b/ldk-server/src/main.rs index 27450b54..077d3da4 100644 --- a/ldk-server/src/main.rs +++ b/ldk-server/src/main.rs @@ -38,7 +38,7 @@ use log::{debug, error, info, warn}; use prost::Message; use tokio::net::TcpListener; use tokio::select; -use tokio::signal::unix::SignalKind; +use tokio::signal::unix::{Signal, SignalKind}; use tokio::sync::broadcast; use crate::api::node_to_proto_custom_tlv; @@ -322,6 +322,30 @@ fn main() { let (event_sender, _) = broadcast::channel::(1024); let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false); + // Set up everything that can fail locally before the node starts, so that a failure never + // leaves a running node behind, and handle signals from the start. + let server_config = + match get_or_generate_tls_config(config_file.tls_config, storage_dir.to_str().unwrap()) { + Ok(config) => config, + Err(e) => { + error!("Failed to set up TLS: {e}"); + std::process::exit(-1); + }, + }; + let tls_acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(server_config)); + + let grpc_listener = match runtime.block_on(TcpListener::bind(config_file.grpc_service_addr)) { + Ok(listener) => listener, + Err(e) => { + error!("Failed to bind gRPC service address {}: {e}", config_file.grpc_service_addr); + std::process::exit(-1); + }, + }; + + let mut sighup_stream = register_signal(&runtime, SignalKind::hangup()); + let mut sigterm_stream = register_signal(&runtime, SignalKind::terminate()); + let mut sigint_stream = register_signal(&runtime, SignalKind::interrupt()); + info!("Starting ldk-server version {FULL_VERSION}"); match node.start() { Ok(()) => {}, @@ -343,22 +367,6 @@ fn main() { } runtime.block_on(async { - // Register SIGHUP handler for log rotation - let mut sighup_stream = match tokio::signal::unix::signal(SignalKind::hangup()) { - Ok(stream) => stream, - Err(e) => { - error!("Failed to register SIGHUP handler: {e}"); - std::process::exit(-1); - } - }; - - let mut sigterm_stream = match tokio::signal::unix::signal(SignalKind::terminate()) { - Ok(stream) => stream, - Err(e) => { - error!("Failed to register for SIGTERM stream: {e}"); - std::process::exit(-1); - } - }; let event_node = Arc::clone(&node); let mut ready_channel_ids: HashSet = event_node .list_channels() @@ -398,21 +406,6 @@ fn main() { None }; - let grpc_listener = TcpListener::bind(config_file.grpc_service_addr) - .await - .expect("Failed to bind listening port"); - - let server_config = match get_or_generate_tls_config( - config_file.tls_config, - storage_dir.to_str().unwrap(), - ) { - Ok(config) => config, - Err(e) => { - error!("Failed to set up TLS: {e}"); - std::process::exit(-1); - } - }; - let tls_acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(server_config)); info!("gRPC service listening on {}", config_file.grpc_service_addr); systemd::notify_ready(); @@ -753,7 +746,7 @@ fn main() { Err(e) => error!("Failed to accept connection: {}", e), } } - _ = tokio::signal::ctrl_c() => { + _ = sigint_stream.recv() => { info!("Received CTRL-C, shutting down.."); let _ = shutdown_tx.send(true); break; @@ -779,6 +772,16 @@ fn main() { log::logger().flush(); } +fn register_signal(runtime: &tokio::runtime::Runtime, kind: SignalKind) -> Signal { + match runtime.block_on(async { tokio::signal::unix::signal(kind) }) { + Ok(signal) => signal, + Err(e) => { + error!("Failed to register signal handler for {kind:?}: {e}"); + std::process::exit(-1); + }, + } +} + fn ensure_no_unsupported_storage_migration( network_dir: &Path, ldk_node_storage: &LdkNodeStorageConfig, ) -> Result<(), String> {