Skip to content

Commit 50cada3

Browse files
committed
Allow to configure ChainSource::Electrum via builder
We here setup the basic API and structure for the `ChainSource::Electrum`. Currently, we won't have a `Runtime` available when initializing `ChainSource::Electrum` in `Builder::build`. We therefore isolate any runtime-specific behavior into an `ElectrumRuntimeClient`. This might change in the future, but for now we do need this workaround.
1 parent 35865d9 commit 50cada3

File tree

7 files changed

+215
-6
lines changed

7 files changed

+215
-6
lines changed

bindings/ldk_node.udl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ dictionary EsploraSyncConfig {
2929
BackgroundSyncConfig? background_sync_config;
3030
};
3131

32+
dictionary ElectrumSyncConfig {
33+
BackgroundSyncConfig? background_sync_config;
34+
};
35+
3236
dictionary LSPS2ServiceConfig {
3337
string? require_token;
3438
boolean advertise_service;
@@ -71,6 +75,7 @@ interface Builder {
7175
void set_entropy_seed_bytes(sequence<u8> seed_bytes);
7276
void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase);
7377
void set_chain_source_esplora(string server_url, EsploraSyncConfig? config);
78+
void set_chain_source_electrum(string server_url, ElectrumSyncConfig? config);
7479
void set_chain_source_bitcoind_rpc(string rpc_host, u16 rpc_port, string rpc_user, string rpc_password);
7580
void set_gossip_source_p2p();
7681
void set_gossip_source_rgs(string rgs_server_url);

src/builder.rs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
use crate::chain::{ChainSource, DEFAULT_ESPLORA_SERVER_URL};
99
use crate::config::{
10-
default_user_config, Config, EsploraSyncConfig, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL,
11-
WALLET_KEYS_SEED_LEN,
10+
default_user_config, Config, ElectrumSyncConfig, EsploraSyncConfig, DEFAULT_LOG_FILENAME,
11+
DEFAULT_LOG_LEVEL, WALLET_KEYS_SEED_LEN,
1212
};
1313

1414
use crate::connection::ConnectionManager;
@@ -84,6 +84,7 @@ const LSPS_HARDENED_CHILD_INDEX: u32 = 577;
8484
#[derive(Debug, Clone)]
8585
enum ChainDataSourceConfig {
8686
Esplora { server_url: String, sync_config: Option<EsploraSyncConfig> },
87+
Electrum { server_url: String, sync_config: Option<ElectrumSyncConfig> },
8788
BitcoindRpc { rpc_host: String, rpc_port: u16, rpc_user: String, rpc_password: String },
8889
}
8990

@@ -280,6 +281,18 @@ impl NodeBuilder {
280281
self
281282
}
282283

284+
/// Configures the [`Node`] instance to source its chain data from the given Electrum server.
285+
///
286+
/// If no `sync_config` is given, default values are used. See [`ElectrumSyncConfig`] for more
287+
/// information.
288+
pub fn set_chain_source_electrum(
289+
&mut self, server_url: String, sync_config: Option<ElectrumSyncConfig>,
290+
) -> &mut Self {
291+
self.chain_data_source_config =
292+
Some(ChainDataSourceConfig::Electrum { server_url, sync_config });
293+
self
294+
}
295+
283296
/// Configures the [`Node`] instance to source its chain data from the given Bitcoin Core RPC
284297
/// endpoint.
285298
pub fn set_chain_source_bitcoind_rpc(
@@ -671,6 +684,16 @@ impl ArcedNodeBuilder {
671684
self.inner.write().unwrap().set_chain_source_esplora(server_url, sync_config);
672685
}
673686

687+
/// Configures the [`Node`] instance to source its chain data from the given Electrum server.
688+
///
689+
/// If no `sync_config` is given, default values are used. See [`ElectrumSyncConfig`] for more
690+
/// information.
691+
pub fn set_chain_source_electrum(
692+
&self, server_url: String, sync_config: Option<ElectrumSyncConfig>,
693+
) {
694+
self.inner.write().unwrap().set_chain_source_electrum(server_url, sync_config);
695+
}
696+
674697
/// Configures the [`Node`] instance to source its chain data from the given Bitcoin Core RPC
675698
/// endpoint.
676699
pub fn set_chain_source_bitcoind_rpc(
@@ -976,6 +999,20 @@ fn build_with_store_internal(
976999
Arc::clone(&node_metrics),
9771000
))
9781001
},
1002+
Some(ChainDataSourceConfig::Electrum { server_url, sync_config }) => {
1003+
let sync_config = sync_config.unwrap_or(ElectrumSyncConfig::default());
1004+
Arc::new(ChainSource::new_electrum(
1005+
server_url.clone(),
1006+
sync_config,
1007+
Arc::clone(&wallet),
1008+
Arc::clone(&fee_estimator),
1009+
Arc::clone(&tx_broadcaster),
1010+
Arc::clone(&kv_store),
1011+
Arc::clone(&config),
1012+
Arc::clone(&logger),
1013+
Arc::clone(&node_metrics),
1014+
))
1015+
},
9791016
Some(ChainDataSourceConfig::BitcoindRpc { rpc_host, rpc_port, rpc_user, rpc_password }) => {
9801017
Arc::new(ChainSource::new_bitcoind_rpc(
9811018
rpc_host.clone(),

src/chain/electrum.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// This file is Copyright its original authors, visible in version control history.
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5+
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6+
// accordance with one or both of these licenses.
7+
8+
use crate::error::Error;
9+
use crate::logger::{log_error, LdkLogger, Logger};
10+
11+
use lightning_transaction_sync::ElectrumSyncClient;
12+
13+
use bdk_electrum::BdkElectrumClient;
14+
15+
use electrum_client::Client as ElectrumClient;
16+
17+
use std::sync::Arc;
18+
19+
pub(crate) struct ElectrumRuntimeClient {
20+
electrum_client: Arc<ElectrumClient>,
21+
bdk_electrum_client: Arc<BdkElectrumClient<ElectrumClient>>,
22+
tx_sync: Arc<ElectrumSyncClient<Arc<Logger>>>,
23+
runtime: Arc<tokio::runtime::Runtime>,
24+
logger: Arc<Logger>,
25+
}
26+
27+
impl ElectrumRuntimeClient {
28+
pub(crate) fn new(
29+
server_url: String, runtime: Arc<tokio::runtime::Runtime>, logger: Arc<Logger>,
30+
) -> Result<Self, Error> {
31+
let electrum_client = Arc::new(ElectrumClient::new(&server_url).map_err(|e| {
32+
log_error!(logger, "Failed to connect to electrum server: {}", e);
33+
Error::ConnectionFailed
34+
})?);
35+
let electrum_client_2 = ElectrumClient::new(&server_url).map_err(|e| {
36+
log_error!(logger, "Failed to connect to electrum server: {}", e);
37+
Error::ConnectionFailed
38+
})?;
39+
let bdk_electrum_client = Arc::new(BdkElectrumClient::new(electrum_client_2));
40+
let tx_sync = Arc::new(
41+
ElectrumSyncClient::new(server_url.clone(), Arc::clone(&logger)).map_err(|e| {
42+
log_error!(logger, "Failed to connect to electrum server: {}", e);
43+
Error::ConnectionFailed
44+
})?,
45+
);
46+
Ok(Self { electrum_client, bdk_electrum_client, tx_sync, runtime, logger })
47+
}
48+
}

src/chain/mod.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
// accordance with one or both of these licenses.
77

88
mod bitcoind_rpc;
9+
mod electrum;
910

1011
use crate::chain::bitcoind_rpc::{
1112
BitcoindRpcClient, BoundedHeaderCache, ChainListener, FeeRateEstimationMode,
1213
};
14+
use crate::chain::electrum::ElectrumRuntimeClient;
1315
use crate::config::{
14-
Config, EsploraSyncConfig, BDK_CLIENT_CONCURRENCY, BDK_CLIENT_STOP_GAP,
16+
Config, ElectrumSyncConfig, EsploraSyncConfig, BDK_CLIENT_CONCURRENCY, BDK_CLIENT_STOP_GAP,
1517
BDK_WALLET_SYNC_TIMEOUT_SECS, FEE_RATE_CACHE_UPDATE_TIMEOUT_SECS, LDK_WALLET_SYNC_TIMEOUT_SECS,
1618
RESOLVED_CHANNEL_MONITOR_ARCHIVAL_INTERVAL, TX_BROADCAST_TIMEOUT_SECS,
1719
WALLET_SYNC_INTERVAL_MINIMUM_SECS,
@@ -122,6 +124,20 @@ pub(crate) enum ChainSource {
122124
logger: Arc<Logger>,
123125
node_metrics: Arc<RwLock<NodeMetrics>>,
124126
},
127+
Electrum {
128+
server_url: String,
129+
sync_config: ElectrumSyncConfig,
130+
electrum_runtime_client: RwLock<Option<ElectrumRuntimeClient>>,
131+
onchain_wallet: Arc<Wallet>,
132+
onchain_wallet_sync_status: Mutex<WalletSyncStatus>,
133+
lightning_wallet_sync_status: Mutex<WalletSyncStatus>,
134+
fee_estimator: Arc<OnchainFeeEstimator>,
135+
tx_broadcaster: Arc<Broadcaster>,
136+
kv_store: Arc<DynStore>,
137+
config: Arc<Config>,
138+
logger: Arc<Logger>,
139+
node_metrics: Arc<RwLock<NodeMetrics>>,
140+
},
125141
BitcoindRpc {
126142
bitcoind_rpc_client: Arc<BitcoindRpcClient>,
127143
header_cache: tokio::sync::Mutex<BoundedHeaderCache>,
@@ -167,6 +183,31 @@ impl ChainSource {
167183
}
168184
}
169185

186+
pub(crate) fn new_electrum(
187+
server_url: String, sync_config: ElectrumSyncConfig, onchain_wallet: Arc<Wallet>,
188+
fee_estimator: Arc<OnchainFeeEstimator>, tx_broadcaster: Arc<Broadcaster>,
189+
kv_store: Arc<DynStore>, config: Arc<Config>, logger: Arc<Logger>,
190+
node_metrics: Arc<RwLock<NodeMetrics>>,
191+
) -> Self {
192+
let electrum_runtime_client = RwLock::new(None);
193+
let onchain_wallet_sync_status = Mutex::new(WalletSyncStatus::Completed);
194+
let lightning_wallet_sync_status = Mutex::new(WalletSyncStatus::Completed);
195+
Self::Electrum {
196+
server_url,
197+
sync_config,
198+
electrum_runtime_client,
199+
onchain_wallet,
200+
onchain_wallet_sync_status,
201+
lightning_wallet_sync_status,
202+
fee_estimator,
203+
tx_broadcaster,
204+
kv_store,
205+
config,
206+
logger,
207+
node_metrics,
208+
}
209+
}
210+
170211
pub(crate) fn new_bitcoind_rpc(
171212
host: String, port: u16, rpc_user: String, rpc_password: String,
172213
onchain_wallet: Arc<Wallet>, fee_estimator: Arc<OnchainFeeEstimator>,
@@ -193,6 +234,35 @@ impl ChainSource {
193234
}
194235
}
195236

237+
pub(crate) fn start(&self, runtime: Arc<tokio::runtime::Runtime>) -> Result<(), Error> {
238+
match self {
239+
Self::Electrum { server_url, electrum_runtime_client, logger, .. } => {
240+
let mut locked_client = electrum_runtime_client.write().unwrap();
241+
*locked_client = Some(ElectrumRuntimeClient::new(
242+
server_url.clone(),
243+
runtime,
244+
Arc::clone(&logger),
245+
)?);
246+
},
247+
_ => {
248+
// Nothing to do for other chain sources.
249+
},
250+
}
251+
Ok(())
252+
}
253+
254+
pub(crate) fn stop(&self) {
255+
match self {
256+
Self::Electrum { electrum_runtime_client, .. } => {
257+
let mut locked_client = electrum_runtime_client.write().unwrap();
258+
*locked_client = None;
259+
},
260+
_ => {
261+
// Nothing to do for other chain sources.
262+
},
263+
}
264+
}
265+
196266
pub(crate) fn as_utxo_source(&self) -> Option<Arc<dyn UtxoSource>> {
197267
match self {
198268
Self::BitcoindRpc { bitcoind_rpc_client, .. } => Some(bitcoind_rpc_client.rpc_client()),
@@ -271,6 +341,7 @@ impl ChainSource {
271341
return;
272342
}
273343
},
344+
Self::Electrum { .. } => todo!(),
274345
Self::BitcoindRpc {
275346
bitcoind_rpc_client,
276347
header_cache,
@@ -538,6 +609,7 @@ impl ChainSource {
538609

539610
res
540611
},
612+
Self::Electrum { .. } => todo!(),
541613
Self::BitcoindRpc { .. } => {
542614
// In BitcoindRpc mode we sync lightning and onchain wallet in one go by via
543615
// `ChainPoller`. So nothing to do here.
@@ -637,6 +709,7 @@ impl ChainSource {
637709

638710
res
639711
},
712+
Self::Electrum { .. } => todo!(),
640713
Self::BitcoindRpc { .. } => {
641714
// In BitcoindRpc mode we sync lightning and onchain wallet in one go by via
642715
// `ChainPoller`. So nothing to do here.
@@ -655,6 +728,11 @@ impl ChainSource {
655728
// `sync_onchain_wallet` and `sync_lightning_wallet`. So nothing to do here.
656729
unreachable!("Listeners will be synced via transction-based syncing")
657730
},
731+
Self::Electrum { .. } => {
732+
// In Electrum mode we sync lightning and onchain wallets via
733+
// `sync_onchain_wallet` and `sync_lightning_wallet`. So nothing to do here.
734+
unreachable!("Listeners will be synced via transction-based syncing")
735+
},
658736
Self::BitcoindRpc {
659737
bitcoind_rpc_client,
660738
header_cache,
@@ -875,6 +953,7 @@ impl ChainSource {
875953

876954
Ok(())
877955
},
956+
Self::Electrum { .. } => todo!(),
878957
Self::BitcoindRpc {
879958
bitcoind_rpc_client,
880959
fee_estimator,
@@ -1085,6 +1164,7 @@ impl ChainSource {
10851164
}
10861165
}
10871166
},
1167+
Self::Electrum { .. } => todo!(),
10881168
Self::BitcoindRpc { bitcoind_rpc_client, tx_broadcaster, logger, .. } => {
10891169
// While it's a bit unclear when we'd be able to lean on Bitcoin Core >v28
10901170
// features, we should eventually switch to use `submitpackage` via the
@@ -1147,12 +1227,14 @@ impl Filter for ChainSource {
11471227
fn register_tx(&self, txid: &bitcoin::Txid, script_pubkey: &bitcoin::Script) {
11481228
match self {
11491229
Self::Esplora { tx_sync, .. } => tx_sync.register_tx(txid, script_pubkey),
1230+
Self::Electrum { .. } => todo!(),
11501231
Self::BitcoindRpc { .. } => (),
11511232
}
11521233
}
11531234
fn register_output(&self, output: lightning::chain::WatchedOutput) {
11541235
match self {
11551236
Self::Esplora { tx_sync, .. } => tx_sync.register_output(output),
1237+
Self::Electrum { .. } => todo!(),
11561238
Self::BitcoindRpc { .. } => (),
11571239
}
11581240
}

src/config.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ pub struct EsploraSyncConfig {
328328
/// Background sync configuration.
329329
///
330330
/// If set to `None`, background syncing will be disabled. Users will need to manually
331-
/// sync via `Node::sync_wallets` for the wallets and fee rate updates.
331+
/// sync via [`Node::sync_wallets`] for the wallets and fee rate updates.
332332
///
333333
/// [`Node::sync_wallets`]: crate::Node::sync_wallets
334334
pub background_sync_config: Option<BackgroundSyncConfig>,
@@ -340,6 +340,27 @@ impl Default for EsploraSyncConfig {
340340
}
341341
}
342342

343+
/// Configuration for syncing with an Electrum backend.
344+
///
345+
/// Background syncing is enabled by default, using the default values specified in
346+
/// [`BackgroundSyncConfig`].
347+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
348+
pub struct ElectrumSyncConfig {
349+
/// Background sync configuration.
350+
///
351+
/// If set to `None`, background syncing will be disabled. Users will need to manually
352+
/// sync via [`Node::sync_wallets`] for the wallets and fee rate updates.
353+
///
354+
/// [`Node::sync_wallets`]: crate::Node::sync_wallets
355+
pub background_sync_config: Option<BackgroundSyncConfig>,
356+
}
357+
358+
impl Default for ElectrumSyncConfig {
359+
fn default() -> Self {
360+
Self { background_sync_config: Some(BackgroundSyncConfig::default()) }
361+
}
362+
}
363+
343364
/// Options which apply on a per-channel basis and may change at runtime or based on negotiation
344365
/// with our counterparty.
345366
#[derive(Copy, Clone, Debug, PartialEq, Eq)]

src/lib.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,12 @@ impl Node {
236236
self.config.network
237237
);
238238

239+
// Start up any runtime-dependant chain sources (e.g. Electrum)
240+
self.chain_source.start(Arc::clone(&runtime)).map_err(|e| {
241+
log_error!(self.logger, "Failed to start chain syncing: {}", e);
242+
e
243+
})?;
244+
239245
// Block to ensure we update our fee rate cache once on startup
240246
let chain_source = Arc::clone(&self.chain_source);
241247
let runtime_ref = &runtime;
@@ -638,6 +644,9 @@ impl Node {
638644

639645
log_info!(self.logger, "Shutting down LDK Node with node ID {}...", self.node_id());
640646

647+
// Stop any runtime-dependant chain sources.
648+
self.chain_source.stop();
649+
641650
// Stop the runtime.
642651
match self.stop_sender.send(()) {
643652
Ok(_) => (),
@@ -1247,6 +1256,13 @@ impl Node {
12471256
.await?;
12481257
chain_source.sync_onchain_wallet().await?;
12491258
},
1259+
ChainSource::Electrum { .. } => {
1260+
chain_source.update_fee_rate_estimates().await?;
1261+
chain_source
1262+
.sync_lightning_wallet(sync_cman, sync_cmon, sync_sweeper)
1263+
.await?;
1264+
chain_source.sync_onchain_wallet().await?;
1265+
},
12501266
ChainSource::BitcoindRpc { .. } => {
12511267
chain_source.update_fee_rate_estimates().await?;
12521268
chain_source

0 commit comments

Comments
 (0)