From 10db851a45f1f83db71928dc9546e90a20ff75d3 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 23 Jan 2026 14:08:39 +0100 Subject: [PATCH 1/4] Move `async_payment_role` to `TestConfig` It's weird to have a special intermediary `setup_node` method if we have `TestConfig` for exactly that reason by now. So we move `async_payment_role` over. --- tests/common/mod.rs | 13 ++++--------- tests/integration_tests_rust.rs | 24 +++++++----------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 96f58297c..b5a6affa7 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -292,6 +292,7 @@ pub(crate) struct TestConfig { pub log_writer: TestLogWriter, pub store_type: TestStoreType, pub node_entropy: NodeEntropy, + pub async_payments_role: Option, } impl Default for TestConfig { @@ -302,7 +303,8 @@ impl Default for TestConfig { let mnemonic = generate_entropy_mnemonic(None); let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic, None); - TestConfig { node_config, log_writer, store_type, node_entropy } + let async_payments_role = None; + TestConfig { node_config, log_writer, store_type, node_entropy, async_payments_role } } } @@ -359,13 +361,6 @@ pub(crate) fn setup_two_nodes_with_store( } pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) -> TestNode { - setup_node_for_async_payments(chain_source, config, None) -} - -pub(crate) fn setup_node_for_async_payments( - chain_source: &TestChainSource, config: TestConfig, - async_payments_role: Option, -) -> TestNode { setup_builder!(builder, config.node_config); match chain_source { TestChainSource::Esplora(electrsd) => { @@ -417,7 +412,7 @@ pub(crate) fn setup_node_for_async_payments( }, } - builder.set_async_payments_role(async_payments_role).unwrap(); + builder.set_async_payments_role(config.async_payments_role).unwrap(); let node = match config.store_type { TestStoreType::TestSyncStore => { diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 4e94dd044..6e89dda72 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -23,8 +23,7 @@ use common::{ expect_splice_pending_event, generate_blocks_and_wait, open_channel, open_channel_push_amt, premine_and_distribute_funds, premine_blocks, prepare_rbf, random_config, random_listening_addresses, setup_bitcoind_and_electrsd, setup_builder, setup_node, - setup_node_for_async_payments, setup_two_nodes, wait_for_tx, TestChainSource, TestStoreType, - TestSyncStore, + setup_two_nodes, wait_for_tx, TestChainSource, TestStoreType, TestSyncStore, }; use ldk_node::config::{AsyncPaymentsRole, EsploraSyncConfig}; use ldk_node::entropy::NodeEntropy; @@ -1315,30 +1314,21 @@ async fn async_payment() { config_sender.node_config.node_alias = None; config_sender.log_writer = TestLogWriter::Custom(Arc::new(MultiNodeLogger::new("sender ".to_string()))); - let node_sender = setup_node_for_async_payments( - &chain_source, - config_sender, - Some(AsyncPaymentsRole::Client), - ); + config_sender.async_payments_role = Some(AsyncPaymentsRole::Client); + let node_sender = setup_node(&chain_source, config_sender); let mut config_sender_lsp = random_config(true); config_sender_lsp.log_writer = TestLogWriter::Custom(Arc::new(MultiNodeLogger::new("sender_lsp ".to_string()))); - let node_sender_lsp = setup_node_for_async_payments( - &chain_source, - config_sender_lsp, - Some(AsyncPaymentsRole::Server), - ); + config_sender_lsp.async_payments_role = Some(AsyncPaymentsRole::Server); + let node_sender_lsp = setup_node(&chain_source, config_sender_lsp); let mut config_receiver_lsp = random_config(true); config_receiver_lsp.log_writer = TestLogWriter::Custom(Arc::new(MultiNodeLogger::new("receiver_lsp".to_string()))); + config_receiver_lsp.async_payments_role = Some(AsyncPaymentsRole::Server); - let node_receiver_lsp = setup_node_for_async_payments( - &chain_source, - config_receiver_lsp, - Some(AsyncPaymentsRole::Server), - ); + let node_receiver_lsp = setup_node(&chain_source, config_receiver_lsp); let mut config_receiver = random_config(true); config_receiver.node_config.listening_addresses = None; From 8ca908c28824120ccca1c630c663fa95de29158c Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 23 Jan 2026 14:36:08 +0100 Subject: [PATCH 2/4] Randomize chain sources in tests .. all of our tests should be robust against switching chain sources. We here opt to pick a random one each time to considerably extend our test coverage, instead of just running some cases against non-Esplora chain sources. --- tests/common/mod.rs | 25 ++++++++++ tests/integration_tests_rust.rs | 85 +++++++++++---------------------- 2 files changed, 54 insertions(+), 56 deletions(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index b5a6affa7..5eec98da1 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -206,6 +206,31 @@ pub(crate) fn setup_bitcoind_and_electrsd() -> (BitcoinD, ElectrsD) { (bitcoind, electrsd) } +pub(crate) fn random_chain_source<'a>( + bitcoind: &'a BitcoinD, electrsd: &'a ElectrsD, +) -> TestChainSource<'a> { + let r = rand::random_range(0..3); + match r { + 0 => { + println!("Randomly setting up Esplora chain syncing..."); + TestChainSource::Esplora(electrsd) + }, + 1 => { + println!("Randomly setting up Electrum chain syncing..."); + TestChainSource::Electrum(electrsd) + }, + 2 => { + println!("Randomly setting up Bitcoind RPC chain syncing..."); + TestChainSource::BitcoindRpcSync(bitcoind) + }, + 3 => { + println!("Randomly setting up Bitcoind REST chain syncing..."); + TestChainSource::BitcoindRestSync(bitcoind) + }, + _ => unreachable!(), + } +} + pub(crate) fn random_storage_path() -> PathBuf { let mut temp_path = std::env::temp_dir(); let mut rng = rng(); diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 6e89dda72..a2943eb87 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -21,7 +21,7 @@ use common::{ expect_channel_pending_event, expect_channel_ready_event, expect_event, expect_payment_claimable_event, expect_payment_received_event, expect_payment_successful_event, expect_splice_pending_event, generate_blocks_and_wait, open_channel, open_channel_push_amt, - premine_and_distribute_funds, premine_blocks, prepare_rbf, random_config, + premine_and_distribute_funds, premine_blocks, prepare_rbf, random_chain_source, random_config, random_listening_addresses, setup_bitcoind_and_electrsd, setup_builder, setup_node, setup_two_nodes, wait_for_tx, TestChainSource, TestStoreType, TestSyncStore, }; @@ -43,34 +43,7 @@ use log::LevelFilter; #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn channel_full_cycle() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); - do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, false) - .await; -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn channel_full_cycle_electrum() { - let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Electrum(&electrsd); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); - do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, false) - .await; -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn channel_full_cycle_bitcoind_rpc_sync() { - let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::BitcoindRpcSync(&bitcoind); - let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); - do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, false) - .await; -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 1)] -async fn channel_full_cycle_bitcoind_rest_sync() { - let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::BitcoindRestSync(&bitcoind); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, false) .await; @@ -79,7 +52,7 @@ async fn channel_full_cycle_bitcoind_rest_sync() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn channel_full_cycle_force_close() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, true) .await; @@ -88,7 +61,7 @@ async fn channel_full_cycle_force_close() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn channel_full_cycle_force_close_trusted_no_reserve() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, true); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, true, true) .await; @@ -97,7 +70,7 @@ async fn channel_full_cycle_force_close_trusted_no_reserve() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn channel_full_cycle_0conf() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, true, true, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, true, true, false) .await; @@ -106,7 +79,7 @@ async fn channel_full_cycle_0conf() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn channel_full_cycle_legacy_staticremotekey() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false); do_channel_full_cycle(node_a, node_b, &bitcoind.client, &electrsd.client, false, false, false) .await; @@ -115,7 +88,7 @@ async fn channel_full_cycle_legacy_staticremotekey() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn channel_open_fails_when_funds_insufficient() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); let addr_a = node_a.onchain_payment().new_address().unwrap(); @@ -320,7 +293,7 @@ async fn start_stop_reinit() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn onchain_send_receive() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); let addr_a = node_a.onchain_payment().new_address().unwrap(); @@ -521,7 +494,7 @@ async fn onchain_send_receive() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn onchain_send_all_retains_reserve() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); // Setup nodes @@ -606,7 +579,7 @@ async fn onchain_send_all_retains_reserve() { async fn onchain_wallet_recovery() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let original_config = random_config(true); let original_node_entropy = original_config.node_entropy; @@ -821,9 +794,9 @@ async fn run_rbf_test(is_insert_block: bool) { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn sign_verify_msg() { - let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); let config = random_config(true); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let node = setup_node(&chain_source, config); // Tests arbitrary message signing and later verification @@ -835,8 +808,8 @@ async fn sign_verify_msg() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn connection_multi_listen() { - let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false); let node_id_b = node_b.node_id(); @@ -855,8 +828,8 @@ async fn connection_restart_behavior() { } async fn do_connection_restart_behavior(persist: bool) { - let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, false, false); let node_id_a = node_a.node_id(); @@ -902,8 +875,8 @@ async fn do_connection_restart_behavior(persist: bool) { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn concurrent_connections_succeed() { - let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); let node_a = Arc::new(node_a); @@ -1078,7 +1051,7 @@ async fn splice_channel() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn simple_bolt12_send_receive() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); let address_a = node_a.onchain_payment().new_address().unwrap(); @@ -1307,7 +1280,7 @@ async fn simple_bolt12_send_receive() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn async_payment() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let mut config_sender = random_config(true); config_sender.node_config.listening_addresses = None; @@ -1433,7 +1406,7 @@ async fn async_payment() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn test_node_announcement_propagation() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); // Node A will use both listening and announcement addresses let mut config_a = random_config(true); @@ -1525,7 +1498,7 @@ async fn test_node_announcement_propagation() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn generate_bip21_uri() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); @@ -1580,7 +1553,7 @@ async fn generate_bip21_uri() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn unified_send_receive_bip21_uri() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); @@ -1918,8 +1891,8 @@ async fn do_lsps2_client_service_integration(client_trusts_lsp: bool) { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn facade_logging() { - let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = random_chain_source(&bitcoind, &electrsd); let logger = init_log_logger(LevelFilter::Trace); let mut config = random_config(false); @@ -1937,7 +1910,7 @@ async fn facade_logging() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn spontaneous_send_with_custom_preimage() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); let address_a = node_a.onchain_payment().new_address().unwrap(); @@ -2003,8 +1976,8 @@ async fn spontaneous_send_with_custom_preimage() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn drop_in_async_context() { - let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = random_chain_source(&bitcoind, &electrsd); let config = random_config(true); let node = setup_node(&chain_source, config); node.stop().unwrap(); @@ -2313,7 +2286,7 @@ async fn lsps2_lsp_trusts_client_but_client_does_not_claim() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn payment_persistence_after_restart() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - let chain_source = TestChainSource::Esplora(&electrsd); + let chain_source = random_chain_source(&bitcoind, &electrsd); // Setup nodes manually so we can restart node_a with the same config println!("== Node A =="); From ef6895d06ffbb5d09ac3aa614a7dd1b82169f2ad Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 23 Jan 2026 16:19:49 +0100 Subject: [PATCH 3/4] Fix: Also update the payment store for mempool transactions When we intially implemented `bitcoind` syncing polling the mempool was very frequent and rather inefficient so we made a choice not to unnecessarily update the payment store for mempool changes, especially since we only consider transactions `Succeeded` after `ANTI_REORG_DELAY` anyways. However, since then we made quite a few peformance improvements to the mempool syncing, and by now we should just update they payment store as not doing so will lead to rather unexpected behavior, making some tests fail for `TestChainSource::Bitcoind`, e.g., `channel_full_cycle_0conf`, which we fix here. --- src/wallet/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 5fd7b3d8e..7d1e41c45 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -139,6 +139,10 @@ impl Wallet { pub(crate) fn apply_mempool_txs( &self, unconfirmed_txs: Vec<(Transaction, u64)>, evicted_txids: Vec<(Txid, u64)>, ) -> Result<(), Error> { + if unconfirmed_txs.is_empty() { + return Ok(()); + } + let mut locked_wallet = self.inner.lock().unwrap(); locked_wallet.apply_unconfirmed_txs(unconfirmed_txs); locked_wallet.apply_evicted_txs(evicted_txids); @@ -149,6 +153,11 @@ impl Wallet { Error::PersistenceFailed })?; + self.update_payment_store(&mut *locked_wallet).map_err(|e| { + log_error!(self.logger, "Failed to update payment store: {}", e); + Error::PersistenceFailed + })?; + Ok(()) } From b8179ca0b32b45dffc5354de5a890b9b6f567d9f Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 23 Jan 2026 17:04:34 +0100 Subject: [PATCH 4/4] Fix: Add (onchain) recovery mode Previously, we fixed than a fresh node syncing via `bitcoind` RPC would resync all chain data back to genesis. However, while introducing a wallet birthday is great, it disallowed discovery of historical funds if a wallet would be imported from seed. Here, we add a recovery mode flag to the builder that explictly allows to re-enable resyncing from genesis in such a scenario. Going forward, we intend to reuse that API for an upcoming Lightning recoery flow, too. --- bindings/ldk_node.udl | 1 + src/builder.rs | 57 ++++++++++++++++++++++++--------- tests/common/mod.rs | 19 ++++++++++- tests/integration_tests_rust.rs | 1 + 4 files changed, 62 insertions(+), 16 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index c881dbe09..4c7311a53 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -119,6 +119,7 @@ interface Builder { void set_node_alias(string node_alias); [Throws=BuildError] void set_async_payments_role(AsyncPaymentsRole? role); + void set_wallet_recovery_mode(); [Throws=BuildError] Node build(NodeEntropy node_entropy); [Throws=BuildError] diff --git a/src/builder.rs b/src/builder.rs index 0e8e8c166..a7328f1b0 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -241,6 +241,7 @@ pub struct NodeBuilder { async_payments_role: Option, runtime_handle: Option, pathfinding_scores_sync_config: Option, + recovery_mode: bool, } impl NodeBuilder { @@ -258,6 +259,7 @@ impl NodeBuilder { let log_writer_config = None; let runtime_handle = None; let pathfinding_scores_sync_config = None; + let recovery_mode = false; Self { config, chain_data_source_config, @@ -267,6 +269,7 @@ impl NodeBuilder { runtime_handle, async_payments_role: None, pathfinding_scores_sync_config, + recovery_mode, } } @@ -541,6 +544,16 @@ impl NodeBuilder { Ok(self) } + /// Configures the [`Node`] to resync chain data from genesis on first startup, recovering any + /// historical wallet funds. + /// + /// This should only be set on first startup when importing an older wallet from a previously + /// used [`NodeEntropy`]. + pub fn set_wallet_recovery_mode(&mut self) -> &mut Self { + self.recovery_mode = true; + self + } + /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. pub fn build(&self, node_entropy: NodeEntropy) -> Result { @@ -676,6 +689,7 @@ impl NodeBuilder { self.liquidity_source_config.as_ref(), self.pathfinding_scores_sync_config.as_ref(), self.async_payments_role, + self.recovery_mode, seed_bytes, runtime, logger, @@ -916,6 +930,15 @@ impl ArcedNodeBuilder { self.inner.write().unwrap().set_async_payments_role(role).map(|_| ()) } + /// Configures the [`Node`] to resync chain data from genesis on first startup, recovering any + /// historical wallet funds. + /// + /// This should only be set on first startup when importing an older wallet from a previously + /// used [`NodeEntropy`]. + pub fn set_wallet_recovery_mode(&self) { + self.inner.write().unwrap().set_wallet_recovery_mode(); + } + /// Builds a [`Node`] instance with a [`SqliteStore`] backend and according to the options /// previously configured. pub fn build(&self, node_entropy: Arc) -> Result, BuildError> { @@ -1030,8 +1053,8 @@ fn build_with_store_internal( gossip_source_config: Option<&GossipSourceConfig>, liquidity_source_config: Option<&LiquiditySourceConfig>, pathfinding_scores_sync_config: Option<&PathfindingScoresSyncConfig>, - async_payments_role: Option, seed_bytes: [u8; 64], runtime: Arc, - logger: Arc, kv_store: Arc, + async_payments_role: Option, recovery_mode: bool, seed_bytes: [u8; 64], + runtime: Arc, logger: Arc, kv_store: Arc, ) -> Result { optionally_install_rustls_cryptoprovider(); @@ -1225,19 +1248,23 @@ fn build_with_store_internal( BuildError::WalletSetupFailed })?; - if let Some(best_block) = chain_tip_opt { - // Insert the first checkpoint if we have it, to avoid resyncing from genesis. - // TODO: Use a proper wallet birthday once BDK supports it. - let mut latest_checkpoint = wallet.latest_checkpoint(); - let block_id = - bdk_chain::BlockId { height: best_block.height, hash: best_block.block_hash }; - latest_checkpoint = latest_checkpoint.insert(block_id); - let update = - bdk_wallet::Update { chain: Some(latest_checkpoint), ..Default::default() }; - wallet.apply_update(update).map_err(|e| { - log_error!(logger, "Failed to apply checkpoint during wallet setup: {}", e); - BuildError::WalletSetupFailed - })?; + if !recovery_mode { + if let Some(best_block) = chain_tip_opt { + // Insert the first checkpoint if we have it, to avoid resyncing from genesis. + // TODO: Use a proper wallet birthday once BDK supports it. + let mut latest_checkpoint = wallet.latest_checkpoint(); + let block_id = bdk_chain::BlockId { + height: best_block.height, + hash: best_block.block_hash, + }; + latest_checkpoint = latest_checkpoint.insert(block_id); + let update = + bdk_wallet::Update { chain: Some(latest_checkpoint), ..Default::default() }; + wallet.apply_update(update).map_err(|e| { + log_error!(logger, "Failed to apply checkpoint during wallet setup: {}", e); + BuildError::WalletSetupFailed + })?; + } } wallet }, diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 5eec98da1..f2127d526 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -318,6 +318,7 @@ pub(crate) struct TestConfig { pub store_type: TestStoreType, pub node_entropy: NodeEntropy, pub async_payments_role: Option, + pub recovery_mode: bool, } impl Default for TestConfig { @@ -329,7 +330,15 @@ impl Default for TestConfig { let mnemonic = generate_entropy_mnemonic(None); let node_entropy = NodeEntropy::from_bip39_mnemonic(mnemonic, None); let async_payments_role = None; - TestConfig { node_config, log_writer, store_type, node_entropy, async_payments_role } + let recovery_mode = false; + TestConfig { + node_config, + log_writer, + store_type, + node_entropy, + async_payments_role, + recovery_mode, + } } } @@ -439,6 +448,10 @@ pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) -> builder.set_async_payments_role(config.async_payments_role).unwrap(); + if config.recovery_mode { + builder.set_wallet_recovery_mode(); + } + let node = match config.store_type { TestStoreType::TestSyncStore => { let kv_store = TestSyncStore::new(config.node_config.storage_dir_path.into()); @@ -447,6 +460,10 @@ pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) -> TestStoreType::Sqlite => builder.build(config.node_entropy.into()).unwrap(), }; + if config.recovery_mode { + builder.set_wallet_recovery_mode(); + } + node.start().unwrap(); assert!(node.status().is_running); assert!(node.status().latest_fee_rate_cache_update_timestamp.is_some()); diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index a2943eb87..bb1e11fdb 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -624,6 +624,7 @@ async fn onchain_wallet_recovery() { // Now we start from scratch, only the seed remains the same. let mut recovered_config = random_config(true); recovered_config.node_entropy = original_node_entropy; + recovered_config.recovery_mode = true; let recovered_node = setup_node(&chain_source, recovered_config); recovered_node.sync_wallets().unwrap();