Bitcoin Core 32.99.0
P2P Digital Currency
rescan_tests.cpp
Go to the documentation of this file.
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>
8#include <interfaces/chain.h>
9#include <node/blockstorage.h>
10#include <test/util/common.h>
11#include <test/util/logging.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>
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
34
35namespace wallet {
36
37BOOST_FIXTURE_TEST_SUITE(rescan_tests, WalletTestingSetup)
38
39BOOST_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 CBlockIndex* oldTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
43 WITH_LOCK(::cs_main, m_node.chainman->m_blockman.GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE);
44 CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
45 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 {
50 {
51 LOCK(wallet.cs_wallet);
52 LOCK(Assert(m_node.chainman)->GetMutex());
53 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
54 wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
55 }
56 AddKey(wallet, coinbaseKey);
58 reserver.reserve();
59 ScanResult result = wallet.Scanner().Scan(/*start_block=*/{}, /*start_height=*/0, /*max_height=*/{}, reserver, /*save_progress=*/false);
64 BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 0);
65 }
66
67 // Verify Scan picks up transactions in both the old
68 // and new block files.
69 {
71 {
72 LOCK(wallet.cs_wallet);
73 LOCK(Assert(m_node.chainman)->GetMutex());
74 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
75 wallet.SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash());
76 }
77 AddKey(wallet, coinbaseKey);
79 std::chrono::steady_clock::time_point fake_time;
80 reserver.setNow([&] { fake_time += 60s; return fake_time; });
81 reserver.reserve();
82
83 {
84 CBlockLocator locator;
85 BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator));
86 BOOST_REQUIRE(!locator.IsNull());
87 BOOST_CHECK(locator.vHave.front() == newTip->GetBlockHash());
88 }
89
90 ScanResult result = wallet.Scanner().Scan(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*save_progress=*/true);
93 BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
94 BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
95 BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 100 * COIN);
96
97 {
98 CBlockLocator locator;
99 BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator));
100 BOOST_REQUIRE(!locator.IsNull());
101 BOOST_CHECK(locator.vHave.front() == newTip->GetBlockHash());
102 }
103 }
104
105 // Prune the older block file.
106 int file_number;
107 {
108 LOCK(cs_main);
109 file_number = oldTip->GetBlockPos().nFile;
110 Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);
111 }
112 m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});
113
114 // Verify Scan only picks transactions in the new block
115 // file.
116 {
118 {
119 LOCK(wallet.cs_wallet);
120 LOCK(Assert(m_node.chainman)->GetMutex());
121 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
122 wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
123 }
124 AddKey(wallet, coinbaseKey);
126 reserver.reserve();
127 ScanResult result = wallet.Scanner().Scan(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*save_progress=*/false);
130 BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
131 BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
132 BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 50 * COIN);
133 }
134
135 // Prune the remaining block file.
136 {
137 LOCK(cs_main);
138 file_number = newTip->GetBlockPos().nFile;
139 Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);
140 }
141 m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});
142
143 // Verify Scan scans no blocks.
144 {
146 {
147 LOCK(wallet.cs_wallet);
148 LOCK(Assert(m_node.chainman)->GetMutex());
149 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
150 wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
151 }
152 AddKey(wallet, coinbaseKey);
154 reserver.reserve();
155 ScanResult result = wallet.Scanner().Scan(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*save_progress=*/false);
157 BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash());
160 BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 0);
161 }
162}
163
164BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions_reorged_block, TestChain100Setup)
165{
166 BOOST_REQUIRE(InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1_MiB, /*f_memory=*/true));
168 BOOST_REQUIRE(filter_index.Init());
169 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 CBlockIndex* stale_block = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
175 const uint256 stale_hash{stale_block->GetBlockHash()};
176 const int stale_height{stale_block->nHeight};
178 BOOST_REQUIRE(m_node.chainman->ActiveChainstate().InvalidateBlock(state, stale_block));
179 const CScript replacement_script{GetScriptForRawPubKey(GenerateRandomKey().GetPubKey())};
180 CreateAndProcessBlock({}, replacement_script);
181 CreateAndProcessBlock({}, replacement_script);
182 BOOST_REQUIRE(filter_index.BlockUntilSyncedToCurrentChain());
183 {
184 LOCK(Assert(m_node.chainman)->GetMutex());
185 BOOST_REQUIRE(!m_node.chainman->ActiveChain().Contains(*stale_block));
186 BOOST_REQUIRE_EQUAL(m_node.chainman->ActiveChain().Height(), stale_height + 1);
187 }
188
189 {
190 BlockFilter filter;
191 BOOST_REQUIRE(filter_index.LookupFilter(stale_block, filter));
192 }
193
194 // Test wallet whose scripts do not match the stale block's filter.
195 {
197 {
198 LOCK(wallet.cs_wallet);
199 LOCK(Assert(m_node.chainman)->GetMutex());
200 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
201 wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
202 }
204 reserver.reserve();
205 ScanResult result = wallet.Scanner().Scan(stale_hash, stale_height, /*max_height=*/{}, reserver, /*save_progress=*/false);
208 BOOST_CHECK_EQUAL(result.last_scanned_block, stale_hash);
209 BOOST_CHECK_EQUAL(*result.last_scanned_height, stale_height);
210 }
211
212 // Test wallet whose scripts do match the stale block's filter.
213 {
215 {
216 LOCK(wallet.cs_wallet);
217 LOCK(Assert(m_node.chainman)->GetMutex());
218 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
219 wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
220 }
221 AddKey(wallet, coinbaseKey); // the stale block's coinbase pays coinbaseKey
223 reserver.reserve();
224 ScanResult result = wallet.Scanner().Scan(stale_hash, stale_height, /*max_height=*/{}, reserver, /*save_progress=*/false);
226 BOOST_CHECK_EQUAL(result.last_failed_block, stale_hash);
229 BOOST_CHECK(WITH_LOCK(wallet.cs_wallet, return wallet.mapWallet.empty()));
230 }
231
232 // Prune the stale block's file — the block is now not active AND unreadable.
233 int file_number;
234 {
235 LOCK(cs_main);
236 file_number = stale_block->GetBlockPos().nFile;
237 Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);
238 }
239 m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});
240
241 {
243 {
244 LOCK(wallet.cs_wallet);
245 LOCK(Assert(m_node.chainman)->GetMutex());
246 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
247 wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
248 }
249 AddKey(wallet, coinbaseKey);
251 reserver.reserve();
252 ScanResult result = wallet.Scanner().Scan(stale_hash, stale_height, /*max_height=*/{}, reserver, /*save_progress=*/false);
254 BOOST_CHECK_EQUAL(result.last_failed_block, stale_hash);
257 BOOST_CHECK(WITH_LOCK(wallet.cs_wallet, return wallet.mapWallet.empty()));
258 }
259
260 StopIndex(filter_index, m_node);
262}
263
264BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions_abort, TestChain100Setup)
265{
267 uint256 genesis_hash;
268 {
269 LOCK(wallet.cs_wallet);
270 LOCK(Assert(m_node.chainman)->GetMutex());
271 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
272 wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
273 genesis_hash = m_node.chainman->ActiveChain().Genesis()->GetBlockHash();
274 }
275
276 // An abort requested while no rescan is held is stale and must
277 // not cancel a later scan.
278 wallet.Scanner().Abort();
280 BOOST_CHECK(reserver.reserve());
281 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 wallet.Scanner().Abort();
287 ScanResult result = wallet.Scanner().Scan(genesis_hash, /*start_height=*/0, /*max_height=*/{}, reserver, /*save_progress=*/false);
292}
293
294BOOST_FIXTURE_TEST_CASE(wallet_rescan_reserver, TestingSetup)
295{
297
298 // No scan in progress: accessors report idle state.
299 BOOST_CHECK(!wallet.Scanner().IsScanning());
300 BOOST_CHECK(wallet.Scanner().ScanningDuration() == SteadyClock::duration{});
301 BOOST_CHECK_EQUAL(wallet.Scanner().ScanningProgress(), 0.0);
302
303 {
304 WalletRescanReserver first_reserver(wallet);
305 BOOST_CHECK(first_reserver.reserve());
306 BOOST_CHECK(first_reserver.isReserved());
307 BOOST_CHECK(wallet.Scanner().IsScanning());
308 BOOST_CHECK(!wallet.Scanner().IsScanningWithPassphrase());
309 BOOST_CHECK_EQUAL(wallet.Scanner().ScanningProgress(), 0.0);
310
311 // Only one reservation can be held at a time.
312 WalletRescanReserver second_reserver(wallet);
313 BOOST_CHECK(!second_reserver.reserve());
314 BOOST_CHECK(!second_reserver.isReserved());
315 }
316 // Destroying the reserver (RAII) clears the scanning state.
317 BOOST_CHECK(!wallet.Scanner().IsScanning());
318
319 {
320 WalletRescanReserver passphrase_reserver(wallet);
321 BOOST_CHECK(passphrase_reserver.reserve(/*with_passphrase=*/true));
322 BOOST_CHECK(wallet.Scanner().IsScanningWithPassphrase());
323 }
324 BOOST_CHECK(!wallet.Scanner().IsScanningWithPassphrase());
325}
326
327BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions_bounded, TestChain100Setup)
328{
329 uint256 genesis_hash, max_hash, tip_hash;
330 int max_height, tip_height;
331 {
332 LOCK(Assert(m_node.chainman)->GetMutex());
333 genesis_hash = m_node.chainman->ActiveChain().Genesis()->GetBlockHash();
334 tip_height = m_node.chainman->ActiveChain().Height();
335 tip_hash = m_node.chainman->ActiveChain().Tip()->GetBlockHash();
336 max_height = tip_height - 2;
337 max_hash = m_node.chainman->ActiveChain()[max_height]->GetBlockHash();
338 }
339
340 // A scan with max_height set stops exactly at max_height and does not
341 // sync any blocks beyond it.
342 {
344 {
345 LOCK(wallet.cs_wallet);
346 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
347 wallet.SetLastBlockProcessed(tip_height, tip_hash);
348 }
349 AddKey(wallet, coinbaseKey);
351 reserver.reserve();
352 ScanResult result = wallet.Scanner().Scan(genesis_hash, /*start_height=*/0, max_height, reserver, /*save_progress=*/false);
355 BOOST_CHECK_EQUAL(result.last_scanned_block, max_hash);
356 BOOST_CHECK_EQUAL(*result.last_scanned_height, max_height);
357 // One coinbase per block from height 1 through max_height.
358 BOOST_CHECK_EQUAL(WITH_LOCK(wallet.cs_wallet, return wallet.mapWallet.size()), static_cast<size_t>(max_height));
359 }
360
361 // A single-block range (start == max_height == tip) scans exactly that
362 // block.
363 {
365 {
366 LOCK(wallet.cs_wallet);
367 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
368 wallet.SetLastBlockProcessed(tip_height, tip_hash);
369 }
370 AddKey(wallet, coinbaseKey);
372 reserver.reserve();
373 ScanResult result = wallet.Scanner().Scan(tip_hash, tip_height, tip_height, reserver, /*save_progress=*/false);
376 BOOST_CHECK_EQUAL(result.last_scanned_block, tip_hash);
377 BOOST_CHECK_EQUAL(*result.last_scanned_height, tip_height);
378 BOOST_CHECK_EQUAL(WITH_LOCK(wallet.cs_wallet, return wallet.mapWallet.size()), 1U);
379 }
380}
381
382BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions_tip_extension, TestChain100Setup)
383{
385 uint256 genesis_hash;
386 int start_tip_height{0};
387 {
388 LOCK(wallet.cs_wallet);
389 LOCK(Assert(m_node.chainman)->GetMutex());
390 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
391 start_tip_height = m_node.chainman->ActiveChain().Height();
392 wallet.SetLastBlockProcessed(start_tip_height, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
393 genesis_hash = m_node.chainman->ActiveChain().Genesis()->GetBlockHash();
394 }
395 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 uint256 new_tip_hash;
402 bool extended{false};
403 auto handler = wallet.ShowProgress.connect([&](const std::string&, int) {
404 if (extended) return;
405 extended = true;
406 CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
407 LOCK(wallet.cs_wallet);
408 LOCK(Assert(m_node.chainman)->GetMutex());
409 const CBlockIndex* new_tip = m_node.chainman->ActiveChain().Tip();
410 new_tip_hash = new_tip->GetBlockHash();
411 wallet.SetLastBlockProcessed(new_tip->nHeight, new_tip_hash);
412 });
413
415 reserver.reserve();
416 ScanResult result = wallet.Scanner().Scan(genesis_hash, /*start_height=*/0, /*max_height=*/{}, reserver, /*save_progress=*/false);
417 handler.disconnect();
419 BOOST_CHECK_EQUAL(result.last_scanned_block, new_tip_hash);
420 BOOST_CHECK_EQUAL(*result.last_scanned_height, start_tip_height + 1);
421}
422
423BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions_no_progress_saved, TestChain100Setup)
424{
426 uint256 genesis_hash, tip_hash;
427 int max_height;
428 {
429 LOCK(wallet.cs_wallet);
430 LOCK(Assert(m_node.chainman)->GetMutex());
431 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
432 tip_hash = m_node.chainman->ActiveChain().Tip()->GetBlockHash();
433 wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), tip_hash);
434 genesis_hash = m_node.chainman->ActiveChain().Genesis()->GetBlockHash();
435 max_height = m_node.chainman->ActiveChain().Height() - 2;
436 }
437 AddKey(wallet, coinbaseKey);
438
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 std::chrono::steady_clock::time_point fake_time;
443 reserver.setNow([&] { fake_time += 60s; return fake_time; });
444 reserver.reserve();
445
446 ScanResult result = wallet.Scanner().Scan(genesis_hash, /*start_height=*/0, max_height, reserver, /*save_progress=*/false);
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 CBlockLocator locator;
453 BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator));
454 BOOST_CHECK(!locator.IsNull());
455 BOOST_CHECK_EQUAL(locator.vHave.front(), tip_hash);
456}
457
459{
460 // Cap last block file size, and mine new block in a new block file.
461 CBlockIndex* old_tip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
462 WITH_LOCK(::cs_main, m_node.chainman->m_blockman.GetBlockFileInfo(old_tip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE);
463 CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
464 CBlockIndex* new_tip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());
465
466 // Prune the older block file.
467 int file_number;
468 {
469 LOCK(cs_main);
470 file_number = old_tip->GetBlockPos().nFile;
471 Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);
472 }
473 m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});
474
476 {
477 LOCK(wallet.cs_wallet);
478 LOCK(Assert(m_node.chainman)->GetMutex());
479 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
480 wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
481 }
482 AddKey(wallet, coinbaseKey);
484 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 const int64_t genesis_time{WITH_LOCK(::cs_main, return m_node.chainman->ActiveChain().Genesis()->GetBlockTime())};
490 BOOST_CHECK_EQUAL(wallet.Scanner().ScanFromTime(genesis_time, reserver),
491 WITH_LOCK(::cs_main, return old_tip->GetBlockTimeMax()) + TIMESTAMP_WINDOW + 1);
492
493 bool scan_logged{false};
494 DebugLogHelper scan_check{"Rescan started from block", [&](const std::string* s) {
495 if (s) scan_logged = true;
496 return false;
497 }};
498 // A timestamp past the tip requires no scanning and is returned unchanged.
499 const int64_t future_time{WITH_LOCK(::cs_main, return new_tip->GetBlockTimeMax()) + TIMESTAMP_WINDOW + 1};
500 BOOST_CHECK(!scan_logged);
501 BOOST_CHECK_EQUAL(wallet.Scanner().ScanFromTime(future_time, reserver), future_time);
502}
503
504BOOST_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 BOOST_REQUIRE(InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1_MiB, /*f_memory=*/true));
511 BOOST_REQUIRE(filter_index.Init());
512
513 {
515 uint256 genesis_hash, tip_hash;
516 int tip_height;
517 {
518 LOCK(wallet.cs_wallet);
519 LOCK(Assert(m_node.chainman)->GetMutex());
520 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
521 genesis_hash = m_node.chainman->ActiveChain().Genesis()->GetBlockHash();
522 tip_height = m_node.chainman->ActiveChain().Height();
523 auto tip{m_node.chainman->ActiveChain().Tip()};
524 tip_hash = tip->GetBlockHash();
525 wallet.SetLastBlockProcessed(tip_height, tip_hash);
526 BlockFilter filter;
527 BOOST_REQUIRE(!filter_index.LookupFilter(tip, filter));
528 }
529 AddKey(wallet, coinbaseKey);
531 reserver.reserve();
532 bool fast_scan_logged{false};
533 DebugLogHelper scan_check{"fast variant using block filters", [&](const std::string* s) {
534 if (s) fast_scan_logged = true;
535 return false;
536 }};
537 ScanResult result = wallet.Scanner().Scan(genesis_hash, /*start_height=*/0, /*max_height=*/{}, reserver, /*save_progress=*/false);
538 BOOST_REQUIRE(fast_scan_logged);
541 BOOST_CHECK_EQUAL(result.last_scanned_block, tip_hash);
542 BOOST_CHECK_EQUAL(*result.last_scanned_height, tip_height);
543 // One coinbase per block from height 1 through the tip.
544 BOOST_CHECK_EQUAL(WITH_LOCK(wallet.cs_wallet, return wallet.mapWallet.size()), static_cast<size_t>(tip_height));
545 }
546
547 StopIndex(filter_index, m_node);
549}
550
554BOOST_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 m_args.ForceSetArg("-unsafesqlitesync", "1");
558
559 // Create a wallet owning the coinbases, and unload it at the current tip.
561 context.args = &m_args;
562 context.chain = m_node.chain.get();
564 AddKey(*wallet, coinbaseKey);
565 TestUnloadWallet(std::move(wallet));
566
567 // Extend the chain while the wallet is not loaded.
568 constexpr int NEW_BLOCKS{5};
569 for (int i = 0; i < NEW_BLOCKS; ++i) {
570 CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
571 }
572
573 int tip_height;
574 uint256 tip_hash;
575 {
576 LOCK(Assert(m_node.chainman)->GetMutex());
577 tip_height = m_node.chainman->ActiveChain().Height();
578 tip_hash = m_node.chainman->ActiveChain().Tip()->GetBlockHash();
579 }
580
581 // Loading the wallet must rescan the extension from the recorded best
582 // block and find its coinbases.
584 {
585 LOCK(wallet->cs_wallet);
586 BOOST_CHECK_EQUAL(wallet->GetLastBlockHeight(), tip_height);
587 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 BOOST_CHECK_EQUAL(wallet->mapWallet.size(), static_cast<size_t>(NEW_BLOCKS + 1));
591 }
592 TestUnloadWallet(std::move(wallet));
593}
594
596
597} // namespace wallet
constexpr CAmount COIN
The amount of satoshis in one BTC.
Definition: amount.h:15
node::NodeContext m_node
Definition: bitcoin-gui.cpp:48
bool DestroyBlockFilterIndex(BlockFilterType filter_type)
Destroy the block filter index with the given type.
BlockFilterIndex * GetBlockFilterIndex(BlockFilterType filter_type)
Get a block filter index by type.
bool InitBlockFilterIndex(std::function< std::unique_ptr< interfaces::Chain >()> make_chain, BlockFilterType filter_type, size_t n_cache_size, bool f_memory, bool f_wipe)
Initialize a block filter index for the given type if one does not already exist.
constexpr int64_t TIMESTAMP_WINDOW
Timestamp window used as a grace period by code that compares external timestamps (such as timestamps...
Definition: chain.h:37
#define Assert(val)
Identity function.
Definition: check.h:116
Complete block filter struct as defined in BIP 157.
Definition: blockfilter.h:116
BlockFilterIndex is used to store and retrieve block filters, hashes, and headers for a range of bloc...
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: chain.h:94
uint256 GetBlockHash() const
Definition: chain.h:198
int64_t GetBlockTimeMax() const
Definition: chain.h:226
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition: chain.h:106
FlatFilePos GetBlockPos() const EXCLUSIVE_LOCKS_REQUIRED(
Definition: chain.h:163
Serialized script, used inside transaction inputs and outputs.
Definition: script.h:406
constexpr bool IsNull() const
Definition: uint256.h:50
256-bit opaque blob.
Definition: uint256.h:196
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition: wallet.h:318
Access to the wallet database.
Definition: walletdb.h:199
RAII object to check and reserve a wallet rescan.
Definition: scan.h:37
bool isReserved() const
Definition: scan.cpp:49
void setNow(NowFn now)
Definition: scan.h:52
bool reserve(bool with_passphrase=false)
Definition: scan.cpp:40
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
Definition: cs_main.cpp:8
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_EQUAL(headers.FindFirst("key"), "value")
CKey GenerateRandomKey(bool compressed) noexcept
Definition: key.cpp:354
std::unique_ptr< Chain > MakeChain(node::NodeContext &node)
Return implementation of Chain interface.
constexpr unsigned int MAX_BLOCKFILE_SIZE
The maximum size of a blk?????.dat file (since 0.8)
Definition: blockstorage.h:124
std::unique_ptr< WalletDatabase > CreateMockableWalletDatabase()
Definition: util.cpp:126
BOOST_FIXTURE_TEST_CASE(wallet_coinsresult_test, BasicTestingSetup)
void TestUnloadWallet(std::shared_ptr< CWallet > &&wallet)
Definition: util.cpp:103
void AddKey(CWallet &wallet, const CKey &key)
Definition: util.cpp:45
std::shared_ptr< CWallet > TestLoadWallet(std::unique_ptr< WalletDatabase > database, WalletContext &context)
Definition: util.cpp:81
Balance GetBalance(const CWallet &wallet, const int min_depth, bool avoid_reuse, bool include_nonmempool)
Definition: receive.cpp:245
std::shared_ptr< CWallet > TestCreateWallet(std::unique_ptr< WalletDatabase > database, WalletContext &context, uint64_t create_flags)
Definition: util.cpp:57
@ WALLET_FLAG_DESCRIPTORS
Indicate that this wallet supports DescriptorScriptPubKeyMan.
Definition: walletutil.h:53
#define BOOST_CHECK(expr)
Definition: object.cpp:16
bool(* handler)(const std::any &context, HTTPRequest *req, const std::string &strReq)
Definition: rest.cpp:1198
static bool GetPubKey(const SigningProvider &provider, const SignatureData &sigdata, const CKeyID &address, CPubKey &pubkey)
Definition: sign.cpp:243
CScript GetScriptForRawPubKey(const CPubKey &pubKey)
Generate a P2PK script for the given pubkey.
Definition: solver.cpp:213
Describes a place in the block chain to another node such that if the other node doesn't have the sam...
Definition: block.h:117
std::vector< uint256 > vHave
Definition: block.h:127
bool IsNull() const
Definition: block.h:145
int32_t nFile
Definition: flatfile.h:16
Testing fixture that pre-creates a 100-block REGTEST-mode block chain.
Definition: setup_common.h:143
Testing setup that configures a complete environment.
Definition: setup_common.h:119
std::unique_ptr< ChainstateManager > chainman
Definition: context.h:74
std::unique_ptr< interfaces::Chain > chain
Definition: context.h:78
Result of a wallet scan.
Definition: scan.h:19
uint256 last_scanned_block
Hash and height of most recent block that was successfully scanned.
Definition: scan.h:25
enum wallet::ScanResult::@19 status
std::optional< int > last_scanned_height
Definition: scan.h:26
uint256 last_failed_block
Height of the most recent block that could not be scanned due to read errors or pruning.
Definition: scan.h:32
WalletContext struct containing references to state shared between CWallet instances,...
Definition: context.h:36
interfaces::Chain * chain
Definition: context.h:37
ArgsManager * args
Definition: context.h:39
#define LOCK(cs)
Definition: sync.h:268
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:299
void StopIndex(BaseIndex &index, node::NodeContext &node)
Drain validation callbacks before stopping an index, matching Shutdown() in init.cpp.
Definition: validation.cpp:158
std::shared_ptr< CWallet > wallet
WalletContext context