Skip to content

Commit cd0b6ea

Browse files
committed
Add esplora chain source
1 parent ae2535f commit cd0b6ea

File tree

3 files changed

+153
-32
lines changed

3 files changed

+153
-32
lines changed

ldk-server/ldk-server-config.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,19 @@ rest_service_address = "127.0.0.1:3002" # LDK Server REST address
88
[storage.disk]
99
dir_path = "/tmp/ldk-server/" # Path for LDK and BDK data persistence
1010

11+
12+
# Must set either bitcoind or esplora settings, but not both
13+
1114
# Bitcoin Core settings
1215
[bitcoind]
1316
rpc_address = "127.0.0.1:18444" # RPC endpoint
1417
rpc_user = "polaruser" # RPC username
1518
rpc_password = "polarpass" # RPC password
1619

20+
# Esplora settings
21+
[esplora]
22+
server_url = "https://mempool.space/api" # Esplora endpoint
23+
1724
# RabbitMQ settings (only required if using events-rabbitmq feature)
1825
[rabbitmq]
1926
connection_string = "" # RabbitMQ connection string

ldk-server/src/main.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::io::persist::{
2424
FORWARDED_PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE, PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE,
2525
PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE,
2626
};
27-
use crate::util::config::load_config;
27+
use crate::util::config::{load_config, ChainSource};
2828
use crate::util::proto_adapter::{forwarded_payment_to_proto, payment_to_proto};
2929
use hex::DisplayHex;
3030
use ldk_node::config::Config;
@@ -79,14 +79,19 @@ fn main() {
7979
let mut builder = Builder::from_config(ldk_node_config);
8080
builder.set_log_facade_logger();
8181

82-
let bitcoind_rpc_addr = config_file.bitcoind_rpc_addr;
83-
84-
builder.set_chain_source_bitcoind_rpc(
85-
bitcoind_rpc_addr.ip().to_string(),
86-
bitcoind_rpc_addr.port(),
87-
config_file.bitcoind_rpc_user,
88-
config_file.bitcoind_rpc_password,
89-
);
82+
match config_file.chain_source {
83+
ChainSource::Rpc { rpc_address, rpc_user, rpc_password } => {
84+
builder.set_chain_source_bitcoind_rpc(
85+
rpc_address.ip().to_string(),
86+
rpc_address.port(),
87+
rpc_user,
88+
rpc_password,
89+
);
90+
},
91+
ChainSource::Esplora { server_url } => {
92+
builder.set_chain_source_esplora(server_url, None);
93+
},
94+
}
9095

9196
// LSPS2 support is highly experimental and for testing purposes only.
9297
#[cfg(feature = "experimental-lsps2-support")]

ldk-server/src/util/config.rs

Lines changed: 132 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@ pub struct Config {
1616
pub network: Network,
1717
pub rest_service_addr: SocketAddr,
1818
pub storage_dir_path: String,
19-
pub bitcoind_rpc_addr: SocketAddr,
20-
pub bitcoind_rpc_user: String,
21-
pub bitcoind_rpc_password: String,
19+
pub chain_source: ChainSource,
2220
pub rabbitmq_connection_string: String,
2321
pub rabbitmq_exchange_name: String,
2422
pub lsps2_service_config: Option<LSPS2ServiceConfig>,
2523
}
2624

25+
#[derive(Debug)]
26+
pub enum ChainSource {
27+
Rpc { rpc_address: SocketAddr, rpc_user: String, rpc_password: String },
28+
Esplora { server_url: String },
29+
}
30+
2731
impl TryFrom<TomlConfig> for Config {
2832
type Error = io::Error;
2933

@@ -42,13 +46,30 @@ impl TryFrom<TomlConfig> for Config {
4246
format!("Invalid rest service address configured: {}", e),
4347
)
4448
})?;
45-
let bitcoind_rpc_addr =
46-
SocketAddr::from_str(&toml_config.bitcoind.rpc_address).map_err(|e| {
47-
io::Error::new(
49+
let chain_source = match (toml_config.esplora, toml_config.bitcoind) {
50+
(Some(EsploraConfig { server_url }), None) => ChainSource::Esplora { server_url },
51+
(None, Some(BitcoindConfig { rpc_address, rpc_user, rpc_password })) => {
52+
let rpc_address = SocketAddr::from_str(&rpc_address).map_err(|e| {
53+
io::Error::new(
54+
io::ErrorKind::InvalidInput,
55+
format!("Invalid bitcoind RPC address configured: {}", e),
56+
)
57+
})?;
58+
ChainSource::Rpc { rpc_address, rpc_user, rpc_password }
59+
},
60+
(Some(_), Some(_)) => {
61+
return Err(io::Error::new(
4862
io::ErrorKind::InvalidInput,
49-
format!("Invalid bitcoind RPC address configured: {}", e),
50-
)
51-
})?;
63+
format!("Must set a single chain source, multiple were configured"),
64+
))
65+
},
66+
(None, None) => {
67+
return Err(io::Error::new(
68+
io::ErrorKind::InvalidInput,
69+
format!("At least one chain source must be set, either bitcoind or esplora"),
70+
))
71+
},
72+
};
5273

5374
let alias = if let Some(alias_str) = toml_config.node.alias {
5475
let mut bytes = [0u8; 32];
@@ -97,9 +118,7 @@ impl TryFrom<TomlConfig> for Config {
97118
alias,
98119
rest_service_addr,
99120
storage_dir_path: toml_config.storage.disk.dir_path,
100-
bitcoind_rpc_addr,
101-
bitcoind_rpc_user: toml_config.bitcoind.rpc_user,
102-
bitcoind_rpc_password: toml_config.bitcoind.rpc_password,
121+
chain_source,
103122
rabbitmq_connection_string,
104123
rabbitmq_exchange_name,
105124
lsps2_service_config,
@@ -112,7 +131,8 @@ impl TryFrom<TomlConfig> for Config {
112131
pub struct TomlConfig {
113132
node: NodeConfig,
114133
storage: StorageConfig,
115-
bitcoind: BitcoindConfig,
134+
bitcoind: Option<BitcoindConfig>,
135+
esplora: Option<EsploraConfig>,
116136
rabbitmq: Option<RabbitmqConfig>,
117137
liquidity: Option<LiquidityConfig>,
118138
}
@@ -142,6 +162,11 @@ struct BitcoindConfig {
142162
rpc_password: String,
143163
}
144164

165+
#[derive(Deserialize, Serialize)]
166+
struct EsploraConfig {
167+
server_url: String,
168+
}
169+
145170
#[derive(Deserialize, Serialize)]
146171
struct RabbitmqConfig {
147172
connection_string: String,
@@ -233,10 +258,8 @@ mod tests {
233258
[storage.disk]
234259
dir_path = "/tmp"
235260
236-
[bitcoind]
237-
rpc_address = "127.0.0.1:8332" # RPC endpoint
238-
rpc_user = "bitcoind-testuser"
239-
rpc_password = "bitcoind-testpassword"
261+
[esplora]
262+
server_url = "https://mempool.space/api"
240263
241264
[rabbitmq]
242265
connection_string = "rabbitmq_connection_string"
@@ -266,9 +289,9 @@ mod tests {
266289
network: Network::Regtest,
267290
rest_service_addr: SocketAddr::from_str("127.0.0.1:3002").unwrap(),
268291
storage_dir_path: "/tmp".to_string(),
269-
bitcoind_rpc_addr: SocketAddr::from_str("127.0.0.1:8332").unwrap(),
270-
bitcoind_rpc_user: "bitcoind-testuser".to_string(),
271-
bitcoind_rpc_password: "bitcoind-testpassword".to_string(),
292+
chain_source: ChainSource::Esplora {
293+
server_url: String::from("https://mempool.space/api"),
294+
},
272295
rabbitmq_connection_string: "rabbitmq_connection_string".to_string(),
273296
rabbitmq_exchange_name: "rabbitmq_exchange_name".to_string(),
274297
lsps2_service_config: Some(LSPS2ServiceConfig {
@@ -288,12 +311,98 @@ mod tests {
288311
assert_eq!(config.network, expected.network);
289312
assert_eq!(config.rest_service_addr, expected.rest_service_addr);
290313
assert_eq!(config.storage_dir_path, expected.storage_dir_path);
291-
assert_eq!(config.bitcoind_rpc_addr, expected.bitcoind_rpc_addr);
292-
assert_eq!(config.bitcoind_rpc_user, expected.bitcoind_rpc_user);
293-
assert_eq!(config.bitcoind_rpc_password, expected.bitcoind_rpc_password);
314+
let ChainSource::Esplora { server_url } = config.chain_source else {
315+
panic!("unexpected config chain source");
316+
};
317+
let ChainSource::Esplora { server_url: expected_server_url } = expected.chain_source else {
318+
panic!("unexpected chain source");
319+
};
320+
assert_eq!(server_url, expected_server_url);
294321
assert_eq!(config.rabbitmq_connection_string, expected.rabbitmq_connection_string);
295322
assert_eq!(config.rabbitmq_exchange_name, expected.rabbitmq_exchange_name);
296323
#[cfg(feature = "experimental-lsps2-support")]
297324
assert_eq!(config.lsps2_service_config.is_some(), expected.lsps2_service_config.is_some());
325+
326+
// Test case where only bitcoind is set
327+
328+
let toml_config = r#"
329+
[node]
330+
network = "regtest"
331+
listening_address = "localhost:3001"
332+
rest_service_address = "127.0.0.1:3002"
333+
alias = "LDK Server"
334+
335+
[storage.disk]
336+
dir_path = "/tmp"
337+
338+
[bitcoind]
339+
rpc_address = "127.0.0.1:8332" # RPC endpoint
340+
rpc_user = "bitcoind-testuser"
341+
rpc_password = "bitcoind-testpassword"
342+
343+
[rabbitmq]
344+
connection_string = "rabbitmq_connection_string"
345+
exchange_name = "rabbitmq_exchange_name"
346+
347+
[liquidity.lsps2_service]
348+
advertise_service = false
349+
channel_opening_fee_ppm = 1000 # 0.1% fee
350+
channel_over_provisioning_ppm = 500000 # 50% extra capacity
351+
min_channel_opening_fee_msat = 10000000 # 10,000 satoshis
352+
min_channel_lifetime = 4320 # ~30 days
353+
max_client_to_self_delay = 1440 # ~10 days
354+
min_payment_size_msat = 10000000 # 10,000 satoshis
355+
max_payment_size_msat = 25000000000 # 0.25 BTC
356+
"#;
357+
358+
fs::write(storage_path.join(config_file_name), toml_config).unwrap();
359+
let config = load_config(storage_path.join(config_file_name)).unwrap();
360+
361+
let ChainSource::Rpc { rpc_address, rpc_user, rpc_password } = config.chain_source else {
362+
panic!("unexpected chain source");
363+
};
364+
365+
assert_eq!(rpc_address, SocketAddr::from_str("127.0.0.1:8332").unwrap());
366+
assert_eq!(rpc_user, "bitcoind-testuser");
367+
assert_eq!(rpc_password, "bitcoind-testpassword");
368+
369+
// Test case where both bitcoind and esplora are set, resulting in an error
370+
371+
let toml_config = r#"
372+
[node]
373+
network = "regtest"
374+
listening_address = "localhost:3001"
375+
rest_service_address = "127.0.0.1:3002"
376+
alias = "LDK Server"
377+
378+
[storage.disk]
379+
dir_path = "/tmp"
380+
381+
[bitcoind]
382+
rpc_address = "127.0.0.1:8332" # RPC endpoint
383+
rpc_user = "bitcoind-testuser"
384+
rpc_password = "bitcoind-testpassword"
385+
386+
[esplora]
387+
server_url = "https://mempool.space/api"
388+
389+
[rabbitmq]
390+
connection_string = "rabbitmq_connection_string"
391+
exchange_name = "rabbitmq_exchange_name"
392+
393+
[liquidity.lsps2_service]
394+
advertise_service = false
395+
channel_opening_fee_ppm = 1000 # 0.1% fee
396+
channel_over_provisioning_ppm = 500000 # 50% extra capacity
397+
min_channel_opening_fee_msat = 10000000 # 10,000 satoshis
398+
min_channel_lifetime = 4320 # ~30 days
399+
max_client_to_self_delay = 1440 # ~10 days
400+
min_payment_size_msat = 10000000 # 10,000 satoshis
401+
max_payment_size_msat = 25000000000 # 0.25 BTC
402+
"#;
403+
404+
fs::write(storage_path.join(config_file_name), toml_config).unwrap();
405+
let error = load_config(storage_path.join(config_file_name)).unwrap_err();
406+
assert_eq!(error.to_string(), "Must set a single chain source, multiple were configured");
298407
}
299408
}

0 commit comments

Comments
 (0)