Skip to content

Commit 3fc6c7a

Browse files
benthecarmanclaude
andcommitted
Make rabbitmq and LSPS2 config optional when features are enabled
Allow building with features enabled but unconfigured so we can distribute single binaries and Docker images with all features compiled in, letting users opt-in via config at runtime. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 53fe7b0 commit 3fc6c7a

3 files changed

Lines changed: 68 additions & 152 deletions

File tree

ldk-server/src/io/events/event_publisher.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,9 @@ pub trait EventPublisher: Send + Sync {
5252
}
5353

5454
/// A no-op implementation of the [`EventPublisher`] trait.
55-
#[cfg(not(feature = "events-rabbitmq"))]
5655
pub(crate) struct NoopEventPublisher;
5756

5857
#[async_trait]
59-
#[cfg(not(feature = "events-rabbitmq"))]
6058
impl EventPublisher for NoopEventPublisher {
6159
/// Publishes an event to a no-op sink, effectively discarding it.
6260
///

ldk-server/src/main.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,9 @@ fn main() {
221221

222222
// LSPS2 support is highly experimental and for testing purposes only.
223223
#[cfg(feature = "experimental-lsps2-support")]
224-
builder.set_liquidity_provider_lsps2(
225-
config_file.lsps2_service_config.expect("Missing liquidity.lsps2_server config"),
226-
);
224+
if let Some(lsps2_config) = config_file.lsps2_service_config {
225+
builder.set_liquidity_provider_lsps2(lsps2_config);
226+
}
227227

228228
let runtime = match tokio::runtime::Builder::new_multi_thread().enable_all().build() {
229229
Ok(runtime) => Arc::new(runtime),
@@ -263,15 +263,21 @@ fn main() {
263263

264264
#[cfg(not(feature = "events-rabbitmq"))]
265265
let event_publisher: Arc<dyn EventPublisher> =
266-
Arc::new(crate::io::events::event_publisher::NoopEventPublisher);
266+
Arc::new(io::events::event_publisher::NoopEventPublisher);
267267

268268
#[cfg(feature = "events-rabbitmq")]
269269
let event_publisher: Arc<dyn EventPublisher> = {
270-
let rabbitmq_config = RabbitMqConfig {
271-
connection_string: config_file.rabbitmq_connection_string,
272-
exchange_name: config_file.rabbitmq_exchange_name,
273-
};
274-
Arc::new(RabbitMqEventPublisher::new(rabbitmq_config))
270+
match (config_file.rabbitmq_connection_string, config_file.rabbitmq_exchange_name) {
271+
(Some(connection_string), Some(exchange_name)) => {
272+
let rabbitmq_config = RabbitMqConfig { connection_string, exchange_name };
273+
Arc::new(RabbitMqEventPublisher::new(rabbitmq_config))
274+
},
275+
(None, None) => Arc::new(io::events::event_publisher::NoopEventPublisher),
276+
_ => {
277+
error!("Invalid RabbitMQ connection string or exchange name");
278+
std::process::exit(-1);
279+
},
280+
}
275281
};
276282

277283
info!("Starting up...");

ldk-server/src/util/config.rs

Lines changed: 53 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,9 @@ pub struct Config {
4848
pub storage_dir_path: Option<String>,
4949
pub chain_source: ChainSource,
5050
pub rgs_server_url: Option<String>,
51-
#[cfg_attr(not(feature = "events-rabbitmq"), allow(dead_code))]
52-
pub rabbitmq_connection_string: String,
53-
#[cfg_attr(not(feature = "events-rabbitmq"), allow(dead_code))]
54-
pub rabbitmq_exchange_name: String,
51+
pub rabbitmq_connection_string: Option<String>,
52+
pub rabbitmq_exchange_name: Option<String>,
5553
pub lsps2_client_config: Option<LSPSClientConfig>,
56-
#[cfg_attr(not(feature = "experimental-lsps2-support"), allow(dead_code))]
5754
pub lsps2_service_config: Option<LSPS2ServiceConfig>,
5855
pub log_level: LevelFilter,
5956
pub log_file_path: Option<String>,
@@ -174,8 +171,10 @@ impl ConfigBuilder {
174171
}
175172

176173
if let Some(rabbitmq) = toml.rabbitmq {
177-
self.rabbitmq_connection_string = Some(rabbitmq.connection_string);
178-
self.rabbitmq_exchange_name = Some(rabbitmq.exchange_name);
174+
self.rabbitmq_connection_string =
175+
rabbitmq.connection_string.or(self.rabbitmq_connection_string.clone());
176+
self.rabbitmq_exchange_name =
177+
rabbitmq.exchange_name.or(self.rabbitmq_exchange_name.clone());
179178
}
180179

181180
if let Some(liquidity) = toml.liquidity {
@@ -348,29 +347,17 @@ impl ConfigBuilder {
348347
.transpose()?
349348
.unwrap_or(LevelFilter::Debug);
350349

351-
#[cfg(feature = "events-rabbitmq")]
352-
let (rabbitmq_connection_string, rabbitmq_exchange_name) = {
353-
let connection_string = self.rabbitmq_connection_string.ok_or_else(|| io::Error::new(
354-
io::ErrorKind::InvalidInput,
355-
"Both `rabbitmq.connection_string` and `rabbitmq.exchange_name` must be configured if enabling `events-rabbitmq` feature."
356-
))?;
357-
let exchange_name = self.rabbitmq_exchange_name.ok_or_else(|| io::Error::new(
358-
io::ErrorKind::InvalidInput,
359-
"Both `rabbitmq.connection_string` and `rabbitmq.exchange_name` must be configured if enabling `events-rabbitmq` feature."
360-
))?;
361-
362-
if connection_string.is_empty() || exchange_name.is_empty() {
363-
return Err(io::Error::new(
364-
io::ErrorKind::InvalidInput,
365-
"Both `rabbitmq.connection_string` and `rabbitmq.exchange_name` must be configured if enabling `events-rabbitmq` feature."
366-
));
367-
}
368-
369-
(connection_string, exchange_name)
370-
};
371-
372-
#[cfg(not(feature = "events-rabbitmq"))]
373-
let (rabbitmq_connection_string, rabbitmq_exchange_name) = (String::new(), String::new());
350+
let (rabbitmq_connection_string, rabbitmq_exchange_name) =
351+
match (self.rabbitmq_connection_string, self.rabbitmq_exchange_name) {
352+
(Some(conn), Some(exchange)) => (Some(conn), Some(exchange)),
353+
(None, None) => (None, None),
354+
_ => {
355+
return Err(io::Error::new(
356+
io::ErrorKind::InvalidInput,
357+
"Both `rabbitmq.connection_string` and `rabbitmq.exchange_name` must be configured together.",
358+
));
359+
},
360+
};
374361

375362
let lsps2_client_config = self
376363
.lsps2
@@ -379,21 +366,7 @@ impl ConfigBuilder {
379366
.map(LSPSClientConfig::try_from)
380367
.transpose()?;
381368

382-
#[cfg(feature = "experimental-lsps2-support")]
383-
let lsps2_service_config = {
384-
let liquidity = self.lsps2.ok_or_else(|| io::Error::new(
385-
io::ErrorKind::InvalidInput,
386-
"`liquidity.lsps2_service` must be defined in config if enabling `experimental-lsps2-support` feature."
387-
))?;
388-
let lsps2_service = liquidity.lsps2_service.ok_or_else(|| io::Error::new(
389-
io::ErrorKind::InvalidInput,
390-
"`liquidity.lsps2_service` must be defined in config if enabling `experimental-lsps2-support` feature."
391-
))?;
392-
Some(lsps2_service.into())
393-
};
394-
395-
#[cfg(not(feature = "experimental-lsps2-support"))]
396-
let lsps2_service_config = None;
369+
let lsps2_service_config = self.lsps2.and_then(|l| l.lsps2_service).map(|s| s.into());
397370

398371
let pathfinding_scores_source_url = self.pathfinding_scores_source_url;
399372

@@ -482,8 +455,8 @@ struct LogConfig {
482455

483456
#[derive(Deserialize, Serialize)]
484457
struct RabbitmqConfig {
485-
connection_string: String,
486-
exchange_name: String,
458+
connection_string: Option<String>,
459+
exchange_name: Option<String>,
487460
}
488461

489462
#[derive(Deserialize, Serialize)]
@@ -825,13 +798,6 @@ mod tests {
825798

826799
let alias = "LDK Server";
827800

828-
#[cfg(feature = "events-rabbitmq")]
829-
let (expected_rabbit_conn, expected_rabbit_exchange) =
830-
("rabbitmq_connection_string".to_string(), "rabbitmq_exchange_name".to_string());
831-
832-
#[cfg(not(feature = "events-rabbitmq"))]
833-
let (expected_rabbit_conn, expected_rabbit_exchange) = (String::new(), String::new());
834-
835801
let expected = Config {
836802
listening_addrs: Some(vec![SocketAddress::from_str("localhost:3001").unwrap()]),
837803
announcement_addrs: Some(vec![SocketAddress::from_str("54.3.7.81:3001").unwrap()]),
@@ -851,8 +817,8 @@ mod tests {
851817
rpc_password: "bitcoind-testpassword".to_string(),
852818
},
853819
rgs_server_url: Some("https://rapidsync.lightningdevkit.org/snapshot/v2/".to_string()),
854-
rabbitmq_connection_string: expected_rabbit_conn,
855-
rabbitmq_exchange_name: expected_rabbit_exchange,
820+
rabbitmq_connection_string: Some("rabbitmq_connection_string".to_string()),
821+
rabbitmq_exchange_name: Some("rabbitmq_exchange_name".to_string()),
856822
lsps2_client_config: Some(LSPSClientConfig {
857823
node_id: PublicKey::from_str(
858824
"0217890e3aad8d35bc054f43acc00084b25229ecff0ab68debd82883ad65ee8266",
@@ -889,7 +855,6 @@ mod tests {
889855
assert_eq!(config.rabbitmq_connection_string, expected.rabbitmq_connection_string);
890856
assert_eq!(config.rabbitmq_exchange_name, expected.rabbitmq_exchange_name);
891857
assert_eq!(config.lsps2_client_config, expected.lsps2_client_config);
892-
#[cfg(feature = "experimental-lsps2-support")]
893858
assert_eq!(config.lsps2_service_config.is_some(), expected.lsps2_service_config.is_some());
894859
assert_eq!(config.log_level, expected.log_level);
895860
assert_eq!(config.log_file_path, expected.log_file_path);
@@ -1067,46 +1032,6 @@ mod tests {
10671032
assert_eq!(error.to_string(), "Must set a single chain source, multiple were configured");
10681033
}
10691034

1070-
#[test]
1071-
fn test_config_optional_values() {
1072-
let storage_path = std::env::temp_dir();
1073-
let config_file_name = "test_only_required_config.toml";
1074-
1075-
let mut args_config = empty_args_config();
1076-
args_config.config_file =
1077-
Some(storage_path.join(config_file_name).to_string_lossy().to_string());
1078-
1079-
// Test with optional values not specified in the config file
1080-
let toml_config = r#"
1081-
[node]
1082-
network = "regtest"
1083-
rest_service_address = "127.0.0.1:3002"
1084-
1085-
[bitcoind]
1086-
rpc_address = "127.0.0.1:8332"
1087-
rpc_user = "bitcoind-testuser"
1088-
rpc_password = "bitcoind-testpassword"
1089-
1090-
[rabbitmq]
1091-
connection_string = "rabbitmq_connection_string"
1092-
exchange_name = "rabbitmq_exchange_name"
1093-
1094-
[liquidity.lsps2_service]
1095-
advertise_service = false
1096-
channel_opening_fee_ppm = 1000 # 0.1% fee
1097-
channel_over_provisioning_ppm = 500000 # 50% extra capacity
1098-
min_channel_opening_fee_msat = 10000000 # 10,000 satoshis
1099-
min_channel_lifetime = 4320 # ~30 days
1100-
max_client_to_self_delay = 1440 # ~10 days
1101-
min_payment_size_msat = 10000000 # 10,000 satoshis
1102-
max_payment_size_msat = 25000000000 # 0.25 BTC
1103-
client_trusts_lsp = true
1104-
"#;
1105-
1106-
fs::write(storage_path.join(config_file_name), toml_config).unwrap();
1107-
assert!(load_config(&args_config).is_ok());
1108-
}
1109-
11101035
#[test]
11111036
fn test_config_missing_fields_in_file() {
11121037
let storage_path = std::env::temp_dir();
@@ -1129,22 +1054,6 @@ mod tests {
11291054
};
11301055
}
11311056

1132-
#[cfg(feature = "experimental-lsps2-support")]
1133-
{
1134-
validate_missing!(
1135-
"[liquidity.lsps2_service]",
1136-
"`liquidity.lsps2_service` must be defined in config if enabling `experimental-lsps2-support` feature."
1137-
);
1138-
}
1139-
1140-
#[cfg(feature = "events-rabbitmq")]
1141-
{
1142-
validate_missing!(
1143-
"[rabbitmq]",
1144-
"Both `rabbitmq.connection_string` and `rabbitmq.exchange_name` must be configured if enabling `events-rabbitmq` feature."
1145-
);
1146-
}
1147-
11481057
validate_missing!("rpc_password", missing_field_msg("bitcoind_rpc_password"));
11491058
validate_missing!("rpc_user", missing_field_msg("bitcoind_rpc_user"));
11501059
validate_missing!("rpc_address", missing_field_msg("bitcoind_rpc_address"));
@@ -1161,8 +1070,6 @@ mod tests {
11611070
}
11621071

11631072
#[test]
1164-
#[cfg(not(feature = "experimental-lsps2-support"))]
1165-
#[cfg(not(feature = "events-rabbitmq"))]
11661073
fn test_config_from_args_config() {
11671074
let args_config = default_args_config();
11681075
let config = load_config(&args_config).unwrap();
@@ -1193,8 +1100,8 @@ mod tests {
11931100
rpc_password: args_config.bitcoind_rpc_password.unwrap(),
11941101
},
11951102
rgs_server_url: None,
1196-
rabbitmq_connection_string: String::new(),
1197-
rabbitmq_exchange_name: String::new(),
1103+
rabbitmq_connection_string: None,
1104+
rabbitmq_exchange_name: None,
11981105
lsps2_client_config: None,
11991106
lsps2_service_config: None,
12001107
log_level: LevelFilter::Trace,
@@ -1216,8 +1123,6 @@ mod tests {
12161123
}
12171124

12181125
#[test]
1219-
#[cfg(not(feature = "experimental-lsps2-support"))]
1220-
#[cfg(not(feature = "events-rabbitmq"))]
12211126
fn test_config_missing_fields_in_args_config() {
12221127
macro_rules! validate_missing {
12231128
($field:ident, $err_msg:expr) => {
@@ -1248,13 +1153,6 @@ mod tests {
12481153
args_config.config_file =
12491154
Some(storage_path.join(config_file_name).to_string_lossy().to_string());
12501155

1251-
#[cfg(feature = "events-rabbitmq")]
1252-
let (expected_rabbit_conn, expected_rabbit_exchange) =
1253-
("rabbitmq_connection_string".to_string(), "rabbitmq_exchange_name".to_string());
1254-
1255-
#[cfg(not(feature = "events-rabbitmq"))]
1256-
let (expected_rabbit_conn, expected_rabbit_exchange) = (String::new(), String::new());
1257-
12581156
let (host, port) =
12591157
parse_host_port(args_config.bitcoind_rpc_address.clone().unwrap().as_str()).unwrap();
12601158

@@ -1287,8 +1185,8 @@ mod tests {
12871185
rpc_password: args_config.bitcoind_rpc_password.unwrap(),
12881186
},
12891187
rgs_server_url: Some("https://rapidsync.lightningdevkit.org/snapshot/v2/".to_string()),
1290-
rabbitmq_connection_string: expected_rabbit_conn,
1291-
rabbitmq_exchange_name: expected_rabbit_exchange,
1188+
rabbitmq_connection_string: Some("rabbitmq_connection_string".to_string()),
1189+
rabbitmq_exchange_name: Some("rabbitmq_exchange_name".to_string()),
12921190
lsps2_client_config: Some(LSPSClientConfig {
12931191
node_id: PublicKey::from_str(
12941192
"0217890e3aad8d35bc054f43acc00084b25229ecff0ab68debd82883ad65ee8266",
@@ -1324,28 +1222,42 @@ mod tests {
13241222
assert_eq!(config.rabbitmq_connection_string, expected.rabbitmq_connection_string);
13251223
assert_eq!(config.rabbitmq_exchange_name, expected.rabbitmq_exchange_name);
13261224
assert_eq!(config.lsps2_client_config, expected.lsps2_client_config);
1327-
#[cfg(feature = "experimental-lsps2-support")]
13281225
assert_eq!(config.lsps2_service_config.is_some(), expected.lsps2_service_config.is_some());
13291226
assert_eq!(config.pathfinding_scores_source_url, expected.pathfinding_scores_source_url);
13301227
}
13311228

13321229
#[test]
1333-
#[cfg(feature = "events-rabbitmq")]
1334-
fn test_error_if_rabbitmq_feature_without_valid_config_file() {
1335-
let args_config = empty_args_config();
1336-
let result = load_config(&args_config);
1337-
assert!(result.is_err());
1338-
let err = result.unwrap_err();
1339-
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
1340-
}
1230+
fn test_error_if_partial_rabbitmq_config() {
1231+
let storage_path = std::env::temp_dir();
1232+
let config_file_name = "test_error_if_partial_rabbitmq_config.toml";
13411233

1342-
#[test]
1343-
#[cfg(feature = "experimental-lsps2-support")]
1344-
fn test_error_if_lsps2_feature_without_valid_config_file() {
1345-
let args_config = empty_args_config();
1234+
let mut args_config = empty_args_config();
1235+
args_config.config_file =
1236+
Some(storage_path.join(config_file_name).to_string_lossy().to_string());
1237+
1238+
// Only connection_string set, missing exchange_name
1239+
let toml_config = r#"
1240+
[node]
1241+
network = "regtest"
1242+
rest_service_address = "127.0.0.1:3002"
1243+
1244+
[bitcoind]
1245+
rpc_address = "127.0.0.1:8332"
1246+
rpc_user = "bitcoind-testuser"
1247+
rpc_password = "bitcoind-testpassword"
1248+
1249+
[rabbitmq]
1250+
connection_string = "amqp://localhost"
1251+
"#;
1252+
1253+
fs::write(storage_path.join(config_file_name), toml_config).unwrap();
13461254
let result = load_config(&args_config);
13471255
assert!(result.is_err());
13481256
let err = result.unwrap_err();
13491257
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
1258+
assert_eq!(
1259+
err.to_string(),
1260+
"Both `rabbitmq.connection_string` and `rabbitmq.exchange_name` must be configured together."
1261+
);
13501262
}
13511263
}

0 commit comments

Comments
 (0)