Skip to content

Commit 8f16055

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 536fe5b commit 8f16055

3 files changed

Lines changed: 68 additions & 149 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
@@ -204,9 +204,9 @@ fn main() {
204204

205205
// LSPS2 support is highly experimental and for testing purposes only.
206206
#[cfg(feature = "experimental-lsps2-support")]
207-
builder.set_liquidity_provider_lsps2(
208-
config_file.lsps2_service_config.expect("Missing liquidity.lsps2_server config"),
209-
);
207+
if let Some(lsps2_config) = config_file.lsps2_service_config {
208+
builder.set_liquidity_provider_lsps2(lsps2_config);
209+
}
210210

211211
let runtime = match tokio::runtime::Builder::new_multi_thread().enable_all().build() {
212212
Ok(runtime) => Arc::new(runtime),
@@ -246,15 +246,21 @@ fn main() {
246246

247247
#[cfg(not(feature = "events-rabbitmq"))]
248248
let event_publisher: Arc<dyn EventPublisher> =
249-
Arc::new(crate::io::events::event_publisher::NoopEventPublisher);
249+
Arc::new(io::events::event_publisher::NoopEventPublisher);
250250

251251
#[cfg(feature = "events-rabbitmq")]
252252
let event_publisher: Arc<dyn EventPublisher> = {
253-
let rabbitmq_config = RabbitMqConfig {
254-
connection_string: config_file.rabbitmq_connection_string,
255-
exchange_name: config_file.rabbitmq_exchange_name,
256-
};
257-
Arc::new(RabbitMqEventPublisher::new(rabbitmq_config))
253+
match (config_file.rabbitmq_connection_string, config_file.rabbitmq_exchange_name) {
254+
(Some(connection_string), Some(exchange_name)) => {
255+
let rabbitmq_config = RabbitMqConfig { connection_string, exchange_name };
256+
Arc::new(RabbitMqEventPublisher::new(rabbitmq_config))
257+
},
258+
(None, None) => Arc::new(io::events::event_publisher::NoopEventPublisher),
259+
_ => {
260+
error!("Invalid RabbitMQ connection string or exchange name");
261+
std::process::exit(-1);
262+
},
263+
}
258264
};
259265

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

ldk-server/src/util/config.rs

Lines changed: 53 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ pub struct Config {
4646
pub rest_service_addr: SocketAddr,
4747
pub storage_dir_path: Option<String>,
4848
pub chain_source: ChainSource,
49-
pub rabbitmq_connection_string: String,
50-
pub rabbitmq_exchange_name: String,
49+
pub rabbitmq_connection_string: Option<String>,
50+
pub rabbitmq_exchange_name: Option<String>,
5151
pub lsps2_service_config: Option<LSPS2ServiceConfig>,
5252
pub log_level: LevelFilter,
5353
pub log_file_path: Option<String>,
@@ -155,8 +155,10 @@ impl ConfigBuilder {
155155
}
156156

157157
if let Some(rabbitmq) = toml.rabbitmq {
158-
self.rabbitmq_connection_string = Some(rabbitmq.connection_string);
159-
self.rabbitmq_exchange_name = Some(rabbitmq.exchange_name);
158+
self.rabbitmq_connection_string =
159+
rabbitmq.connection_string.or(self.rabbitmq_connection_string.clone());
160+
self.rabbitmq_exchange_name =
161+
rabbitmq.exchange_name.or(self.rabbitmq_exchange_name.clone());
160162
}
161163

162164
if let Some(liquidity) = toml.liquidity {
@@ -325,45 +327,19 @@ impl ConfigBuilder {
325327
.transpose()?
326328
.unwrap_or(LevelFilter::Debug);
327329

328-
#[cfg(feature = "events-rabbitmq")]
329-
let (rabbitmq_connection_string, rabbitmq_exchange_name) = {
330-
let connection_string = self.rabbitmq_connection_string.ok_or_else(|| io::Error::new(
331-
io::ErrorKind::InvalidInput,
332-
"Both `rabbitmq.connection_string` and `rabbitmq.exchange_name` must be configured if enabling `events-rabbitmq` feature."
333-
))?;
334-
let exchange_name = self.rabbitmq_exchange_name.ok_or_else(|| io::Error::new(
335-
io::ErrorKind::InvalidInput,
336-
"Both `rabbitmq.connection_string` and `rabbitmq.exchange_name` must be configured if enabling `events-rabbitmq` feature."
337-
))?;
338-
339-
if connection_string.is_empty() || exchange_name.is_empty() {
340-
return Err(io::Error::new(
341-
io::ErrorKind::InvalidInput,
342-
"Both `rabbitmq.connection_string` and `rabbitmq.exchange_name` must be configured if enabling `events-rabbitmq` feature."
343-
));
344-
}
345-
346-
(connection_string, exchange_name)
347-
};
348-
349-
#[cfg(not(feature = "events-rabbitmq"))]
350-
let (rabbitmq_connection_string, rabbitmq_exchange_name) = (String::new(), String::new());
351-
352-
#[cfg(feature = "experimental-lsps2-support")]
353-
let lsps2_service_config = {
354-
let liquidity = self.lsps2.ok_or_else(|| io::Error::new(
355-
io::ErrorKind::InvalidInput,
356-
"`liquidity.lsps2_service` must be defined in config if enabling `experimental-lsps2-support` feature."
357-
))?;
358-
let lsps2_service = liquidity.lsps2_service.ok_or_else(|| io::Error::new(
359-
io::ErrorKind::InvalidInput,
360-
"`liquidity.lsps2_service` must be defined in config if enabling `experimental-lsps2-support` feature."
361-
))?;
362-
Some(lsps2_service.into())
363-
};
330+
let (rabbitmq_connection_string, rabbitmq_exchange_name) =
331+
match (self.rabbitmq_connection_string, self.rabbitmq_exchange_name) {
332+
(Some(conn), Some(exchange)) => (Some(conn), Some(exchange)),
333+
(None, None) => (None, None),
334+
_ => {
335+
return Err(io::Error::new(
336+
io::ErrorKind::InvalidInput,
337+
"Both `rabbitmq.connection_string` and `rabbitmq.exchange_name` must be configured together.",
338+
));
339+
},
340+
};
364341

365-
#[cfg(not(feature = "experimental-lsps2-support"))]
366-
let lsps2_service_config = None;
342+
let lsps2_service_config = self.lsps2.and_then(|l| l.lsps2_service).map(|s| s.into());
367343

368344
Ok(Config {
369345
network,
@@ -445,8 +421,8 @@ struct LogConfig {
445421

446422
#[derive(Deserialize, Serialize)]
447423
struct RabbitmqConfig {
448-
connection_string: String,
449-
exchange_name: String,
424+
connection_string: Option<String>,
425+
exchange_name: Option<String>,
450426
}
451427

452428
#[derive(Deserialize, Serialize)]
@@ -743,13 +719,6 @@ mod tests {
743719

744720
let alias = "LDK Server";
745721

746-
#[cfg(feature = "events-rabbitmq")]
747-
let (expected_rabbit_conn, expected_rabbit_exchange) =
748-
("rabbitmq_connection_string".to_string(), "rabbitmq_exchange_name".to_string());
749-
750-
#[cfg(not(feature = "events-rabbitmq"))]
751-
let (expected_rabbit_conn, expected_rabbit_exchange) = (String::new(), String::new());
752-
753722
let expected = Config {
754723
listening_addrs: Some(vec![SocketAddress::from_str("localhost:3001").unwrap()]),
755724
announcement_addrs: Some(vec![SocketAddress::from_str("54.3.7.81:3001").unwrap()]),
@@ -768,8 +737,8 @@ mod tests {
768737
rpc_user: "bitcoind-testuser".to_string(),
769738
rpc_password: "bitcoind-testpassword".to_string(),
770739
},
771-
rabbitmq_connection_string: expected_rabbit_conn,
772-
rabbitmq_exchange_name: expected_rabbit_exchange,
740+
rabbitmq_connection_string: Some("rabbitmq_connection_string".to_string()),
741+
rabbitmq_exchange_name: Some("rabbitmq_exchange_name".to_string()),
773742
lsps2_service_config: Some(LSPS2ServiceConfig {
774743
require_token: None,
775744
advertise_service: false,
@@ -795,7 +764,6 @@ mod tests {
795764
assert_eq!(config.chain_source, expected.chain_source);
796765
assert_eq!(config.rabbitmq_connection_string, expected.rabbitmq_connection_string);
797766
assert_eq!(config.rabbitmq_exchange_name, expected.rabbitmq_exchange_name);
798-
#[cfg(feature = "experimental-lsps2-support")]
799767
assert_eq!(config.lsps2_service_config.is_some(), expected.lsps2_service_config.is_some());
800768
assert_eq!(config.log_level, expected.log_level);
801769
assert_eq!(config.log_file_path, expected.log_file_path);
@@ -957,46 +925,6 @@ mod tests {
957925
assert_eq!(error.to_string(), "Must set a single chain source, multiple were configured");
958926
}
959927

960-
#[test]
961-
fn test_config_optional_values() {
962-
let storage_path = std::env::temp_dir();
963-
let config_file_name = "test_only_required_config.toml";
964-
965-
let mut args_config = empty_args_config();
966-
args_config.config_file =
967-
Some(storage_path.join(config_file_name).to_string_lossy().to_string());
968-
969-
// Test with optional values not specified in the config file
970-
let toml_config = r#"
971-
[node]
972-
network = "regtest"
973-
rest_service_address = "127.0.0.1:3002"
974-
975-
[bitcoind]
976-
rpc_address = "127.0.0.1:8332"
977-
rpc_user = "bitcoind-testuser"
978-
rpc_password = "bitcoind-testpassword"
979-
980-
[rabbitmq]
981-
connection_string = "rabbitmq_connection_string"
982-
exchange_name = "rabbitmq_exchange_name"
983-
984-
[liquidity.lsps2_service]
985-
advertise_service = false
986-
channel_opening_fee_ppm = 1000 # 0.1% fee
987-
channel_over_provisioning_ppm = 500000 # 50% extra capacity
988-
min_channel_opening_fee_msat = 10000000 # 10,000 satoshis
989-
min_channel_lifetime = 4320 # ~30 days
990-
max_client_to_self_delay = 1440 # ~10 days
991-
min_payment_size_msat = 10000000 # 10,000 satoshis
992-
max_payment_size_msat = 25000000000 # 0.25 BTC
993-
client_trusts_lsp = true
994-
"#;
995-
996-
fs::write(storage_path.join(config_file_name), toml_config).unwrap();
997-
assert!(load_config(&args_config).is_ok());
998-
}
999-
1000928
#[test]
1001929
fn test_config_missing_fields_in_file() {
1002930
let storage_path = std::env::temp_dir();
@@ -1019,22 +947,6 @@ mod tests {
1019947
};
1020948
}
1021949

1022-
#[cfg(feature = "experimental-lsps2-support")]
1023-
{
1024-
validate_missing!(
1025-
"[liquidity.lsps2_service]",
1026-
"`liquidity.lsps2_service` must be defined in config if enabling `experimental-lsps2-support` feature."
1027-
);
1028-
}
1029-
1030-
#[cfg(feature = "events-rabbitmq")]
1031-
{
1032-
validate_missing!(
1033-
"[rabbitmq]",
1034-
"Both `rabbitmq.connection_string` and `rabbitmq.exchange_name` must be configured if enabling `events-rabbitmq` feature."
1035-
);
1036-
}
1037-
1038950
validate_missing!("rpc_password", missing_field_msg("bitcoind_rpc_password"));
1039951
validate_missing!("rpc_user", missing_field_msg("bitcoind_rpc_user"));
1040952
validate_missing!("rpc_address", missing_field_msg("bitcoind_rpc_address"));
@@ -1051,8 +963,6 @@ mod tests {
1051963
}
1052964

1053965
#[test]
1054-
#[cfg(not(feature = "experimental-lsps2-support"))]
1055-
#[cfg(not(feature = "events-rabbitmq"))]
1056966
fn test_config_from_args_config() {
1057967
let args_config = default_args_config();
1058968
let config = load_config(&args_config).unwrap();
@@ -1082,8 +992,8 @@ mod tests {
1082992
rpc_user: args_config.bitcoind_rpc_user.unwrap(),
1083993
rpc_password: args_config.bitcoind_rpc_password.unwrap(),
1084994
},
1085-
rabbitmq_connection_string: String::new(),
1086-
rabbitmq_exchange_name: String::new(),
995+
rabbitmq_connection_string: None,
996+
rabbitmq_exchange_name: None,
1087997
lsps2_service_config: None,
1088998
log_level: LevelFilter::Trace,
1089999
log_file_path: Some("/var/log/ldk-server.log".to_string()),
@@ -1101,8 +1011,6 @@ mod tests {
11011011
}
11021012

11031013
#[test]
1104-
#[cfg(not(feature = "experimental-lsps2-support"))]
1105-
#[cfg(not(feature = "events-rabbitmq"))]
11061014
fn test_config_missing_fields_in_args_config() {
11071015
macro_rules! validate_missing {
11081016
($field:ident, $err_msg:expr) => {
@@ -1133,13 +1041,6 @@ mod tests {
11331041
args_config.config_file =
11341042
Some(storage_path.join(config_file_name).to_string_lossy().to_string());
11351043

1136-
#[cfg(feature = "events-rabbitmq")]
1137-
let (expected_rabbit_conn, expected_rabbit_exchange) =
1138-
("rabbitmq_connection_string".to_string(), "rabbitmq_exchange_name".to_string());
1139-
1140-
#[cfg(not(feature = "events-rabbitmq"))]
1141-
let (expected_rabbit_conn, expected_rabbit_exchange) = (String::new(), String::new());
1142-
11431044
let (host, port) =
11441045
parse_host_port(args_config.bitcoind_rpc_address.clone().unwrap().as_str()).unwrap();
11451046

@@ -1171,8 +1072,8 @@ mod tests {
11711072
rpc_user: args_config.bitcoind_rpc_user.unwrap(),
11721073
rpc_password: args_config.bitcoind_rpc_password.unwrap(),
11731074
},
1174-
rabbitmq_connection_string: expected_rabbit_conn,
1175-
rabbitmq_exchange_name: expected_rabbit_exchange,
1075+
rabbitmq_connection_string: Some("rabbitmq_connection_string".to_string()),
1076+
rabbitmq_exchange_name: Some("rabbitmq_exchange_name".to_string()),
11761077
lsps2_service_config: Some(LSPS2ServiceConfig {
11771078
require_token: None,
11781079
advertise_service: false,
@@ -1197,27 +1098,41 @@ mod tests {
11971098
assert_eq!(config.chain_source, expected.chain_source);
11981099
assert_eq!(config.rabbitmq_connection_string, expected.rabbitmq_connection_string);
11991100
assert_eq!(config.rabbitmq_exchange_name, expected.rabbitmq_exchange_name);
1200-
#[cfg(feature = "experimental-lsps2-support")]
12011101
assert_eq!(config.lsps2_service_config.is_some(), expected.lsps2_service_config.is_some());
12021102
}
12031103

12041104
#[test]
1205-
#[cfg(feature = "events-rabbitmq")]
1206-
fn test_error_if_rabbitmq_feature_without_valid_config_file() {
1207-
let args_config = empty_args_config();
1208-
let result = load_config(&args_config);
1209-
assert!(result.is_err());
1210-
let err = result.unwrap_err();
1211-
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
1212-
}
1105+
fn test_error_if_partial_rabbitmq_config() {
1106+
let storage_path = std::env::temp_dir();
1107+
let config_file_name = "test_error_if_partial_rabbitmq_config.toml";
12131108

1214-
#[test]
1215-
#[cfg(feature = "experimental-lsps2-support")]
1216-
fn test_error_if_lsps2_feature_without_valid_config_file() {
1217-
let args_config = empty_args_config();
1109+
let mut args_config = empty_args_config();
1110+
args_config.config_file =
1111+
Some(storage_path.join(config_file_name).to_string_lossy().to_string());
1112+
1113+
// Only connection_string set, missing exchange_name
1114+
let toml_config = r#"
1115+
[node]
1116+
network = "regtest"
1117+
rest_service_address = "127.0.0.1:3002"
1118+
1119+
[bitcoind]
1120+
rpc_address = "127.0.0.1:8332"
1121+
rpc_user = "bitcoind-testuser"
1122+
rpc_password = "bitcoind-testpassword"
1123+
1124+
[rabbitmq]
1125+
connection_string = "amqp://localhost"
1126+
"#;
1127+
1128+
fs::write(storage_path.join(config_file_name), toml_config).unwrap();
12181129
let result = load_config(&args_config);
12191130
assert!(result.is_err());
12201131
let err = result.unwrap_err();
12211132
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
1133+
assert_eq!(
1134+
err.to_string(),
1135+
"Both `rabbitmq.connection_string` and `rabbitmq.exchange_name` must be configured together."
1136+
);
12221137
}
12231138
}

0 commit comments

Comments
 (0)