Skip to content

Commit e793b6f

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 274a21f commit e793b6f

File tree

7 files changed

+264
-6
lines changed

7 files changed

+264
-6
lines changed

bindings/ldk_node.udl

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

33+
dictionary ElectrumSyncConfig {
34+
BackgroundSyncConfig? background_sync_config;
35+
};
36+
3337
dictionary LSPS2ServiceConfig {
3438
string? require_token;
3539
boolean advertise_service;
@@ -72,6 +76,7 @@ interface Builder {
7276
void set_entropy_seed_bytes(sequence<u8> seed_bytes);
7377
void set_entropy_bip39_mnemonic(Mnemonic mnemonic, string? passphrase);
7478
void set_chain_source_esplora(string server_url, EsploraSyncConfig? config);
79+
void set_chain_source_electrum(string server_url, ElectrumSyncConfig? config);
7580
void set_chain_source_bitcoind_rpc(string rpc_host, u16 rpc_port, string rpc_user, string rpc_password);
7681
void set_gossip_source_p2p();
7782
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, may_announce_channel, AnnounceError, Config, EsploraSyncConfig,
11-
DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL, WALLET_KEYS_SEED_LEN,
10+
default_user_config, may_announce_channel, AnnounceError, Config, ElectrumSyncConfig,
11+
EsploraSyncConfig, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL, WALLET_KEYS_SEED_LEN,
1212
};
1313

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

@@ -284,6 +285,18 @@ impl NodeBuilder {
284285
self
285286
}
286287

288+
/// Configures the [`Node`] instance to source its chain data from the given Electrum server.
289+
///
290+
/// If no `sync_config` is given, default values are used. See [`ElectrumSyncConfig`] for more
291+
/// information.
292+
pub fn set_chain_source_electrum(
293+
&mut self, server_url: String, sync_config: Option<ElectrumSyncConfig>,
294+
) -> &mut Self {
295+
self.chain_data_source_config =
296+
Some(ChainDataSourceConfig::Electrum { server_url, sync_config });
297+
self
298+
}
299+
287300
/// Configures the [`Node`] instance to source its chain data from the given Bitcoin Core RPC
288301
/// endpoint.
289302
pub fn set_chain_source_bitcoind_rpc(
@@ -691,6 +704,16 @@ impl ArcedNodeBuilder {
691704
self.inner.write().unwrap().set_chain_source_esplora(server_url, sync_config);
692705
}
693706

707+
/// Configures the [`Node`] instance to source its chain data from the given Electrum server.
708+
///
709+
/// If no `sync_config` is given, default values are used. See [`ElectrumSyncConfig`] for more
710+
/// information.
711+
pub fn set_chain_source_electrum(
712+
&self, server_url: String, sync_config: Option<ElectrumSyncConfig>,
713+
) {
714+
self.inner.write().unwrap().set_chain_source_electrum(server_url, sync_config);
715+
}
716+
694717
/// Configures the [`Node`] instance to source its chain data from the given Bitcoin Core RPC
695718
/// endpoint.
696719
pub fn set_chain_source_bitcoind_rpc(
@@ -1024,6 +1047,20 @@ fn build_with_store_internal(
10241047
Arc::clone(&node_metrics),
10251048
))
10261049
},
1050+
Some(ChainDataSourceConfig::Electrum { server_url, sync_config }) => {
1051+
let sync_config = sync_config.unwrap_or(ElectrumSyncConfig::default());
1052+
Arc::new(ChainSource::new_electrum(
1053+
server_url.clone(),
1054+
sync_config,
1055+
Arc::clone(&wallet),
1056+
Arc::clone(&fee_estimator),
1057+
Arc::clone(&tx_broadcaster),
1058+
Arc::clone(&kv_store),
1059+
Arc::clone(&config),
1060+
Arc::clone(&logger),
1061+
Arc::clone(&node_metrics),
1062+
))
1063+
},
10271064
Some(ChainDataSourceConfig::BitcoindRpc { rpc_host, rpc_port, rpc_user, rpc_password }) => {
10281065
Arc::new(ChainSource::new_bitcoind_rpc(
10291066
rpc_host.clone(),

src/chain/electrum.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
use electrum_client::ConfigBuilder as ElectrumConfigBuilder;
17+
18+
use std::sync::Arc;
19+
20+
const ELECTRUM_CLIENT_NUM_RETRIES: u8 = 3;
21+
const ELECTRUM_CLIENT_TIMEOUT_SECS: u8 = 20;
22+
23+
pub(crate) struct ElectrumRuntimeClient {
24+
electrum_client: Arc<ElectrumClient>,
25+
bdk_electrum_client: Arc<BdkElectrumClient<ElectrumClient>>,
26+
tx_sync: Arc<ElectrumSyncClient<Arc<Logger>>>,
27+
runtime: Arc<tokio::runtime::Runtime>,
28+
logger: Arc<Logger>,
29+
}
30+
31+
impl ElectrumRuntimeClient {
32+
pub(crate) fn new(
33+
server_url: String, runtime: Arc<tokio::runtime::Runtime>, logger: Arc<Logger>,
34+
) -> Result<Self, Error> {
35+
let electrum_config = ElectrumConfigBuilder::new()
36+
.retry(ELECTRUM_CLIENT_NUM_RETRIES)
37+
.timeout(Some(ELECTRUM_CLIENT_TIMEOUT_SECS))
38+
.build();
39+
40+
let electrum_client = Arc::new(
41+
ElectrumClient::from_config(&server_url, electrum_config.clone()).map_err(|e| {
42+
log_error!(logger, "Failed to connect to electrum server: {}", e);
43+
Error::ConnectionFailed
44+
})?,
45+
);
46+
let electrum_client_2 =
47+
ElectrumClient::from_config(&server_url, electrum_config).map_err(|e| {
48+
log_error!(logger, "Failed to connect to electrum server: {}", e);
49+
Error::ConnectionFailed
50+
})?;
51+
let bdk_electrum_client = Arc::new(BdkElectrumClient::new(electrum_client_2));
52+
let tx_sync = Arc::new(
53+
ElectrumSyncClient::new(server_url.clone(), Arc::clone(&logger)).map_err(|e| {
54+
log_error!(logger, "Failed to connect to electrum server: {}", e);
55+
Error::ConnectionFailed
56+
})?,
57+
);
58+
Ok(Self { electrum_client, bdk_electrum_client, tx_sync, runtime, logger })
59+
}
60+
}

src/chain/mod.rs

Lines changed: 120 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,
@@ -107,6 +109,45 @@ impl WalletSyncStatus {
107109
}
108110
}
109111

112+
pub(crate) enum ElectrumRuntimeStatus {
113+
Started(Arc<ElectrumRuntimeClient>),
114+
Stopped,
115+
}
116+
117+
impl ElectrumRuntimeStatus {
118+
pub(crate) fn new() -> Self {
119+
Self::Stopped
120+
}
121+
122+
pub(crate) fn start(
123+
&mut self, server_url: String, runtime: Arc<tokio::runtime::Runtime>, logger: Arc<Logger>,
124+
) -> Result<(), Error> {
125+
match self {
126+
Self::Stopped => {
127+
let client =
128+
Arc::new(ElectrumRuntimeClient::new(server_url.clone(), runtime, logger)?);
129+
130+
*self = Self::Started(client);
131+
},
132+
Self::Started(_) => {
133+
debug_assert!(false, "We shouldn't call start if we're already started")
134+
},
135+
}
136+
Ok(())
137+
}
138+
139+
pub(crate) fn stop(&mut self) {
140+
*self = Self::new()
141+
}
142+
143+
pub(crate) fn client(&self) -> Option<&Arc<ElectrumRuntimeClient>> {
144+
match self {
145+
Self::Started(client) => Some(&client),
146+
Self::Stopped { .. } => None,
147+
}
148+
}
149+
}
150+
110151
pub(crate) enum ChainSource {
111152
Esplora {
112153
sync_config: EsploraSyncConfig,
@@ -122,6 +163,20 @@ pub(crate) enum ChainSource {
122163
logger: Arc<Logger>,
123164
node_metrics: Arc<RwLock<NodeMetrics>>,
124165
},
166+
Electrum {
167+
server_url: String,
168+
sync_config: ElectrumSyncConfig,
169+
electrum_runtime_status: RwLock<ElectrumRuntimeStatus>,
170+
onchain_wallet: Arc<Wallet>,
171+
onchain_wallet_sync_status: Mutex<WalletSyncStatus>,
172+
lightning_wallet_sync_status: Mutex<WalletSyncStatus>,
173+
fee_estimator: Arc<OnchainFeeEstimator>,
174+
tx_broadcaster: Arc<Broadcaster>,
175+
kv_store: Arc<DynStore>,
176+
config: Arc<Config>,
177+
logger: Arc<Logger>,
178+
node_metrics: Arc<RwLock<NodeMetrics>>,
179+
},
125180
BitcoindRpc {
126181
bitcoind_rpc_client: Arc<BitcoindRpcClient>,
127182
header_cache: tokio::sync::Mutex<BoundedHeaderCache>,
@@ -167,6 +222,31 @@ impl ChainSource {
167222
}
168223
}
169224

225+
pub(crate) fn new_electrum(
226+
server_url: String, sync_config: ElectrumSyncConfig, onchain_wallet: Arc<Wallet>,
227+
fee_estimator: Arc<OnchainFeeEstimator>, tx_broadcaster: Arc<Broadcaster>,
228+
kv_store: Arc<DynStore>, config: Arc<Config>, logger: Arc<Logger>,
229+
node_metrics: Arc<RwLock<NodeMetrics>>,
230+
) -> Self {
231+
let electrum_runtime_status = RwLock::new(ElectrumRuntimeStatus::new());
232+
let onchain_wallet_sync_status = Mutex::new(WalletSyncStatus::Completed);
233+
let lightning_wallet_sync_status = Mutex::new(WalletSyncStatus::Completed);
234+
Self::Electrum {
235+
server_url,
236+
sync_config,
237+
electrum_runtime_status,
238+
onchain_wallet,
239+
onchain_wallet_sync_status,
240+
lightning_wallet_sync_status,
241+
fee_estimator,
242+
tx_broadcaster,
243+
kv_store,
244+
config,
245+
logger,
246+
node_metrics,
247+
}
248+
}
249+
170250
pub(crate) fn new_bitcoind_rpc(
171251
host: String, port: u16, rpc_user: String, rpc_password: String,
172252
onchain_wallet: Arc<Wallet>, fee_estimator: Arc<OnchainFeeEstimator>,
@@ -193,6 +273,33 @@ impl ChainSource {
193273
}
194274
}
195275

276+
pub(crate) fn start(&self, runtime: Arc<tokio::runtime::Runtime>) -> Result<(), Error> {
277+
match self {
278+
Self::Electrum { server_url, electrum_runtime_status, logger, .. } => {
279+
electrum_runtime_status.write().unwrap().start(
280+
server_url.clone(),
281+
runtime,
282+
Arc::clone(&logger),
283+
)?;
284+
},
285+
_ => {
286+
// Nothing to do for other chain sources.
287+
},
288+
}
289+
Ok(())
290+
}
291+
292+
pub(crate) fn stop(&self) {
293+
match self {
294+
Self::Electrum { electrum_runtime_status, .. } => {
295+
electrum_runtime_status.write().unwrap().stop();
296+
},
297+
_ => {
298+
// Nothing to do for other chain sources.
299+
},
300+
}
301+
}
302+
196303
pub(crate) fn as_utxo_source(&self) -> Option<Arc<dyn UtxoSource>> {
197304
match self {
198305
Self::BitcoindRpc { bitcoind_rpc_client, .. } => Some(bitcoind_rpc_client.rpc_client()),
@@ -271,6 +378,7 @@ impl ChainSource {
271378
return;
272379
}
273380
},
381+
Self::Electrum { .. } => todo!(),
274382
Self::BitcoindRpc {
275383
bitcoind_rpc_client,
276384
header_cache,
@@ -538,6 +646,7 @@ impl ChainSource {
538646

539647
res
540648
},
649+
Self::Electrum { .. } => todo!(),
541650
Self::BitcoindRpc { .. } => {
542651
// In BitcoindRpc mode we sync lightning and onchain wallet in one go by via
543652
// `ChainPoller`. So nothing to do here.
@@ -637,6 +746,7 @@ impl ChainSource {
637746

638747
res
639748
},
749+
Self::Electrum { .. } => todo!(),
640750
Self::BitcoindRpc { .. } => {
641751
// In BitcoindRpc mode we sync lightning and onchain wallet in one go by via
642752
// `ChainPoller`. So nothing to do here.
@@ -655,6 +765,11 @@ impl ChainSource {
655765
// `sync_onchain_wallet` and `sync_lightning_wallet`. So nothing to do here.
656766
unreachable!("Listeners will be synced via transction-based syncing")
657767
},
768+
Self::Electrum { .. } => {
769+
// In Electrum mode we sync lightning and onchain wallets via
770+
// `sync_onchain_wallet` and `sync_lightning_wallet`. So nothing to do here.
771+
unreachable!("Listeners will be synced via transction-based syncing")
772+
},
658773
Self::BitcoindRpc {
659774
bitcoind_rpc_client,
660775
header_cache,
@@ -875,6 +990,7 @@ impl ChainSource {
875990

876991
Ok(())
877992
},
993+
Self::Electrum { .. } => todo!(),
878994
Self::BitcoindRpc {
879995
bitcoind_rpc_client,
880996
fee_estimator,
@@ -1085,6 +1201,7 @@ impl ChainSource {
10851201
}
10861202
}
10871203
},
1204+
Self::Electrum { .. } => todo!(),
10881205
Self::BitcoindRpc { bitcoind_rpc_client, tx_broadcaster, logger, .. } => {
10891206
// While it's a bit unclear when we'd be able to lean on Bitcoin Core >v28
10901207
// features, we should eventually switch to use `submitpackage` via the
@@ -1147,12 +1264,14 @@ impl Filter for ChainSource {
11471264
fn register_tx(&self, txid: &bitcoin::Txid, script_pubkey: &bitcoin::Script) {
11481265
match self {
11491266
Self::Esplora { tx_sync, .. } => tx_sync.register_tx(txid, script_pubkey),
1267+
Self::Electrum { .. } => todo!(),
11501268
Self::BitcoindRpc { .. } => (),
11511269
}
11521270
}
11531271
fn register_output(&self, output: lightning::chain::WatchedOutput) {
11541272
match self {
11551273
Self::Esplora { tx_sync, .. } => tx_sync.register_output(output),
1274+
Self::Electrum { .. } => todo!(),
11561275
Self::BitcoindRpc { .. } => (),
11571276
}
11581277
}

0 commit comments

Comments
 (0)