Skip to content

Commit 7ce1cbc

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 91ed8ec commit 7ce1cbc

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
@@ -50,12 +50,9 @@ pub struct Config {
5050
pub storage_dir_path: Option<String>,
5151
pub chain_source: ChainSource,
5252
pub rgs_server_url: Option<String>,
53-
#[cfg_attr(not(feature = "events-rabbitmq"), allow(dead_code))]
54-
pub rabbitmq_connection_string: String,
55-
#[cfg_attr(not(feature = "events-rabbitmq"), allow(dead_code))]
56-
pub rabbitmq_exchange_name: String,
53+
pub rabbitmq_connection_string: Option<String>,
54+
pub rabbitmq_exchange_name: Option<String>,
5755
pub lsps2_client_config: Option<LSPSClientConfig>,
58-
#[cfg_attr(not(feature = "experimental-lsps2-support"), allow(dead_code))]
5956
pub lsps2_service_config: Option<LSPS2ServiceConfig>,
6057
pub log_level: LevelFilter,
6158
pub log_file_path: Option<String>,
@@ -176,8 +173,10 @@ impl ConfigBuilder {
176173
}
177174

178175
if let Some(rabbitmq) = toml.rabbitmq {
179-
self.rabbitmq_connection_string = Some(rabbitmq.connection_string);
180-
self.rabbitmq_exchange_name = Some(rabbitmq.exchange_name);
176+
self.rabbitmq_connection_string =
177+
rabbitmq.connection_string.or(self.rabbitmq_connection_string.clone());
178+
self.rabbitmq_exchange_name =
179+
rabbitmq.exchange_name.or(self.rabbitmq_exchange_name.clone());
181180
}
182181

183182
if let Some(liquidity) = toml.liquidity {
@@ -350,29 +349,17 @@ impl ConfigBuilder {
350349
.transpose()?
351350
.unwrap_or(LevelFilter::Debug);
352351

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

377364
let lsps2_client_config = self
378365
.lsps2
@@ -381,21 +368,7 @@ impl ConfigBuilder {
381368
.map(LSPSClientConfig::try_from)
382369
.transpose()?;
383370

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

400373
let pathfinding_scores_source_url = self.pathfinding_scores_source_url;
401374

@@ -484,8 +457,8 @@ struct LogConfig {
484457

485458
#[derive(Deserialize, Serialize)]
486459
struct RabbitmqConfig {
487-
connection_string: String,
488-
exchange_name: String,
460+
connection_string: Option<String>,
461+
exchange_name: Option<String>,
489462
}
490463

491464
#[derive(Deserialize, Serialize)]
@@ -827,13 +800,6 @@ mod tests {
827800

828801
let alias = "LDK Server";
829802

830-
#[cfg(feature = "events-rabbitmq")]
831-
let (expected_rabbit_conn, expected_rabbit_exchange) =
832-
("rabbitmq_connection_string".to_string(), "rabbitmq_exchange_name".to_string());
833-
834-
#[cfg(not(feature = "events-rabbitmq"))]
835-
let (expected_rabbit_conn, expected_rabbit_exchange) = (String::new(), String::new());
836-
837803
let expected = Config {
838804
listening_addrs: Some(vec![SocketAddress::from_str("localhost:3001").unwrap()]),
839805
announcement_addrs: Some(vec![SocketAddress::from_str("54.3.7.81:3001").unwrap()]),
@@ -853,8 +819,8 @@ mod tests {
853819
rpc_password: "bitcoind-testpassword".to_string(),
854820
},
855821
rgs_server_url: Some("https://rapidsync.lightningdevkit.org/snapshot/v2/".to_string()),
856-
rabbitmq_connection_string: expected_rabbit_conn,
857-
rabbitmq_exchange_name: expected_rabbit_exchange,
822+
rabbitmq_connection_string: Some("rabbitmq_connection_string".to_string()),
823+
rabbitmq_exchange_name: Some("rabbitmq_exchange_name".to_string()),
858824
lsps2_client_config: Some(LSPSClientConfig {
859825
node_id: PublicKey::from_str(
860826
"0217890e3aad8d35bc054f43acc00084b25229ecff0ab68debd82883ad65ee8266",
@@ -891,7 +857,6 @@ mod tests {
891857
assert_eq!(config.rabbitmq_connection_string, expected.rabbitmq_connection_string);
892858
assert_eq!(config.rabbitmq_exchange_name, expected.rabbitmq_exchange_name);
893859
assert_eq!(config.lsps2_client_config, expected.lsps2_client_config);
894-
#[cfg(feature = "experimental-lsps2-support")]
895860
assert_eq!(config.lsps2_service_config.is_some(), expected.lsps2_service_config.is_some());
896861
assert_eq!(config.log_level, expected.log_level);
897862
assert_eq!(config.log_file_path, expected.log_file_path);
@@ -1069,46 +1034,6 @@ mod tests {
10691034
assert_eq!(error.to_string(), "Must set a single chain source, multiple were configured");
10701035
}
10711036

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

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

11641073
#[test]
1165-
#[cfg(not(feature = "experimental-lsps2-support"))]
1166-
#[cfg(not(feature = "events-rabbitmq"))]
11671074
fn test_config_from_args_config() {
11681075
let args_config = default_args_config();
11691076
let config = load_config(&args_config).unwrap();
@@ -1194,8 +1101,8 @@ mod tests {
11941101
rpc_password: args_config.bitcoind_rpc_password.unwrap(),
11951102
},
11961103
rgs_server_url: None,
1197-
rabbitmq_connection_string: String::new(),
1198-
rabbitmq_exchange_name: String::new(),
1104+
rabbitmq_connection_string: None,
1105+
rabbitmq_exchange_name: None,
11991106
lsps2_client_config: None,
12001107
lsps2_service_config: None,
12011108
log_level: LevelFilter::Trace,
@@ -1217,8 +1124,6 @@ mod tests {
12171124
}
12181125

12191126
#[test]
1220-
#[cfg(not(feature = "experimental-lsps2-support"))]
1221-
#[cfg(not(feature = "events-rabbitmq"))]
12221127
fn test_config_missing_fields_in_args_config() {
12231128
macro_rules! validate_missing {
12241129
($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,29 +1222,43 @@ 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

13521264
#[test]

0 commit comments

Comments
 (0)