Skip to content

Commit c6c0afb

Browse files
committed
Reject unknown config options
Fail TOML parsing when unsupported config keys are present so typos do not get silently ignored. Keep the LSPS2 service section typed in all builds, while default builds still parse and ignore it unless the experimental feature is enabled.
1 parent 06c40b5 commit c6c0afb

1 file changed

Lines changed: 82 additions & 0 deletions

File tree

ldk-server/src/util/config.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ impl ConfigBuilder {
460460

461461
/// Configuration loaded from a TOML file.
462462
#[derive(Deserialize, Serialize)]
463+
#[serde(deny_unknown_fields)]
463464
pub struct TomlConfig {
464465
node: Option<NodeConfig>,
465466
storage: Option<StorageConfig>,
@@ -475,6 +476,7 @@ pub struct TomlConfig {
475476
}
476477

477478
#[derive(Deserialize, Serialize)]
479+
#[serde(deny_unknown_fields)]
478480
struct NodeConfig {
479481
network: Option<Network>,
480482
listening_addresses: Option<Vec<String>>,
@@ -487,46 +489,54 @@ struct NodeConfig {
487489
}
488490

489491
#[derive(Deserialize, Serialize)]
492+
#[serde(deny_unknown_fields)]
490493
struct StorageConfig {
491494
disk: Option<DiskConfig>,
492495
}
493496

494497
#[derive(Deserialize, Serialize)]
498+
#[serde(deny_unknown_fields)]
495499
struct DiskConfig {
496500
dir_path: Option<String>,
497501
}
498502

499503
#[derive(Deserialize, Serialize)]
504+
#[serde(deny_unknown_fields)]
500505
struct BitcoindConfig {
501506
rpc_address: Option<String>,
502507
rpc_user: Option<String>,
503508
rpc_password: Option<String>,
504509
}
505510

506511
#[derive(Deserialize, Serialize)]
512+
#[serde(deny_unknown_fields)]
507513
struct ElectrumConfig {
508514
server_url: String,
509515
}
510516

511517
#[derive(Deserialize, Serialize)]
518+
#[serde(deny_unknown_fields)]
512519
struct EsploraConfig {
513520
server_url: String,
514521
}
515522

516523
#[derive(Deserialize, Serialize)]
524+
#[serde(deny_unknown_fields)]
517525
struct LogConfig {
518526
level: Option<String>,
519527
file: Option<String>,
520528
}
521529

522530
#[derive(Deserialize, Serialize)]
531+
#[serde(deny_unknown_fields)]
523532
struct TomlTlsConfig {
524533
cert_path: Option<String>,
525534
key_path: Option<String>,
526535
hosts: Option<Vec<String>>,
527536
}
528537

529538
#[derive(Deserialize, Serialize)]
539+
#[serde(deny_unknown_fields)]
530540
struct MetricsTomlConfig {
531541
enabled: Option<bool>,
532542
poll_metrics_interval: Option<u64>,
@@ -535,11 +545,13 @@ struct MetricsTomlConfig {
535545
}
536546

537547
#[derive(Deserialize, Serialize)]
548+
#[serde(deny_unknown_fields)]
538549
struct TomlTorConfig {
539550
proxy_address: String,
540551
}
541552

542553
#[derive(Deserialize, Serialize)]
554+
#[serde(deny_unknown_fields)]
543555
struct HrnTomlConfig {
544556
mode: Option<String>,
545557
dns_server_address: Option<String>,
@@ -645,19 +657,22 @@ fn parse_async_payments_role(role: &str) -> io::Result<AsyncPaymentsRole> {
645657
}
646658

647659
#[derive(Deserialize, Serialize)]
660+
#[serde(deny_unknown_fields)]
648661
struct LiquidityConfig {
649662
lsps2_client: Option<LSPSClientTomlConfig>,
650663
lsps2_service: Option<LSPS2ServiceTomlConfig>,
651664
}
652665

653666
#[derive(Deserialize, Serialize, Debug)]
667+
#[serde(deny_unknown_fields)]
654668
struct LSPSClientTomlConfig {
655669
node_pubkey: String,
656670
address: String,
657671
token: Option<String>,
658672
}
659673

660674
#[derive(Deserialize, Serialize, Debug)]
675+
#[serde(deny_unknown_fields)]
661676
struct LSPS2ServiceTomlConfig {
662677
advertise_service: bool,
663678
channel_opening_fee_ppm: u32,
@@ -1343,6 +1358,73 @@ mod tests {
13431358
validate_missing!("network =", missing_field_msg("network"));
13441359
}
13451360

1361+
#[test]
1362+
fn test_config_unknown_fields_in_file() {
1363+
let storage_path = std::env::temp_dir();
1364+
let config_file_name = "test_config_unknown_fields_in_file.toml";
1365+
1366+
let mut args_config = empty_args_config();
1367+
args_config.config_file =
1368+
Some(storage_path.join(config_file_name).to_string_lossy().to_string());
1369+
1370+
fs::write(
1371+
storage_path.join(config_file_name),
1372+
format!("{}\n[unknown]\noption = true\n", DEFAULT_CONFIG),
1373+
)
1374+
.unwrap();
1375+
let err = load_config(&args_config).unwrap_err();
1376+
assert_eq!(err.kind(), io::ErrorKind::InvalidData);
1377+
assert!(err.to_string().contains("unknown field `unknown`"));
1378+
1379+
fs::write(
1380+
storage_path.join(config_file_name),
1381+
DEFAULT_CONFIG
1382+
.replace("network = \"regtest\"", "network = \"regtest\"\nunknown = true"),
1383+
)
1384+
.unwrap();
1385+
let err = load_config(&args_config).unwrap_err();
1386+
assert_eq!(err.kind(), io::ErrorKind::InvalidData);
1387+
assert!(err.to_string().contains("unknown field `unknown`"));
1388+
}
1389+
1390+
#[test]
1391+
#[cfg(not(feature = "experimental-lsps2-support"))]
1392+
fn test_config_allows_unused_lsps2_service_config_without_feature() {
1393+
let storage_path = std::env::temp_dir();
1394+
let config_file_name =
1395+
"test_config_allows_unused_lsps2_service_config_without_feature.toml";
1396+
1397+
let mut args_config = empty_args_config();
1398+
args_config.config_file =
1399+
Some(storage_path.join(config_file_name).to_string_lossy().to_string());
1400+
1401+
let toml_config = r#"
1402+
[node]
1403+
network = "regtest"
1404+
1405+
[bitcoind]
1406+
rpc_address = "127.0.0.1:8332"
1407+
rpc_user = "bitcoind-testuser"
1408+
rpc_password = "bitcoind-testpassword"
1409+
1410+
[liquidity.lsps2_service]
1411+
advertise_service = false
1412+
channel_opening_fee_ppm = 1000
1413+
channel_over_provisioning_ppm = 500000
1414+
min_channel_opening_fee_msat = 10000000
1415+
min_channel_lifetime = 4320
1416+
max_client_to_self_delay = 1440
1417+
min_payment_size_msat = 10000000
1418+
max_payment_size_msat = 25000000000
1419+
client_trusts_lsp = true
1420+
disable_client_reserve = false
1421+
"#;
1422+
1423+
fs::write(storage_path.join(config_file_name), toml_config).unwrap();
1424+
let config = load_config(&args_config).unwrap();
1425+
assert!(config.lsps2_service_config.is_none());
1426+
}
1427+
13461428
fn remove_config_line(config: &str, key: &str) -> String {
13471429
config
13481430
.lines()

0 commit comments

Comments
 (0)