Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2012-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <wallet/wallet.h>7#include <cstdint>8#include <future>9#include <memory>10#include <vector>12#include <addresstype.h>13#include <interfaces/chain.h>14#include <key_io.h>15#include <node/blockstorage.h>16#include <node/types.h>17#include <policy/policy.h>18#include <rpc/server.h>19#include <script/solver.h>20#include <test/util/common.h>21#include <test/util/logging.h>22#include <test/util/random.h>23#include <test/util/setup_common.h>24#include <util/translation.h>25#include <validation.h>26#include <validationinterface.h>27#include <wallet/coincontrol.h>28#include <wallet/context.h>29#include <wallet/receive.h>30#include <wallet/spend.h>31#include <wallet/test/util.h>32#include <wallet/test/wallet_test_fixture.h>34#include <boost/test/unit_test.hpp>35#include <univalue.h>37using node::MAX_BLOCKFILE_SIZE;39namespace wallet {41// Ensure that fee levels defined in the wallet are at least as high42// as the default levels for node policy.43static_assert(DEFAULT_TRANSACTION_MINFEE >= DEFAULT_MIN_RELAY_TX_FEE, "wallet minimum fee is smaller than default relay fee");44static_assert(WALLET_INCREMENTAL_RELAY_FEE >= DEFAULT_INCREMENTAL_RELAY_FEE, "wallet incremental fee is smaller than default incremental relay fee");46BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)48static CMutableTransaction TestSimpleSpend(const CTransaction& from, uint32_t index, const CKey& key, const CScript& pubkey)49{50CMutableTransaction mtx;51mtx.vout.emplace_back(from.vout[index].nValue - DEFAULT_TRANSACTION_MAXFEE, pubkey);52mtx.vin.push_back({CTxIn{from.GetHash(), index}});53FillableSigningProvider keystore;54keystore.AddKey(key);55std::map<COutPoint, Coin> coins;56coins[mtx.vin[0].prevout].out = from.vout[index];57std::map<int, bilingual_str> input_errors;58BOOST_CHECK(SignTransaction(mtx, &keystore, coins, {.sighash_type = SIGHASH_ALL}, input_errors));59return mtx;60}62static void AddKey(CWallet& wallet, const CKey& key)63{64LOCK(wallet.cs_wallet);65FlatSigningProvider provider;66std::string error;67auto descs = Parse("combo(" + EncodeSecret(key) + ")", provider, error, /* require_checksum=*/ false);68assert(descs.size() == 1);69auto& desc = descs.at(0);70WalletDescriptor w_desc(std::move(desc), 0, 0, 1, 1);71Assert(wallet.AddWalletDescriptor(w_desc, provider, "", false));72}74BOOST_FIXTURE_TEST_CASE(update_non_range_descriptor, TestingSetup)75{76CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());77{78LOCK(wallet.cs_wallet);79wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);80auto key{GenerateRandomKey()};81auto desc_str{"combo(" + EncodeSecret(key) + ")"};82FlatSigningProvider provider;83std::string error;84auto descs{Parse(desc_str, provider, error, /* require_checksum=*/ false)};85auto& desc{descs.at(0)};86WalletDescriptor w_desc{std::move(desc), 0, 0, 0, 0};87BOOST_CHECK(wallet.AddWalletDescriptor(w_desc, provider, "", false));88// Wallet should update the non-range descriptor successfully89BOOST_CHECK(wallet.AddWalletDescriptor(w_desc, provider, "", false));90}91}93BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)94{95// Cap last block file size, and mine new block in a new block file.96CBlockIndex* oldTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());97WITH_LOCK(::cs_main, m_node.chainman->m_blockman.GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE);98CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));99CBlockIndex* newTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip());101// Verify ScanForWalletTransactions fails to read an unknown start block.102{103CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());104{105LOCK(wallet.cs_wallet);106LOCK(Assert(m_node.chainman)->GetMutex());107wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);108wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());109}110AddKey(wallet, coinbaseKey);111WalletRescanReserver reserver(wallet);112reserver.reserve();113CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/{}, /*start_height=*/0, /*max_height=*/{}, reserver, /*save_progress=*/false);114BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);115BOOST_CHECK(result.last_failed_block.IsNull());116BOOST_CHECK(result.last_scanned_block.IsNull());117BOOST_CHECK(!result.last_scanned_height);118BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 0);119}121// Verify ScanForWalletTransactions picks up transactions in both the old122// and new block files.123{124CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());125{126LOCK(wallet.cs_wallet);127LOCK(Assert(m_node.chainman)->GetMutex());128wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);129wallet.SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash());130}131AddKey(wallet, coinbaseKey);132WalletRescanReserver reserver(wallet);133std::chrono::steady_clock::time_point fake_time;134reserver.setNow([&] { fake_time += 60s; return fake_time; });135reserver.reserve();137{138CBlockLocator locator;139BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator));140BOOST_REQUIRE(!locator.IsNull());141BOOST_CHECK(locator.vHave.front() == newTip->GetBlockHash());142}144CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*save_progress=*/true);145BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);146BOOST_CHECK(result.last_failed_block.IsNull());147BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());148BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);149BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 100 * COIN);151{152CBlockLocator locator;153BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator));154BOOST_REQUIRE(!locator.IsNull());155BOOST_CHECK(locator.vHave.front() == newTip->GetBlockHash());156}157}159// Prune the older block file.160int file_number;161{162LOCK(cs_main);163file_number = oldTip->GetBlockPos().nFile;164Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);165}166m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});168// Verify ScanForWalletTransactions only picks transactions in the new block169// file.170{171CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());172{173LOCK(wallet.cs_wallet);174LOCK(Assert(m_node.chainman)->GetMutex());175wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);176wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());177}178AddKey(wallet, coinbaseKey);179WalletRescanReserver reserver(wallet);180reserver.reserve();181CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*save_progress=*/false);182BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);183BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash());184BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());185BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);186BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 50 * COIN);187}189// Prune the remaining block file.190{191LOCK(cs_main);192file_number = newTip->GetBlockPos().nFile;193Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);194}195m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});197// Verify ScanForWalletTransactions scans no blocks.198{199CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());200{201LOCK(wallet.cs_wallet);202LOCK(Assert(m_node.chainman)->GetMutex());203wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);204wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());205}206AddKey(wallet, coinbaseKey);207WalletRescanReserver reserver(wallet);208reserver.reserve();209CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*save_progress=*/false);210BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);211BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash());212BOOST_CHECK(result.last_scanned_block.IsNull());213BOOST_CHECK(!result.last_scanned_height);214BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 0);215}216}218BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions_abort, TestChain100Setup)219{220CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());221uint256 genesis_hash;222{223LOCK(wallet.cs_wallet);224LOCK(Assert(m_node.chainman)->GetMutex());225wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);226wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());227genesis_hash = m_node.chainman->ActiveChain().Genesis()->GetBlockHash();228}230// An abort requested while no rescan is held is stale and must231// not cancel a later scan.232wallet.AbortRescan();233WalletRescanReserver reserver(wallet);234BOOST_CHECK(reserver.reserve());235BOOST_CHECK(!wallet.IsAbortingRescan());237// An abort requested after the reservation but before the scan starts238// (e.g. while importdescriptors is still deriving keys) must cancel the239// scan.240wallet.AbortRescan();241CWallet::ScanResult result = wallet.ScanForWalletTransactions(genesis_hash, /*start_height=*/0, /*max_height=*/{}, reserver, /*save_progress=*/false);242BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::USER_ABORT);243BOOST_CHECK(result.last_scanned_block.IsNull());244BOOST_CHECK(!result.last_scanned_height);245BOOST_CHECK(result.last_failed_block.IsNull());246}248// This test verifies that wallet settings can be added and removed249// concurrently, ensuring no race conditions occur during either process.250BOOST_FIXTURE_TEST_CASE(write_wallet_settings_concurrently, TestingSetup)
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.