Skip to content

Commit 0799c2a

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 e8a5562 commit 0799c2a

File tree

7 files changed

+230
-4
lines changed

7 files changed

+230
-4
lines changed

bindings/ldk_node.udl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ dictionary EsploraSyncConfig {
2525
u64 fee_rate_cache_update_interval_secs;
2626
};
2727

28+
dictionary ElectrumSyncConfig {
29+
u64 onchain_wallet_sync_interval_secs;
30+
u64 lightning_wallet_sync_interval_secs;
31+
u64 fee_rate_cache_update_interval_secs;
32+
};
33+
2834
dictionary LSPS2ServiceConfig {
2935
string? require_token;
3036
boolean advertise_service;
@@ -67,6 +73,7 @@ interface Builder {
6773
void set_entropy_seed_bytes(sequence<u8> seed_bytes);
6874
void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase);
6975
void set_chain_source_esplora(string server_url, EsploraSyncConfig? config);
76+
void set_chain_source_electrum(string server_url, ElectrumSyncConfig? config);
7077
void set_chain_source_bitcoind_rpc(string rpc_host, u16 rpc_port, string rpc_user, string rpc_password);
7178
void set_gossip_source_p2p();
7279
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()),
@@ -260,6 +330,7 @@ impl ChainSource {
260330
}
261331
}
262332
},
333+
Self::Electrum { .. } => todo!(),
263334
Self::BitcoindRpc {
264335
bitcoind_rpc_client,
265336
header_cache,
@@ -527,6 +598,7 @@ impl ChainSource {
527598

528599
res
529600
},
601+
Self::Electrum { .. } => todo!(),
530602
Self::BitcoindRpc { .. } => {
531603
// In BitcoindRpc mode we sync lightning and onchain wallet in one go by via
532604
// `ChainPoller`. So nothing to do here.
@@ -626,6 +698,7 @@ impl ChainSource {
626698

627699
res
628700
},
701+
Self::Electrum { .. } => todo!(),
629702
Self::BitcoindRpc { .. } => {
630703
// In BitcoindRpc mode we sync lightning and onchain wallet in one go by via
631704
// `ChainPoller`. So nothing to do here.
@@ -644,6 +717,11 @@ impl ChainSource {
644717
// `sync_onchain_wallet` and `sync_lightning_wallet`. So nothing to do here.
645718
unreachable!("Listeners will be synced via transction-based syncing")
646719
},
720+
Self::Electrum { .. } => {
721+
// In Electrum mode we sync lightning and onchain wallets via
722+
// `sync_onchain_wallet` and `sync_lightning_wallet`. So nothing to do here.
723+
unreachable!("Listeners will be synced via transction-based syncing")
724+
},
647725
Self::BitcoindRpc {
648726
bitcoind_rpc_client,
649727
header_cache,
@@ -864,6 +942,7 @@ impl ChainSource {
864942

865943
Ok(())
866944
},
945+
Self::Electrum { .. } => todo!(),
867946
Self::BitcoindRpc {
868947
bitcoind_rpc_client,
869948
fee_estimator,
@@ -1074,6 +1153,7 @@ impl ChainSource {
10741153
}
10751154
}
10761155
},
1156+
Self::Electrum { .. } => todo!(),
10771157
Self::BitcoindRpc { bitcoind_rpc_client, tx_broadcaster, logger, .. } => {
10781158
// While it's a bit unclear when we'd be able to lean on Bitcoin Core >v28
10791159
// features, we should eventually switch to use `submitpackage` via the
@@ -1136,12 +1216,14 @@ impl Filter for ChainSource {
11361216
fn register_tx(&self, txid: &bitcoin::Txid, script_pubkey: &bitcoin::Script) {
11371217
match self {
11381218
Self::Esplora { tx_sync, .. } => tx_sync.register_tx(txid, script_pubkey),
1219+
Self::Electrum { .. } => todo!(),
11391220
Self::BitcoindRpc { .. } => (),
11401221
}
11411222
}
11421223
fn register_output(&self, output: lightning::chain::WatchedOutput) {
11431224
match self {
11441225
Self::Esplora { tx_sync, .. } => tx_sync.register_output(output),
1226+
Self::Electrum { .. } => todo!(),
11451227
Self::BitcoindRpc { .. } => (),
11461228
}
11471229
}

src/config.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,41 @@ impl Default for EsploraSyncConfig {
317317
}
318318
}
319319

320+
/// Options related to syncing the Lightning and on-chain wallets via an Electrum backend.
321+
///
322+
/// ### Defaults
323+
///
324+
/// | Parameter | Value |
325+
/// |----------------------------------------|--------------------|
326+
/// | `onchain_wallet_sync_interval_secs` | 80 |
327+
/// | `lightning_wallet_sync_interval_secs` | 30 |
328+
/// | `fee_rate_cache_update_interval_secs` | 600 |
329+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
330+
pub struct ElectrumSyncConfig {
331+
/// The time in-between background sync attempts of the onchain wallet, in seconds.
332+
///
333+
/// **Note:** A minimum of 10 seconds is always enforced.
334+
pub onchain_wallet_sync_interval_secs: u64,
335+
/// The time in-between background sync attempts of the LDK wallet, in seconds.
336+
///
337+
/// **Note:** A minimum of 10 seconds is always enforced.
338+
pub lightning_wallet_sync_interval_secs: u64,
339+
/// The time in-between background update attempts to our fee rate cache, in seconds.
340+
///
341+
/// **Note:** A minimum of 10 seconds is always enforced.
342+
pub fee_rate_cache_update_interval_secs: u64,
343+
}
344+
345+
impl Default for ElectrumSyncConfig {
346+
fn default() -> Self {
347+
Self {
348+
onchain_wallet_sync_interval_secs: DEFAULT_BDK_WALLET_SYNC_INTERVAL_SECS,
349+
lightning_wallet_sync_interval_secs: DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS,
350+
fee_rate_cache_update_interval_secs: DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS,
351+
}
352+
}
353+
}
354+
320355
/// Options which apply on a per-channel basis and may change at runtime or based on negotiation
321356
/// with our counterparty.
322357
#[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(_) => (),
@@ -1248,6 +1257,13 @@ impl Node {
12481257
.await?;
12491258
chain_source.sync_onchain_wallet().await?;
12501259
},
1260+
ChainSource::Electrum { .. } => {
1261+
chain_source.update_fee_rate_estimates().await?;
1262+
chain_source
1263+
.sync_lightning_wallet(sync_cman, sync_cmon, sync_sweeper)
1264+
.await?;
1265+
chain_source.sync_onchain_wallet().await?;
1266+
},
12511267
ChainSource::BitcoindRpc { .. } => {
12521268
chain_source.update_fee_rate_estimates().await?;
12531269
chain_source

0 commit comments

Comments
 (0)