Skip to content

Commit da465a4

Browse files
committed
Configure Electrum full-scan stop gap
Expose the bounded BDK full-scan stop gap for Electrum so restore behavior can be tuned consistently across remote backends. Document that the setting applies only to BDK full_scan calls and keeps incremental sync unaffected. Co-Authored-By: HAL 9000
1 parent aec47c5 commit da465a4

4 files changed

Lines changed: 55 additions & 10 deletions

File tree

src/chain/electrum.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,17 @@ use lightning::util::ser::Writeable;
2525
use lightning_transaction_sync::ElectrumSyncClient;
2626

2727
use super::WalletSyncStatus;
28-
use crate::config::{Config, ElectrumSyncConfig, BDK_CLIENT_STOP_GAP};
28+
use crate::config::{
29+
clamp_full_scan_stop_gap, Config, ElectrumSyncConfig, MAX_FULL_SCAN_STOP_GAP,
30+
MIN_FULL_SCAN_STOP_GAP,
31+
};
2932
use crate::error::Error;
3033
use crate::fee_estimator::{
3134
apply_post_estimation_adjustments, get_all_conf_targets, get_num_block_defaults_for_target,
3235
ConfirmationTarget, OnchainFeeEstimator,
3336
};
3437
use crate::io::utils::update_and_persist_node_metrics;
35-
use crate::logger::{log_bytes, log_debug, log_error, log_trace, LdkLogger, Logger};
38+
use crate::logger::{log_bytes, log_debug, log_error, log_trace, log_warn, LdkLogger, Logger};
3639
use crate::runtime::Runtime;
3740
use crate::types::{ChainMonitor, ChannelManager, DynStore, Sweeper, Wallet};
3841
use crate::PersistedNodeMetrics;
@@ -496,11 +499,12 @@ impl ElectrumRuntimeClient {
496499
) -> Result<BdkFullScanResponse<BdkKeyChainKind>, Error> {
497500
let bdk_electrum_client = Arc::clone(&self.bdk_electrum_client);
498501
bdk_electrum_client.populate_tx_cache(cached_txs);
502+
let full_scan_stop_gap = self.bounded_full_scan_stop_gap();
499503

500504
let spawn_fut = self.runtime.spawn_blocking(move || {
501505
bdk_electrum_client.full_scan(
502506
request,
503-
BDK_CLIENT_STOP_GAP,
507+
full_scan_stop_gap,
504508
BDK_ELECTRUM_CLIENT_BATCH_SIZE,
505509
true,
506510
)
@@ -526,6 +530,22 @@ impl ElectrumRuntimeClient {
526530
})
527531
}
528532

533+
fn bounded_full_scan_stop_gap(&self) -> usize {
534+
let configured = self.sync_config.full_scan_stop_gap;
535+
let bounded = clamp_full_scan_stop_gap(configured);
536+
if bounded != configured {
537+
log_warn!(
538+
self.logger,
539+
"Configured Electrum on-chain wallet full-scan stop gap {} is outside the allowed range {}..={}; using {}.",
540+
configured,
541+
MIN_FULL_SCAN_STOP_GAP,
542+
MAX_FULL_SCAN_STOP_GAP,
543+
bounded
544+
);
545+
}
546+
bounded as usize
547+
}
548+
529549
async fn get_incremental_sync_wallet_update(
530550
&self, request: BdkSyncRequest<(BdkKeyChainKind, u32)>,
531551
cached_txs: impl IntoIterator<Item = impl Into<Arc<Transaction>>>,

src/config.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,6 @@ pub const MIN_FULL_SCAN_STOP_GAP: u32 = 1;
7272
/// Values above 1000 are clamped to 1000 when a full scan runs.
7373
pub const MAX_FULL_SCAN_STOP_GAP: u32 = 1000;
7474

75-
// The fixed stop gap used by backends that don't yet expose a configurable value.
76-
pub(crate) const BDK_CLIENT_STOP_GAP: usize = DEFAULT_FULL_SCAN_STOP_GAP as usize;
77-
7875
// The number of concurrent requests made against the API provider.
7976
pub(crate) const BDK_CLIENT_CONCURRENCY: usize = 4;
8077

@@ -571,6 +568,23 @@ pub struct ElectrumSyncConfig {
571568
pub background_sync_config: Option<BackgroundSyncConfig>,
572569
/// Sync timeouts configuration.
573570
pub timeouts_config: SyncTimeoutsConfig,
571+
/// The stop gap used for BDK full scans of the on-chain wallet.
572+
///
573+
/// A full scan for each keychain stops after this many consecutive script pubkeys
574+
/// with no associated transactions. This value is only used for BDK `full_scan`
575+
/// calls, which ldk-node performs on the first on-chain wallet sync or when
576+
/// [`Self::force_wallet_full_scan`] is set. Incremental BDK `sync` calls do not use it.
577+
///
578+
/// **Default:** 20 ([`DEFAULT_FULL_SCAN_STOP_GAP`])
579+
///
580+
/// **Allowed values:** 1 ([`MIN_FULL_SCAN_STOP_GAP`]) to 1000
581+
/// ([`MAX_FULL_SCAN_STOP_GAP`]), inclusive. Values outside this range will be clamped to the
582+
/// nearest bound and a warning will be logged when the full scan runs.
583+
///
584+
/// **Note:** Large values can cause many Electrum requests, hit server rate limits,
585+
/// take a long time to complete, or cause syncs to fail with
586+
/// [`SyncTimeoutsConfig::onchain_wallet_sync_timeout_secs`].
587+
pub full_scan_stop_gap: u32,
574588
/// Whether to force BDK full scans until one succeeds.
575589
///
576590
/// This can be useful when restoring a wallet from seed on a node that has already synced
@@ -583,6 +597,7 @@ impl Default for ElectrumSyncConfig {
583597
Self {
584598
background_sync_config: Some(BackgroundSyncConfig::default()),
585599
timeouts_config: SyncTimeoutsConfig::default(),
600+
full_scan_stop_gap: DEFAULT_FULL_SCAN_STOP_GAP,
586601
force_wallet_full_scan: false,
587602
}
588603
}
@@ -748,9 +763,9 @@ mod tests {
748763
use std::str::FromStr;
749764

750765
use super::{
751-
clamp_full_scan_stop_gap, may_announce_channel, AnnounceError, Config, EsploraSyncConfig,
752-
NodeAlias, SocketAddress, DEFAULT_FULL_SCAN_STOP_GAP, MAX_FULL_SCAN_STOP_GAP,
753-
MIN_FULL_SCAN_STOP_GAP,
766+
clamp_full_scan_stop_gap, may_announce_channel, AnnounceError, Config, ElectrumSyncConfig,
767+
EsploraSyncConfig, NodeAlias, SocketAddress, DEFAULT_FULL_SCAN_STOP_GAP,
768+
MAX_FULL_SCAN_STOP_GAP, MIN_FULL_SCAN_STOP_GAP,
754769
};
755770

756771
#[test]
@@ -802,6 +817,7 @@ mod tests {
802817
#[test]
803818
fn full_scan_stop_gap_defaults() {
804819
assert_eq!(EsploraSyncConfig::default().full_scan_stop_gap, DEFAULT_FULL_SCAN_STOP_GAP);
820+
assert_eq!(ElectrumSyncConfig::default().full_scan_stop_gap, DEFAULT_FULL_SCAN_STOP_GAP);
805821
}
806822

807823
#[test]

tests/common/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,9 @@ pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) ->
554554
let mut sync_config = ElectrumSyncConfig::default();
555555
sync_config.background_sync_config = None;
556556
sync_config.force_wallet_full_scan = config.force_wallet_full_scan;
557+
if let Some(full_scan_stop_gap) = config.full_scan_stop_gap {
558+
sync_config.full_scan_stop_gap = full_scan_stop_gap;
559+
}
557560
builder.set_chain_source_electrum(electrum_url.clone(), Some(sync_config));
558561
},
559562
TestChainSource::BitcoindRpcSync(bitcoind) => {

tests/integration_tests_rust.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1000,7 +1000,7 @@ async fn onchain_wallet_force_full_scan_rediscovers_esplora_funds() {
10001000
}
10011001

10021002
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1003-
async fn onchain_wallet_full_scan_stop_gap_recovers_far_esplora_funds() {
1003+
async fn onchain_wallet_full_scan_stop_gap_recovers_far_esplora_and_electrum_funds() {
10041004
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
10051005
premine_blocks(&bitcoind.client, &electrsd.client).await;
10061006

@@ -1010,6 +1010,12 @@ async fn onchain_wallet_full_scan_stop_gap_recovers_far_esplora_funds() {
10101010
&electrsd,
10111011
)
10121012
.await;
1013+
do_onchain_wallet_full_scan_stop_gap_recovers_far_funds(
1014+
TestChainSource::Electrum(&electrsd),
1015+
&bitcoind,
1016+
&electrsd,
1017+
)
1018+
.await;
10131019
}
10141020

10151021
async fn do_onchain_wallet_full_scan_stop_gap_recovers_far_funds(

0 commit comments

Comments
 (0)