@@ -1557,19 +1557,20 @@ async fn do_onchain_wallet_full_scan_stop_gap_recovers_far_funds(
15571557
15581558#[ cfg( feature = "chain-bitcoind" ) ]
15591559#[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
1560- async fn onchain_wallet_recovery_rescans_from_birthday_height ( ) {
1560+ async fn onchain_wallet_recovery_includes_rescan_height ( ) {
15611561 // End-to-end test for `wallet_rescan_from_height` against a bitcoind chain source. The
15621562 // scenario:
15631563 //
15641564 // 1. Create a node at some "birthday" height and generate two receive addresses.
15651565 // 2. Shut the node down and drop all persisted state except the seed.
15661566 // 3. Advance the chain past the birthday.
1567- // 4. Send funds to the addresses generated at the birthday height and confirm them .
1567+ // 4. Send funds to both addresses and confirm both transactions in the same block .
15681568 // 5. Restart a fresh node with just the seed and no rescan height. Its wallet birthday
1569- // is pinned at the current tip, which is above the blocks containing the funding
1570- // transactions — so the node must not see the funds.
1571- // 6. Restart again with `wallet_rescan_from_height: Some(birthday)`. Now the wallet must
1572- // find and report both funding transactions.
1569+ // is pinned at the funding block, so the node must not see the funds.
1570+ // 6. Restart with the original birthday as `wallet_rescan_from_height` and confirm both
1571+ // transactions are found, preserving the previous test case.
1572+ // 7. Restart again with the funding block itself as `wallet_rescan_from_height` and confirm
1573+ // both transactions are found, proving that the configured height is inclusive.
15731574 let ( bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
15741575 // We specifically exercise the bitcoind RPC backend because that's where
15751576 // `rescan_from_height` is honored precisely (via `get_block_hash_by_height`).
@@ -1587,7 +1588,6 @@ async fn onchain_wallet_recovery_rescans_from_birthday_height() {
15871588
15881589 let addr_1 = original_node. onchain_payment ( ) . new_address ( ) . unwrap ( ) ;
15891590 let addr_2 = original_node. onchain_payment ( ) . new_address ( ) . unwrap ( ) ;
1590-
15911591 let birthday_height: u32 = bitcoind
15921592 . client
15931593 . get_blockchain_info ( )
@@ -1600,11 +1600,11 @@ async fn onchain_wallet_recovery_rescans_from_birthday_height() {
16001600 original_node. stop ( ) . unwrap ( ) ;
16011601 drop ( original_node) ;
16021602
1603- // Step 3: advance the chain past the birthday, so a fresh node would otherwise pin its
1604- // wallet birthday at a height above the funding transactions in step 4.
1603+ // Step 3: advance the chain past the original wallet birthday.
16051604 generate_blocks_and_wait ( & bitcoind. client , & electrsd. client , 10 ) . await ;
16061605
1607- // Step 4: fund both addresses and confirm them.
1606+ // Step 4: queue both transactions before mining so they are confirmed in the same block, as
1607+ // exercised by the previous version of this test.
16081608 let txid_1 = bitcoind
16091609 . client
16101610 . send_to_address ( & addr_1, Amount :: from_sat ( premine_amount_sat) )
@@ -1621,33 +1621,52 @@ async fn onchain_wallet_recovery_rescans_from_birthday_height() {
16211621 . parse ( )
16221622 . unwrap ( ) ;
16231623 wait_for_tx ( & electrsd. client , txid_2) . await ;
1624+ let funding_height: u32 =
1625+ ( bitcoind. client . get_blockchain_info ( ) . expect ( "failed to get blockchain info" ) . blocks + 1 )
1626+ . try_into ( )
1627+ . unwrap ( ) ;
16241628 generate_blocks_and_wait ( & bitcoind. client , & electrsd. client , 1 ) . await ;
16251629
1626- // Step 5: restart a fresh node with only the seed and no rescan height. It must NOT see
1627- // the funds, because its wallet birthday sits above the funding transactions.
1630+ // Step 5: restart a fresh node with only the seed and no rescan height. It must NOT see the
1631+ // funds, because its wallet birthday is the block containing both funding transactions.
16281632 let mut pinned_config = random_config ( ) ;
16291633 pinned_config. node_entropy = original_node_entropy;
16301634 let pinned_node = setup_node ( & chain_source, pinned_config) ;
16311635 pinned_node. sync_wallets ( ) . unwrap ( ) ;
16321636 assert_eq ! (
16331637 pinned_node. list_balances( ) . spendable_onchain_balance_sats,
16341638 0 ,
1635- "fresh node without rescan height should not find funds below its wallet birthday"
1639+ "fresh node without rescan height should not scan its wallet birthday block "
16361640 ) ;
16371641 pinned_node. stop ( ) . unwrap ( ) ;
16381642 drop ( pinned_node) ;
16391643
1640- // Step 6: restart with a rescan height set to the birthday height. Funds must be
1641- // re-discovered .
1642- let mut recovered_config = random_config ( ) ;
1643- recovered_config . node_entropy = original_node_entropy;
1644- recovered_config . wallet_rescan_from_height = Some ( birthday_height) ;
1645- let recovered_node = setup_node ( & chain_source, recovered_config ) ;
1646- recovered_node . sync_wallets ( ) . unwrap ( ) ;
1644+ // Step 6: recover from the original wallet birthday, retaining the previous test's case where
1645+ // both transactions are found 11 blocks after the configured rescan height .
1646+ let mut birthday_recovery_config = random_config ( ) ;
1647+ birthday_recovery_config . node_entropy = original_node_entropy;
1648+ birthday_recovery_config . wallet_rescan_from_height = Some ( birthday_height) ;
1649+ let birthday_recovered_node = setup_node ( & chain_source, birthday_recovery_config ) ;
1650+ birthday_recovered_node . sync_wallets ( ) . unwrap ( ) ;
16471651 assert_eq ! (
1648- recovered_node. list_balances( ) . spendable_onchain_balance_sats,
1652+ birthday_recovered_node. list_balances( ) . spendable_onchain_balance_sats,
1653+ premine_amount_sat * 2 ,
1654+ "node recovered from the wallet birthday should find both same-block transactions"
1655+ ) ;
1656+ birthday_recovered_node. stop ( ) . unwrap ( ) ;
1657+ drop ( birthday_recovered_node) ;
1658+
1659+ // Step 7: recover from the funding block itself. Both transactions must still be found, proving
1660+ // that `wallet_rescan_from_height` is inclusive.
1661+ let mut funding_block_recovery_config = random_config ( ) ;
1662+ funding_block_recovery_config. node_entropy = original_node_entropy;
1663+ funding_block_recovery_config. wallet_rescan_from_height = Some ( funding_height) ;
1664+ let funding_block_recovered_node = setup_node ( & chain_source, funding_block_recovery_config) ;
1665+ funding_block_recovered_node. sync_wallets ( ) . unwrap ( ) ;
1666+ assert_eq ! (
1667+ funding_block_recovered_node. list_balances( ) . spendable_onchain_balance_sats,
16491668 premine_amount_sat * 2 ,
1650- "node recovered with rescan_from_height should see funds sent to pre-birthday addresses "
1669+ "node recovered from the funding block should find both transactions in that block "
16511670 ) ;
16521671}
16531672
0 commit comments