From 6f506465b47523004666d621ed479125f8436dd1 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Fri, 22 May 2026 15:54:36 -0500 Subject: [PATCH 1/2] Allow unknown config options in client We purposely don't parse the entire the config in the client, only the things releveant to the client. When we added the denys in e5eccc14029445492a45572cae4578ae113ca72d we now will fail with a normal config because our cli will throw an error for the config options we don't parse in the client. --- ldk-server-client/src/config.rs | 71 +++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/ldk-server-client/src/config.rs b/ldk-server-client/src/config.rs index e2dca342..cbe9a38c 100644 --- a/ldk-server-client/src/config.rs +++ b/ldk-server-client/src/config.rs @@ -70,7 +70,6 @@ pub fn cert_path_for_storage_dir(storage_dir: &str) -> PathBuf { /// Top-level structure of the `ldk-server` configuration TOML file. #[derive(Debug, Deserialize)] -#[serde(deny_unknown_fields)] pub struct Config { /// Node-level configuration. pub node: NodeConfig, @@ -82,7 +81,6 @@ pub struct Config { /// `[tls]` section of the configuration file. #[derive(Debug, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] pub struct TlsConfig { /// Path to the server's TLS certificate in PEM format. pub cert_path: Option, @@ -90,7 +88,6 @@ pub struct TlsConfig { /// `[node]` section of the configuration file. #[derive(Debug, Deserialize)] -#[serde(deny_unknown_fields)] pub struct NodeConfig { /// Address of the `ldk-server` gRPC service. #[serde(default = "default_grpc_service_address")] @@ -100,7 +97,6 @@ pub struct NodeConfig { /// `[storage]` section of the configuration file. #[derive(Debug, Deserialize)] -#[serde(deny_unknown_fields)] pub struct StorageConfig { /// On-disk storage configuration. pub disk: Option, @@ -108,7 +104,6 @@ pub struct StorageConfig { /// `[storage.disk]` section of the configuration file. #[derive(Debug, Deserialize)] -#[serde(deny_unknown_fields)] pub struct DiskConfig { /// Directory used by the server to store its persistent data. pub dir_path: Option, @@ -208,28 +203,62 @@ mod tests { } #[test] - fn config_rejects_unknown_fields() { - let top_level_err = toml::from_str::( + fn config_allows_server_config_fields() { + let config = toml::from_str::( r#" [node] network = "regtest" - - [unknown] - option = true + listening_addresses = ["localhost:3001"] + announcement_addresses = ["54.3.7.81:3001"] + grpc_service_address = "127.0.0.1:3002" + alias = "LDK Server" + rgs_server_url = "https://rapidsync.lightningdevkit.org/snapshot/v2/" + async_payments_role = "client" + + [tls] + cert_path = "/path/to/tls.crt" + key_path = "/path/to/tls.key" + hosts = ["example.com", "ldk-server.local"] + + [storage.disk] + dir_path = "/tmp" + + [log] + level = "Trace" + file = "/var/log/ldk-server.log" + + [bitcoind] + rpc_address = "127.0.0.1:8332" + rpc_user = "bitcoind-testuser" + rpc_password = "bitcoind-testpassword" + + [liquidity.lsps2_client] + node_pubkey = "0217890e3aad8d35bc054f43acc00084b25229ecff0ab68debd82883ad65ee8266" + address = "127.0.0.1:39735" + token = "lsps2-token" + + [liquidity.lsps2_service] + advertise_service = false + channel_opening_fee_ppm = 1000 + channel_over_provisioning_ppm = 500000 + min_channel_opening_fee_msat = 10000000 + min_channel_lifetime = 4320 + max_client_to_self_delay = 1440 + min_payment_size_msat = 10000000 + max_payment_size_msat = 25000000000 + client_trusts_lsp = true + disable_client_reserve = false + + [tor] + proxy_address = "127.0.0.1:9050" "#, ) - .unwrap_err(); - assert!(top_level_err.to_string().contains("unknown field `unknown`")); + .unwrap(); - let node_err = toml::from_str::( - r#" - [node] - network = "regtest" - unknown = true - "#, - ) - .unwrap_err(); - assert!(node_err.to_string().contains("unknown field `unknown`")); + assert_eq!(config.network().unwrap(), "regtest"); + assert_eq!(config.node.grpc_service_address, "127.0.0.1:3002"); + assert_eq!(config.tls.unwrap().cert_path.unwrap(), "/path/to/tls.crt"); + assert_eq!(config.storage.unwrap().disk.unwrap().dir_path.unwrap(), "/tmp"); } #[test] From 1ca0dabe7e90a878a7aed011dd57c6224b745c90 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Fri, 22 May 2026 16:01:32 -0500 Subject: [PATCH 2/2] Add e2e coverage for CLI config loading Exercise the CLI against the server-generated config file so e2e tests cover client-side parsing of daemon config sections. Rebuild the e2e CLI binary when the client crate changes so this path cannot use stale code. --- e2e-tests/build.rs | 2 ++ e2e-tests/src/lib.rs | 31 +++++++++++++++++++++++++++++++ e2e-tests/tests/e2e.rs | 15 ++++++++++++--- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/e2e-tests/build.rs b/e2e-tests/build.rs index 6bfa0b6a..10ccd3a0 100644 --- a/e2e-tests/build.rs +++ b/e2e-tests/build.rs @@ -54,6 +54,8 @@ fn main() { println!("cargo:rerun-if-changed=../ldk-server/Cargo.toml"); println!("cargo:rerun-if-changed=../ldk-server-cli/src"); println!("cargo:rerun-if-changed=../ldk-server-cli/Cargo.toml"); + println!("cargo:rerun-if-changed=../ldk-server-client/src"); + println!("cargo:rerun-if-changed=../ldk-server-client/Cargo.toml"); println!("cargo:rerun-if-changed=../ldk-server-grpc/src"); println!("cargo:rerun-if-changed=../ldk-server-grpc/Cargo.toml"); println!("cargo:rerun-if-changed=../ldk-server-mcp/src"); diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index a2d5df9a..b90214fb 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -91,6 +91,7 @@ pub struct LdkServerHandle { pub grpc_port: u16, pub p2p_port: u16, pub storage_dir: PathBuf, + pub config_path: PathBuf, pub api_key: String, pub tls_cert_path: PathBuf, pub node_id: String, @@ -214,6 +215,7 @@ poll_metrics_interval = 1 grpc_port, p2p_port, storage_dir, + config_path, api_key, tls_cert_path, node_id: String::new(), @@ -381,6 +383,28 @@ pub fn run_cli_raw(handle: &LdkServerHandle, args: &[&str]) -> String { String::from_utf8(output.stdout).unwrap() } +/// Run a CLI command using the server's config file for connection details. +pub fn run_cli_with_config_raw(handle: &LdkServerHandle, args: &[&str]) -> String { + let cli_path = cli_binary_path(); + let output = Command::new(&cli_path) + .arg("--config") + .arg(handle.config_path.to_str().unwrap()) + .args(args) + .output() + .unwrap_or_else(|e| panic!("Failed to run CLI at {:?}: {}", cli_path, e)); + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + panic!( + "CLI command {:?} failed with status {}\nstdout: {}\nstderr: {}", + args, output.status, stdout, stderr + ); + } + + String::from_utf8(output.stdout).unwrap() +} + /// Run a CLI command against the given server handle and return parsed JSON output. pub fn run_cli(handle: &LdkServerHandle, args: &[&str]) -> serde_json::Value { let stdout = run_cli_raw(handle, args); @@ -388,6 +412,13 @@ pub fn run_cli(handle: &LdkServerHandle, args: &[&str]) -> serde_json::Value { .unwrap_or_else(|e| panic!("Failed to parse CLI output as JSON: {e}\nOutput: {stdout}")) } +/// Run a CLI command using the server's config file and return parsed JSON output. +pub fn run_cli_with_config(handle: &LdkServerHandle, args: &[&str]) -> serde_json::Value { + let stdout = run_cli_with_config_raw(handle, args); + serde_json::from_str(&stdout) + .unwrap_or_else(|e| panic!("Failed to parse CLI output as JSON: {e}\nOutput: {stdout}")) +} + /// Mine blocks and wait for all servers to sync to the new chain tip. pub async fn mine_and_sync( bitcoind: &TestBitcoind, servers: &[&LdkServerHandle], block_count: u64, diff --git a/e2e-tests/tests/e2e.rs b/e2e-tests/tests/e2e.rs index 641cf623..0fb634a8 100644 --- a/e2e-tests/tests/e2e.rs +++ b/e2e-tests/tests/e2e.rs @@ -11,9 +11,9 @@ use std::str::FromStr; use std::time::Duration; use e2e_tests::{ - find_available_port, mine_and_sync, run_cli, run_cli_raw, setup_funded_channel, - wait_for_onchain_balance, wait_for_usable_channel, LdkServerConfig, LdkServerHandle, - TestBitcoind, + find_available_port, mine_and_sync, run_cli, run_cli_raw, run_cli_with_config, + setup_funded_channel, wait_for_onchain_balance, wait_for_usable_channel, LdkServerConfig, + LdkServerHandle, TestBitcoind, }; use hex_conservative::{DisplayHex, FromHex}; use ldk_node::bitcoin::hashes::{sha256, Hash}; @@ -57,6 +57,15 @@ async fn test_cli_get_node_info() { assert_eq!(output["node_id"], server.node_id()); } +#[tokio::test] +async fn test_cli_get_node_info_with_server_config() { + let bitcoind = TestBitcoind::new(); + let server = LdkServerHandle::start(&bitcoind).await; + + let output = run_cli_with_config(&server, &["get-node-info"]); + assert_eq!(output["node_id"], server.node_id()); +} + #[tokio::test] async fn test_cli_onchain_receive() { let bitcoind = TestBitcoind::new();