LCOV - code coverage report
Current view: top level - src/wallet/test - rescan_tests.cpp (source / functions) Coverage Total Hit
Test: test_bitcoin_coverage.info Lines: 97.0 % 437 424
Test Date: 2026-10-11 07:59:32 Functions: 100.0 % 36 36
Branches: 50.2 % 1958 982

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2012-present The Bitcoin Core developers
       2                 :             : // Distributed under the MIT software license, see the accompanying
       3                 :             : // file COPYING or https://opensource.org/license/mit.
       4                 :             : 
       5                 :             : #include <blockfilter.h>
       6                 :             : #include <chain.h>
       7                 :             : #include <index/blockfilterindex.h>
       8                 :             : #include <interfaces/chain.h>
       9                 :             : #include <node/blockstorage.h>
      10                 :             : #include <test/util/common.h>
      11                 :             : #include <test/util/logging.h>
      12                 :             : #include <test/util/setup_common.h>
      13                 :             : #include <test/util/validation.h>
      14                 :             : #include <util/byte_units.h>
      15                 :             : #include <util/check.h>
      16                 :             : #include <validation.h>
      17                 :             : #include <wallet/context.h>
      18                 :             : #include <wallet/receive.h>
      19                 :             : #include <wallet/scan.h>
      20                 :             : #include <wallet/test/util.h>
      21                 :             : #include <wallet/test/wallet_test_fixture.h>
      22                 :             : #include <wallet/wallet.h>
      23                 :             : #include <wallet/walletdb.h>
      24                 :             : 
      25                 :             : #include <boost/test/unit_test.hpp>
      26                 :             : 
      27                 :             : #include <cassert>
      28                 :             : #include <chrono>
      29                 :             : #include <memory>
      30                 :             : #include <string>
      31                 :             : #include <utility>
      32                 :             : 
      33                 :             : using node::MAX_BLOCKFILE_SIZE;
      34                 :             : 
      35                 :             : namespace wallet {
      36                 :             : 
      37                 :             : BOOST_FIXTURE_TEST_SUITE(rescan_tests, WalletTestingSetup)
      38                 :             : 
      39   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
      40                 :             : {
      41                 :             :     // Cap last block file size, and mine new block in a new block file.
      42   [ -  +  +  -  :           4 :     CBlockIndex* oldTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
             -  +  +  - ]
      43   [ +  -  +  - ]:           3 :     WITH_LOCK(::cs_main, m_node.chainman->m_blockman.GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE);
      44         [ +  - ]:           2 :     CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
      45   [ -  +  +  -  :           4 :     CBlockIndex* newTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
             -  +  +  - ]
      46                 :             : 
      47                 :             :     // Verify Scan fails to read an unknown start block.
      48                 :           1 :     {
      49   [ +  -  +  - ]:           2 :         CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
      50                 :           1 :         {
      51         [ +  - ]:           1 :             LOCK(wallet.cs_wallet);
      52   [ -  +  +  - ]:           1 :             LOCK(Assert(m_node.chainman)->GetMutex());
      53         [ +  - ]:           1 :             wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
      54   [ +  -  -  +  :           2 :             wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
          +  -  -  +  +  
                      - ]
      55         [ +  - ]:           1 :         }
      56         [ +  - ]:           1 :         AddKey(wallet, coinbaseKey);
      57         [ +  - ]:           1 :         WalletRescanReserver reserver(wallet);
      58         [ +  - ]:           1 :         reserver.reserve();
      59   [ +  -  +  - ]:           1 :         ScanResult result = wallet.Scanner().Scan(/*start_block=*/{}, /*start_height=*/0, /*max_height=*/{}, reserver, /*save_progress=*/false);
      60   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.status, ScanResult::FAILURE);
      61   [ +  -  +  -  :           3 :         BOOST_CHECK(result.last_failed_block.IsNull());
                   +  - ]
      62   [ +  -  +  -  :           3 :         BOOST_CHECK(result.last_scanned_block.IsNull());
                   +  - ]
      63   [ +  -  +  -  :           2 :         BOOST_CHECK(!result.last_scanned_height);
                   +  - ]
      64   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 0);
                   +  - ]
      65                 :           1 :     }
      66                 :             : 
      67                 :             :     // Verify Scan picks up transactions in both the old
      68                 :             :     // and new block files.
      69                 :           1 :     {
      70   [ +  -  +  - ]:           2 :         CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
      71                 :           1 :         {
      72         [ +  - ]:           1 :             LOCK(wallet.cs_wallet);
      73   [ -  +  +  - ]:           1 :             LOCK(Assert(m_node.chainman)->GetMutex());
      74         [ +  - ]:           1 :             wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
      75         [ +  - ]:           1 :             wallet.SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash());
      76         [ +  - ]:           1 :         }
      77         [ +  - ]:           1 :         AddKey(wallet, coinbaseKey);
      78                 :           1 :         WalletRescanReserver reserver(wallet);
      79                 :           1 :         std::chrono::steady_clock::time_point fake_time;
      80                 :           8 :         reserver.setNow([&] { fake_time += 60s; return fake_time; });
      81         [ +  - ]:           1 :         reserver.reserve();
      82                 :             : 
      83                 :           1 :         {
      84                 :           1 :             CBlockLocator locator;
      85   [ +  -  +  -  :           3 :             BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator));
          +  -  +  -  +  
                      - ]
      86   [ +  -  +  -  :           2 :             BOOST_REQUIRE(!locator.IsNull());
                   +  - ]
      87   [ +  -  +  - ]:           2 :             BOOST_CHECK(locator.vHave.front() == newTip->GetBlockHash());
      88                 :           0 :         }
      89                 :             : 
      90   [ +  -  +  - ]:           1 :         ScanResult result = wallet.Scanner().Scan(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*save_progress=*/true);
      91   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.status, ScanResult::SUCCESS);
      92   [ +  -  +  -  :           3 :         BOOST_CHECK(result.last_failed_block.IsNull());
                   +  - ]
      93   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
      94   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
      95   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 100 * COIN);
                   +  - ]
      96                 :             : 
      97                 :           1 :         {
      98                 :           1 :             CBlockLocator locator;
      99   [ +  -  +  -  :           3 :             BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator));
          +  -  +  -  +  
                      - ]
     100   [ +  -  +  -  :           2 :             BOOST_REQUIRE(!locator.IsNull());
                   +  - ]
     101   [ +  -  +  - ]:           2 :             BOOST_CHECK(locator.vHave.front() == newTip->GetBlockHash());
     102                 :           0 :         }
     103                 :           1 :     }
     104                 :             : 
     105                 :             :     // Prune the older block file.
     106                 :           1 :     int file_number;
     107                 :           1 :     {
     108                 :           1 :         LOCK(cs_main);
     109                 :           1 :         file_number = oldTip->GetBlockPos().nFile;
     110   [ -  +  +  - ]:           1 :         Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);
     111                 :           0 :     }
     112         [ +  - ]:           1 :     m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});
     113                 :             : 
     114                 :             :     // Verify Scan only picks transactions in the new block
     115                 :             :     // file.
     116                 :           1 :     {
     117   [ +  -  +  - ]:           2 :         CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
     118                 :           1 :         {
     119         [ +  - ]:           1 :             LOCK(wallet.cs_wallet);
     120   [ -  +  +  - ]:           1 :             LOCK(Assert(m_node.chainman)->GetMutex());
     121         [ +  - ]:           1 :             wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     122   [ +  -  -  +  :           2 :             wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
          +  -  -  +  +  
                      - ]
     123         [ +  - ]:           1 :         }
     124         [ +  - ]:           1 :         AddKey(wallet, coinbaseKey);
     125         [ +  - ]:           1 :         WalletRescanReserver reserver(wallet);
     126         [ +  - ]:           1 :         reserver.reserve();
     127   [ +  -  +  - ]:           1 :         ScanResult result = wallet.Scanner().Scan(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*save_progress=*/false);
     128   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.status, ScanResult::FAILURE);
     129   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash());
     130   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
     131   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
     132   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 50 * COIN);
                   +  - ]
     133                 :           1 :     }
     134                 :             : 
     135                 :             :     // Prune the remaining block file.
     136                 :           1 :     {
     137                 :           1 :         LOCK(cs_main);
     138                 :           1 :         file_number = newTip->GetBlockPos().nFile;
     139   [ -  +  +  - ]:           1 :         Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);
     140                 :           0 :     }
     141         [ +  - ]:           1 :     m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});
     142                 :             : 
     143                 :             :     // Verify Scan scans no blocks.
     144                 :           1 :     {
     145   [ +  -  +  - ]:           2 :         CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
     146                 :           1 :         {
     147         [ +  - ]:           1 :             LOCK(wallet.cs_wallet);
     148   [ -  +  +  - ]:           1 :             LOCK(Assert(m_node.chainman)->GetMutex());
     149         [ +  - ]:           1 :             wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     150   [ +  -  -  +  :           2 :             wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
          +  -  -  +  +  
                      - ]
     151         [ +  - ]:           1 :         }
     152         [ +  - ]:           1 :         AddKey(wallet, coinbaseKey);
     153         [ +  - ]:           1 :         WalletRescanReserver reserver(wallet);
     154         [ +  - ]:           1 :         reserver.reserve();
     155   [ +  -  +  - ]:           1 :         ScanResult result = wallet.Scanner().Scan(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*save_progress=*/false);
     156   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.status, ScanResult::FAILURE);
     157   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash());
     158   [ +  -  +  -  :           3 :         BOOST_CHECK(result.last_scanned_block.IsNull());
                   +  - ]
     159   [ +  -  +  -  :           2 :         BOOST_CHECK(!result.last_scanned_height);
                   +  - ]
     160   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 0);
                   +  - ]
     161                 :           1 :     }
     162                 :           1 : }
     163                 :             : 
     164   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions_reorged_block, TestChain100Setup)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     165                 :             : {
     166   [ +  -  +  - ]:           3 :     BOOST_REQUIRE(InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1_MiB, /*f_memory=*/true));
     167         [ -  + ]:           1 :     BlockFilterIndex& filter_index{*Assert(GetBlockFilterIndex(BlockFilterType::BASIC))};
     168   [ +  -  +  - ]:           2 :     BOOST_REQUIRE(filter_index.Init());
     169                 :           1 :     filter_index.Sync();
     170                 :             : 
     171                 :             :     // Reorg the tip out of the active chain: invalidate it, then mine a
     172                 :             :     // longer replacement branch paying a script unrelated to the wallets
     173                 :             :     // below.
     174   [ -  +  +  -  :           4 :     CBlockIndex* stale_block = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
             -  +  +  - ]
     175                 :           1 :     const uint256 stale_hash{stale_block->GetBlockHash()};
     176                 :           1 :     const int stale_height{stale_block->nHeight};
     177         [ +  - ]:           1 :     BlockValidationState state;
     178   [ +  -  +  -  :           2 :     BOOST_REQUIRE(m_node.chainman->ActiveChainstate().InvalidateBlock(state, stale_block));
             +  -  +  - ]
     179   [ +  -  +  - ]:           1 :     const CScript replacement_script{GetScriptForRawPubKey(GenerateRandomKey().GetPubKey())};
     180         [ +  - ]:           2 :     CreateAndProcessBlock({}, replacement_script);
     181         [ +  - ]:           2 :     CreateAndProcessBlock({}, replacement_script);
     182   [ +  -  +  -  :           2 :     BOOST_REQUIRE(filter_index.BlockUntilSyncedToCurrentChain());
             +  -  -  + ]
     183                 :           1 :     {
     184   [ -  +  +  - ]:           1 :         LOCK(Assert(m_node.chainman)->GetMutex());
     185   [ +  -  +  -  :           2 :         BOOST_REQUIRE(!m_node.chainman->ActiveChain().Contains(*stale_block));
             +  -  +  - ]
     186   [ +  -  +  -  :           1 :         BOOST_REQUIRE_EQUAL(m_node.chainman->ActiveChain().Height(), stale_height + 1);
          -  +  +  -  +  
                      - ]
     187                 :           0 :     }
     188                 :             : 
     189                 :           1 :     {
     190         [ +  - ]:           1 :         BlockFilter filter;
     191   [ +  -  +  -  :           2 :         BOOST_REQUIRE(filter_index.LookupFilter(stale_block, filter));
                   +  - ]
     192                 :           0 :     }
     193                 :             : 
     194                 :             :     // Test wallet whose scripts do not match the stale block's filter.
     195                 :           1 :     {
     196   [ +  -  +  -  :           2 :         CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
                   +  - ]
     197                 :           1 :         {
     198         [ +  - ]:           1 :             LOCK(wallet.cs_wallet);
     199   [ -  +  +  - ]:           1 :             LOCK(Assert(m_node.chainman)->GetMutex());
     200         [ +  - ]:           1 :             wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     201   [ +  -  -  +  :           2 :             wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
          +  -  -  +  +  
                      - ]
     202         [ +  - ]:           1 :         }
     203         [ +  - ]:           1 :         WalletRescanReserver reserver(wallet);
     204         [ +  - ]:           1 :         reserver.reserve();
     205   [ +  -  +  - ]:           1 :         ScanResult result = wallet.Scanner().Scan(stale_hash, stale_height, /*max_height=*/{}, reserver, /*save_progress=*/false);
     206   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.status, ScanResult::SUCCESS);
     207   [ +  -  +  -  :           3 :         BOOST_CHECK(result.last_failed_block.IsNull());
                   +  - ]
     208   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.last_scanned_block, stale_hash);
     209   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(*result.last_scanned_height, stale_height);
     210                 :           1 :     }
     211                 :             : 
     212                 :             :     // Test wallet whose scripts do match the stale block's filter.
     213                 :           1 :     {
     214   [ +  -  +  -  :           2 :         CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
                   +  - ]
     215                 :           1 :         {
     216         [ +  - ]:           1 :             LOCK(wallet.cs_wallet);
     217   [ -  +  +  - ]:           1 :             LOCK(Assert(m_node.chainman)->GetMutex());
     218         [ +  - ]:           1 :             wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     219   [ +  -  -  +  :           2 :             wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
          +  -  -  +  +  
                      - ]
     220         [ +  - ]:           1 :         }
     221         [ +  - ]:           1 :         AddKey(wallet, coinbaseKey); // the stale block's coinbase pays coinbaseKey
     222         [ +  - ]:           1 :         WalletRescanReserver reserver(wallet);
     223         [ +  - ]:           1 :         reserver.reserve();
     224   [ +  -  +  - ]:           1 :         ScanResult result = wallet.Scanner().Scan(stale_hash, stale_height, /*max_height=*/{}, reserver, /*save_progress=*/false);
     225   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.status, ScanResult::FAILURE);
     226   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.last_failed_block, stale_hash);
     227   [ +  -  +  -  :           3 :         BOOST_CHECK(result.last_scanned_block.IsNull());
                   +  - ]
     228   [ +  -  +  -  :           2 :         BOOST_CHECK(!result.last_scanned_height);
                   +  - ]
     229   [ +  -  +  -  :           3 :         BOOST_CHECK(WITH_LOCK(wallet.cs_wallet, return wallet.mapWallet.empty()));
                   +  - ]
     230                 :           1 :     }
     231                 :             : 
     232                 :             :     // Prune the stale block's file — the block is now not active AND unreadable.
     233                 :           1 :     int file_number;
     234                 :           1 :     {
     235         [ +  - ]:           1 :         LOCK(cs_main);
     236                 :           1 :         file_number = stale_block->GetBlockPos().nFile;
     237   [ -  +  +  - ]:           1 :         Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);
     238                 :           0 :     }
     239   [ +  -  +  - ]:           1 :     m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});
     240                 :             : 
     241                 :           1 :     {
     242   [ +  -  +  -  :           2 :         CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
                   +  - ]
     243                 :           1 :         {
     244         [ +  - ]:           1 :             LOCK(wallet.cs_wallet);
     245   [ -  +  +  - ]:           1 :             LOCK(Assert(m_node.chainman)->GetMutex());
     246         [ +  - ]:           1 :             wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     247   [ +  -  -  +  :           2 :             wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
          +  -  -  +  +  
                      - ]
     248         [ +  - ]:           1 :         }
     249         [ +  - ]:           1 :         AddKey(wallet, coinbaseKey);
     250         [ +  - ]:           1 :         WalletRescanReserver reserver(wallet);
     251         [ +  - ]:           1 :         reserver.reserve();
     252   [ +  -  +  - ]:           1 :         ScanResult result = wallet.Scanner().Scan(stale_hash, stale_height, /*max_height=*/{}, reserver, /*save_progress=*/false);
     253   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.status, ScanResult::FAILURE);
     254   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.last_failed_block, stale_hash);
     255   [ +  -  +  -  :           3 :         BOOST_CHECK(result.last_scanned_block.IsNull());
                   +  - ]
     256   [ +  -  +  -  :           2 :         BOOST_CHECK(!result.last_scanned_height);
                   +  - ]
     257   [ +  -  +  -  :           3 :         BOOST_CHECK(WITH_LOCK(wallet.cs_wallet, return wallet.mapWallet.empty()));
                   +  - ]
     258                 :           1 :     }
     259                 :             : 
     260         [ +  - ]:           1 :     StopIndex(filter_index, m_node);
     261   [ +  -  +  -  :           2 :     BOOST_REQUIRE(DestroyBlockFilterIndex(BlockFilterType::BASIC));
                   +  - ]
     262                 :           2 : }
     263                 :             : 
     264   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions_abort, TestChain100Setup)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     265                 :             : {
     266   [ +  -  +  - ]:           2 :     CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
     267                 :           1 :     uint256 genesis_hash;
     268                 :           1 :     {
     269         [ +  - ]:           1 :         LOCK(wallet.cs_wallet);
     270   [ -  +  +  - ]:           1 :         LOCK(Assert(m_node.chainman)->GetMutex());
     271         [ +  - ]:           1 :         wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     272   [ +  -  -  +  :           2 :         wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
          +  -  -  +  +  
                      - ]
     273   [ +  -  -  +  :           2 :         genesis_hash = m_node.chainman->ActiveChain().Genesis()->GetBlockHash();
                   +  - ]
     274         [ +  - ]:           1 :     }
     275                 :             : 
     276                 :             :     // An abort requested while no rescan is held is stale and must
     277                 :             :     // not cancel a later scan.
     278   [ +  -  +  - ]:           1 :     wallet.Scanner().Abort();
     279         [ +  - ]:           1 :     WalletRescanReserver reserver(wallet);
     280   [ +  -  +  -  :           2 :     BOOST_CHECK(reserver.reserve());
             +  -  +  - ]
     281   [ +  -  +  -  :           2 :     BOOST_CHECK(!wallet.Scanner().IsAborting());
             +  -  +  - ]
     282                 :             : 
     283                 :             :     // An abort requested after the reservation but before the scan starts
     284                 :             :     // (e.g. while importdescriptors is still deriving keys) must cancel the
     285                 :             :     // scan.
     286   [ +  -  +  - ]:           1 :     wallet.Scanner().Abort();
     287   [ +  -  +  - ]:           1 :     ScanResult result = wallet.Scanner().Scan(genesis_hash, /*start_height=*/0, /*max_height=*/{}, reserver, /*save_progress=*/false);
     288   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(result.status, ScanResult::USER_ABORT);
     289   [ +  -  +  -  :           3 :     BOOST_CHECK(result.last_scanned_block.IsNull());
                   +  - ]
     290   [ +  -  +  -  :           2 :     BOOST_CHECK(!result.last_scanned_height);
                   +  - ]
     291   [ +  -  +  - ]:           3 :     BOOST_CHECK(result.last_failed_block.IsNull());
     292                 :           1 : }
     293                 :             : 
     294   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(wallet_rescan_reserver, TestingSetup)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     295                 :             : {
     296   [ +  -  +  - ]:           2 :     CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
     297                 :             : 
     298                 :             :     // No scan in progress: accessors report idle state.
     299   [ +  -  +  -  :           2 :     BOOST_CHECK(!wallet.Scanner().IsScanning());
             +  -  +  - ]
     300   [ +  -  +  -  :           2 :     BOOST_CHECK(wallet.Scanner().ScanningDuration() == SteadyClock::duration{});
             +  -  +  - ]
     301   [ +  -  +  -  :           1 :     BOOST_CHECK_EQUAL(wallet.Scanner().ScanningProgress(), 0.0);
                   +  - ]
     302                 :             : 
     303                 :           1 :     {
     304         [ +  - ]:           1 :         WalletRescanReserver first_reserver(wallet);
     305   [ +  -  +  -  :           2 :         BOOST_CHECK(first_reserver.reserve());
             +  -  +  - ]
     306   [ +  -  +  -  :           2 :         BOOST_CHECK(first_reserver.isReserved());
             +  -  +  - ]
     307   [ +  -  +  -  :           2 :         BOOST_CHECK(wallet.Scanner().IsScanning());
             +  -  +  - ]
     308   [ +  -  +  -  :           2 :         BOOST_CHECK(!wallet.Scanner().IsScanningWithPassphrase());
             +  -  +  - ]
     309   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(wallet.Scanner().ScanningProgress(), 0.0);
                   +  - ]
     310                 :             : 
     311                 :             :         // Only one reservation can be held at a time.
     312         [ +  - ]:           1 :         WalletRescanReserver second_reserver(wallet);
     313   [ +  -  +  -  :           2 :         BOOST_CHECK(!second_reserver.reserve());
             +  -  +  - ]
     314   [ +  -  +  -  :           2 :         BOOST_CHECK(!second_reserver.isReserved());
                   +  - ]
     315                 :           1 :     }
     316                 :             :     // Destroying the reserver (RAII) clears the scanning state.
     317   [ +  -  +  -  :           2 :     BOOST_CHECK(!wallet.Scanner().IsScanning());
             +  -  +  - ]
     318                 :             : 
     319                 :           1 :     {
     320         [ +  - ]:           1 :         WalletRescanReserver passphrase_reserver(wallet);
     321   [ +  -  +  -  :           2 :         BOOST_CHECK(passphrase_reserver.reserve(/*with_passphrase=*/true));
             +  -  +  - ]
     322   [ +  -  +  -  :           2 :         BOOST_CHECK(wallet.Scanner().IsScanningWithPassphrase());
                   +  - ]
     323                 :           1 :     }
     324   [ +  -  +  -  :           2 :     BOOST_CHECK(!wallet.Scanner().IsScanningWithPassphrase());
                   +  - ]
     325                 :           1 : }
     326                 :             : 
     327   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions_bounded, TestChain100Setup)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     328                 :             : {
     329                 :           1 :     uint256 genesis_hash, max_hash, tip_hash;
     330                 :           1 :     int max_height, tip_height;
     331                 :           1 :     {
     332         [ -  + ]:           1 :         LOCK(Assert(m_node.chainman)->GetMutex());
     333   [ +  -  -  + ]:           2 :         genesis_hash = m_node.chainman->ActiveChain().Genesis()->GetBlockHash();
     334   [ +  -  -  + ]:           1 :         tip_height = m_node.chainman->ActiveChain().Height();
     335   [ +  -  -  + ]:           2 :         tip_hash = m_node.chainman->ActiveChain().Tip()->GetBlockHash();
     336                 :           1 :         max_height = tip_height - 2;
     337   [ +  -  +  -  :           2 :         max_hash = m_node.chainman->ActiveChain()[max_height]->GetBlockHash();
                   +  - ]
     338                 :           0 :     }
     339                 :             : 
     340                 :             :     // A scan with max_height set stops exactly at max_height and does not
     341                 :             :     // sync any blocks beyond it.
     342                 :           1 :     {
     343   [ +  -  +  - ]:           2 :         CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
     344                 :           1 :         {
     345         [ +  - ]:           1 :             LOCK(wallet.cs_wallet);
     346         [ +  - ]:           1 :             wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     347         [ +  - ]:           1 :             wallet.SetLastBlockProcessed(tip_height, tip_hash);
     348                 :           0 :         }
     349         [ +  - ]:           1 :         AddKey(wallet, coinbaseKey);
     350         [ +  - ]:           1 :         WalletRescanReserver reserver(wallet);
     351         [ +  - ]:           1 :         reserver.reserve();
     352   [ +  -  +  - ]:           1 :         ScanResult result = wallet.Scanner().Scan(genesis_hash, /*start_height=*/0, max_height, reserver, /*save_progress=*/false);
     353   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.status, ScanResult::SUCCESS);
     354   [ +  -  +  -  :           3 :         BOOST_CHECK(result.last_failed_block.IsNull());
                   +  - ]
     355   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.last_scanned_block, max_hash);
     356   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(*result.last_scanned_height, max_height);
     357                 :             :         // One coinbase per block from height 1 through max_height.
     358   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(WITH_LOCK(wallet.cs_wallet, return wallet.mapWallet.size()), static_cast<size_t>(max_height));
                   +  - ]
     359                 :           1 :     }
     360                 :             : 
     361                 :             :     // A single-block range (start == max_height == tip) scans exactly that
     362                 :             :     // block.
     363                 :           1 :     {
     364   [ +  -  +  - ]:           2 :         CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
     365                 :           1 :         {
     366         [ +  - ]:           1 :             LOCK(wallet.cs_wallet);
     367         [ +  - ]:           1 :             wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     368         [ +  - ]:           1 :             wallet.SetLastBlockProcessed(tip_height, tip_hash);
     369                 :           0 :         }
     370         [ +  - ]:           1 :         AddKey(wallet, coinbaseKey);
     371         [ +  - ]:           1 :         WalletRescanReserver reserver(wallet);
     372         [ +  - ]:           1 :         reserver.reserve();
     373   [ +  -  +  - ]:           1 :         ScanResult result = wallet.Scanner().Scan(tip_hash, tip_height, tip_height, reserver, /*save_progress=*/false);
     374   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.status, ScanResult::SUCCESS);
     375   [ +  -  +  -  :           3 :         BOOST_CHECK(result.last_failed_block.IsNull());
                   +  - ]
     376   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.last_scanned_block, tip_hash);
     377   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(*result.last_scanned_height, tip_height);
     378   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(WITH_LOCK(wallet.cs_wallet, return wallet.mapWallet.size()), 1U);
                   +  - ]
     379                 :           1 :     }
     380                 :           1 : }
     381                 :             : 
     382   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions_tip_extension, TestChain100Setup)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     383                 :             : {
     384   [ +  -  +  - ]:           2 :     CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
     385                 :           1 :     uint256 genesis_hash;
     386                 :           1 :     int start_tip_height{0};
     387                 :           1 :     {
     388         [ +  - ]:           1 :         LOCK(wallet.cs_wallet);
     389   [ -  +  +  - ]:           1 :         LOCK(Assert(m_node.chainman)->GetMutex());
     390         [ +  - ]:           1 :         wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     391   [ +  -  -  + ]:           1 :         start_tip_height = m_node.chainman->ActiveChain().Height();
     392   [ +  -  -  +  :           2 :         wallet.SetLastBlockProcessed(start_tip_height, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
                   +  - ]
     393   [ +  -  -  +  :           2 :         genesis_hash = m_node.chainman->ActiveChain().Genesis()->GetBlockHash();
                   +  - ]
     394         [ +  - ]:           1 :     }
     395         [ +  - ]:           1 :     AddKey(wallet, coinbaseKey);
     396                 :             : 
     397                 :             :     // Connect a block while the scan is running (the handler fires on the
     398                 :             :     // scanning thread as the scan starts) and advance the wallet's tip, as
     399                 :             :     // the blockConnected notification would. The scan must pick up the new
     400                 :             :     // tip instead of stopping at the height it started with.
     401                 :           1 :     uint256 new_tip_hash;
     402                 :           1 :     bool extended{false};
     403                 :           5 :     auto handler = wallet.ShowProgress.connect([&](const std::string&, int) {
     404         [ +  + ]:           4 :         if (extended) return;
     405                 :           1 :         extended = true;
     406         [ +  - ]:           2 :         CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
     407                 :           1 :         LOCK(wallet.cs_wallet);
     408   [ -  +  +  - ]:           1 :         LOCK(Assert(m_node.chainman)->GetMutex());
     409   [ +  -  -  + ]:           1 :         const CBlockIndex* new_tip = m_node.chainman->ActiveChain().Tip();
     410                 :           1 :         new_tip_hash = new_tip->GetBlockHash();
     411         [ +  - ]:           1 :         wallet.SetLastBlockProcessed(new_tip->nHeight, new_tip_hash);
     412         [ +  - ]:           3 :     });
     413                 :             : 
     414         [ +  - ]:           1 :     WalletRescanReserver reserver(wallet);
     415         [ +  - ]:           1 :     reserver.reserve();
     416   [ +  -  +  - ]:           1 :     ScanResult result = wallet.Scanner().Scan(genesis_hash, /*start_height=*/0, /*max_height=*/{}, reserver, /*save_progress=*/false);
     417                 :           1 :     handler.disconnect();
     418   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(result.status, ScanResult::SUCCESS);
     419   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(result.last_scanned_block, new_tip_hash);
     420   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(*result.last_scanned_height, start_tip_height + 1);
     421         [ +  - ]:           2 : }
     422                 :             : 
     423   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions_no_progress_saved, TestChain100Setup)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     424                 :             : {
     425   [ +  -  +  - ]:           2 :     CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
     426                 :           1 :     uint256 genesis_hash, tip_hash;
     427                 :           1 :     int max_height;
     428                 :           1 :     {
     429         [ +  - ]:           1 :         LOCK(wallet.cs_wallet);
     430   [ -  +  +  - ]:           1 :         LOCK(Assert(m_node.chainman)->GetMutex());
     431         [ +  - ]:           1 :         wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     432   [ +  -  -  + ]:           2 :         tip_hash = m_node.chainman->ActiveChain().Tip()->GetBlockHash();
     433   [ +  -  -  +  :           1 :         wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), tip_hash);
                   +  - ]
     434   [ +  -  -  + ]:           2 :         genesis_hash = m_node.chainman->ActiveChain().Genesis()->GetBlockHash();
     435   [ +  -  -  +  :           1 :         max_height = m_node.chainman->ActiveChain().Height() - 2;
                   +  - ]
     436         [ +  - ]:           1 :     }
     437         [ +  - ]:           1 :     AddKey(wallet, coinbaseKey);
     438                 :             : 
     439                 :           1 :     WalletRescanReserver reserver(wallet);
     440                 :             :     // Advance the clock on every call so that every scanned block would be
     441                 :             :     // eligible for a progress write if save_progress were set.
     442                 :           1 :     std::chrono::steady_clock::time_point fake_time;
     443                 :         202 :     reserver.setNow([&] { fake_time += 60s; return fake_time; });
     444         [ +  - ]:           1 :     reserver.reserve();
     445                 :             : 
     446   [ +  -  +  - ]:           1 :     ScanResult result = wallet.Scanner().Scan(genesis_hash, /*start_height=*/0, max_height, reserver, /*save_progress=*/false);
     447   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(result.status, ScanResult::SUCCESS);
     448                 :             : 
     449                 :             :     // With save_progress=false the scan must not touch the wallet's best
     450                 :             :     // block record: it still points at the tip written when the descriptor
     451                 :             :     // was added, not at any block the scan visited.
     452                 :           1 :     CBlockLocator locator;
     453   [ +  -  +  -  :           3 :     BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator));
          +  -  +  -  +  
                      - ]
     454   [ +  -  +  -  :           2 :     BOOST_CHECK(!locator.IsNull());
                   +  - ]
     455   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(locator.vHave.front(), tip_hash);
     456                 :           1 : }
     457                 :             : 
     458   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(rescan_from_time, TestChain100Setup)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     459                 :             : {
     460                 :             :     // Cap last block file size, and mine new block in a new block file.
     461   [ -  +  +  -  :           4 :     CBlockIndex* old_tip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
             -  +  +  - ]
     462   [ +  -  +  - ]:           3 :     WITH_LOCK(::cs_main, m_node.chainman->m_blockman.GetBlockFileInfo(old_tip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE);
     463         [ +  - ]:           2 :     CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
     464   [ -  +  +  -  :           4 :     CBlockIndex* new_tip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
             -  +  +  - ]
     465                 :             : 
     466                 :             :     // Prune the older block file.
     467                 :           1 :     int file_number;
     468                 :           1 :     {
     469                 :           1 :         LOCK(cs_main);
     470                 :           1 :         file_number = old_tip->GetBlockPos().nFile;
     471   [ -  +  +  - ]:           1 :         Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);
     472                 :           0 :     }
     473         [ +  - ]:           1 :     m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});
     474                 :             : 
     475   [ +  -  +  - ]:           2 :     CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
     476                 :           1 :     {
     477         [ +  - ]:           1 :         LOCK(wallet.cs_wallet);
     478   [ -  +  +  - ]:           1 :         LOCK(Assert(m_node.chainman)->GetMutex());
     479         [ +  - ]:           1 :         wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     480   [ +  -  -  +  :           2 :         wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
          +  -  -  +  +  
                      - ]
     481         [ +  - ]:           1 :     }
     482         [ +  - ]:           1 :     AddKey(wallet, coinbaseKey);
     483         [ +  - ]:           1 :     WalletRescanReserver reserver(wallet);
     484         [ +  - ]:           1 :     reserver.reserve();
     485                 :             : 
     486                 :             :     // Blocks before the prune point cannot be read: the returned timestamp
     487                 :             :     // is moved past the last unreadable block, telling the caller from when
     488                 :             :     // the rescan is actually complete.
     489   [ +  -  -  +  :           4 :     const int64_t genesis_time{WITH_LOCK(::cs_main, return m_node.chainman->ActiveChain().Genesis()->GetBlockTime())};
                   +  - ]
     490   [ +  -  +  -  :           2 :     BOOST_CHECK_EQUAL(wallet.Scanner().ScanFromTime(genesis_time, reserver),
          +  -  +  -  +  
                      - ]
     491                 :             :                       WITH_LOCK(::cs_main, return old_tip->GetBlockTimeMax()) + TIMESTAMP_WINDOW + 1);
     492                 :             : 
     493                 :           1 :     bool scan_logged{false};
     494   [ +  -  +  - ]:           3 :     DebugLogHelper scan_check{"Rescan started from block", [&](const std::string* s) {
     495         [ -  + ]:           1 :         if (s) scan_logged = true;
     496                 :           1 :         return false;
     497         [ +  - ]:           1 :     }};
     498                 :             :     // A timestamp past the tip requires no scanning and is returned unchanged.
     499         [ +  - ]:           2 :     const int64_t future_time{WITH_LOCK(::cs_main, return new_tip->GetBlockTimeMax()) + TIMESTAMP_WINDOW + 1};
     500   [ +  -  +  -  :           2 :     BOOST_CHECK(!scan_logged);
                   +  - ]
     501   [ +  -  +  -  :           1 :     BOOST_CHECK_EQUAL(wallet.Scanner().ScanFromTime(future_time, reserver), future_time);
             +  -  +  - ]
     502                 :           1 : }
     503                 :             : 
     504   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions_missing_filter, TestChain100Setup)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     505                 :             : {
     506                 :             :     // Enable the block filter index but do not sync it: no filters are
     507                 :             :     // available, so the scan must inspect every block rather than treat
     508                 :             :     // the missing filters as misses and skip blocks.
     509   [ +  -  +  - ]:           3 :     BOOST_REQUIRE(InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1_MiB, /*f_memory=*/true));
     510         [ -  + ]:           1 :     BlockFilterIndex& filter_index{*Assert(GetBlockFilterIndex(BlockFilterType::BASIC))};
     511   [ +  -  +  - ]:           2 :     BOOST_REQUIRE(filter_index.Init());
     512                 :             : 
     513                 :           1 :     {
     514   [ +  -  +  - ]:           2 :         CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
     515                 :           1 :         uint256 genesis_hash, tip_hash;
     516                 :           1 :         int tip_height;
     517                 :           1 :         {
     518         [ +  - ]:           1 :             LOCK(wallet.cs_wallet);
     519   [ -  +  +  - ]:           1 :             LOCK(Assert(m_node.chainman)->GetMutex());
     520         [ +  - ]:           1 :             wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
     521   [ +  -  -  + ]:           2 :             genesis_hash = m_node.chainman->ActiveChain().Genesis()->GetBlockHash();
     522   [ +  -  -  + ]:           1 :             tip_height = m_node.chainman->ActiveChain().Height();
     523   [ +  -  -  + ]:           1 :             auto tip{m_node.chainman->ActiveChain().Tip()};
     524                 :           1 :             tip_hash = tip->GetBlockHash();
     525         [ +  - ]:           1 :             wallet.SetLastBlockProcessed(tip_height, tip_hash);
     526         [ +  - ]:           1 :             BlockFilter filter;
     527   [ +  -  +  -  :           2 :             BOOST_REQUIRE(!filter_index.LookupFilter(tip, filter));
                   +  - ]
     528   [ +  -  +  - ]:           2 :         }
     529         [ +  - ]:           1 :         AddKey(wallet, coinbaseKey);
     530         [ +  - ]:           1 :         WalletRescanReserver reserver(wallet);
     531         [ +  - ]:           1 :         reserver.reserve();
     532                 :           1 :         bool fast_scan_logged{false};
     533   [ +  -  +  - ]:           4 :         DebugLogHelper scan_check{"fast variant using block filters", [&](const std::string* s) {
     534         [ +  + ]:           2 :             if (s) fast_scan_logged = true;
     535                 :           2 :             return false;
     536         [ +  - ]:           1 :         }};
     537   [ +  -  +  - ]:           1 :         ScanResult result = wallet.Scanner().Scan(genesis_hash, /*start_height=*/0, /*max_height=*/{}, reserver, /*save_progress=*/false);
     538   [ +  -  +  -  :           2 :         BOOST_REQUIRE(fast_scan_logged);
                   +  - ]
     539   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.status, ScanResult::SUCCESS);
     540   [ +  -  +  -  :           3 :         BOOST_CHECK(result.last_failed_block.IsNull());
                   +  - ]
     541   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.last_scanned_block, tip_hash);
     542   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(*result.last_scanned_height, tip_height);
     543                 :             :         // One coinbase per block from height 1 through the tip.
     544   [ +  -  +  -  :           2 :         BOOST_CHECK_EQUAL(WITH_LOCK(wallet.cs_wallet, return wallet.mapWallet.size()), static_cast<size_t>(tip_height));
                   +  - ]
     545                 :           1 :     }
     546                 :             : 
     547                 :           1 :     StopIndex(filter_index, m_node);
     548   [ +  -  +  - ]:           2 :     BOOST_REQUIRE(DestroyBlockFilterIndex(BlockFilterType::BASIC));
     549                 :           1 : }
     550                 :             : 
     551                 :             : //! Test the rescan that loading a wallet performs when the wallet is behind
     552                 :             : //! the chain tip: it scans from the wallet's recorded best block - a
     553                 :             : //! mid-chain start - with cs_wallet held.
     554   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions_attach_chain, TestChain100Setup)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     555                 :             : {
     556                 :             :     // Do not wait for sqlite to flush data to disk to improve performance
     557   [ +  -  +  - ]:           2 :     m_args.ForceSetArg("-unsafesqlitesync", "1");
     558                 :             : 
     559                 :             :     // Create a wallet owning the coinbases, and unload it at the current tip.
     560                 :           1 :     WalletContext context;
     561                 :           1 :     context.args = &m_args;
     562         [ +  - ]:           1 :     context.chain = m_node.chain.get();
     563         [ +  - ]:           1 :     auto wallet = TestCreateWallet(context);
     564         [ +  - ]:           1 :     AddKey(*wallet, coinbaseKey);
     565         [ +  - ]:           1 :     TestUnloadWallet(std::move(wallet));
     566                 :             : 
     567                 :             :     // Extend the chain while the wallet is not loaded.
     568                 :             :     constexpr int NEW_BLOCKS{5};
     569         [ +  + ]:           6 :     for (int i = 0; i < NEW_BLOCKS; ++i) {
     570   [ +  -  +  -  :          10 :         CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
                   +  - ]
     571                 :             :     }
     572                 :             : 
     573                 :           1 :     int tip_height;
     574                 :           1 :     uint256 tip_hash;
     575                 :           1 :     {
     576   [ -  +  +  - ]:           1 :         LOCK(Assert(m_node.chainman)->GetMutex());
     577   [ +  -  -  + ]:           1 :         tip_height = m_node.chainman->ActiveChain().Height();
     578   [ +  -  -  +  :           2 :         tip_hash = m_node.chainman->ActiveChain().Tip()->GetBlockHash();
                   +  - ]
     579                 :           0 :     }
     580                 :             : 
     581                 :             :     // Loading the wallet must rescan the extension from the recorded best
     582                 :             :     // block and find its coinbases.
     583   [ +  -  -  + ]:           2 :     wallet = TestLoadWallet(context);
     584                 :           1 :     {
     585         [ +  - ]:           1 :         LOCK(wallet->cs_wallet);
     586   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(wallet->GetLastBlockHeight(), tip_height);
     587   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(wallet->GetLastBlockHash(), tip_hash);
     588                 :             :         // The extension's coinbases plus the one of the recorded best block:
     589                 :             :         // the load rescan starts mid-chain, at that block inclusive.
     590   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(wallet->mapWallet.size(), static_cast<size_t>(NEW_BLOCKS + 1));
                   +  - ]
     591                 :           0 :     }
     592         [ +  - ]:           1 :     TestUnloadWallet(std::move(wallet));
     593                 :           1 : }
     594                 :             : 
     595                 :             : BOOST_AUTO_TEST_SUITE_END()
     596                 :             : 
     597                 :             : } // namespace wallet
        

Generated by: LCOV version 2.0-1