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.
251{252auto chain = m_node.chain.get();253const auto NUM_WALLETS{5};255// Since we're counting the number of wallets, ensure we start without any.256BOOST_REQUIRE(chain->getRwSetting("wallet").isNull());258const auto& check_concurrent_wallet = [&](const auto& settings_function, int num_expected_wallets) {259std::vector<std::thread> threads;260threads.reserve(NUM_WALLETS);261for (auto i{0}; i < NUM_WALLETS; ++i) threads.emplace_back(settings_function, i);262for (auto& t : threads) t.join();264auto wallets = chain->getRwSetting("wallet");265BOOST_CHECK_EQUAL(wallets.getValues().size(), num_expected_wallets);266};268// Add NUM_WALLETS wallets concurrently, ensure we end up with NUM_WALLETS stored.269check_concurrent_wallet([&chain](int i) {270Assert(AddWalletSetting(*chain, strprintf("wallet_%d", i)));271},272/*num_expected_wallets=*/NUM_WALLETS);274// Remove NUM_WALLETS wallets concurrently, ensure we end up with 0 wallets.275check_concurrent_wallet([&chain](int i) {276Assert(RemoveWalletSetting(*chain, strprintf("wallet_%d", i)));277},278/*num_expected_wallets=*/0);279}281static int64_t AddTx(ChainstateManager& chainman, CWallet& wallet, uint32_t lockTime, std::chrono::seconds mock_time, int64_t blockTime)282{283CMutableTransaction tx;284TxState state = TxStateInactive{};285tx.nLockTime = lockTime;286FakeNodeClock clock{mock_time};287CBlockIndex* block = nullptr;288if (blockTime > 0) {289LOCK(cs_main);290auto inserted = chainman.BlockIndex().emplace(std::piecewise_construct, std::make_tuple(GetRandHash()), std::make_tuple());291assert(inserted.second);292const uint256& hash = inserted.first->first;293block = &inserted.first->second;294block->nTime = blockTime;295block->phashBlock = &hash;296state = TxStateConfirmed{hash, block->nHeight, /*index=*/0};297}298return wallet.AddToWallet(MakeTransactionRef(tx), state, [&](CWalletTx& wtx, bool /* new_tx */) {299// Assign wtx.m_state to simplify test and avoid the need to simulate300// reorg events. Without this, AddToWallet asserts false when the same301// transaction is confirmed in different blocks.302wtx.m_state = state;303return true;304})->nTimeSmart;305}307// Simple test to verify assignment of CWalletTx::nSmartTime value. Could be308// expanded to cover more corner cases of smart time logic.309BOOST_AUTO_TEST_CASE(ComputeTimeSmart)310{311// New transaction should use clock time if lower than block time.312BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 1, 100s, 120), 100);314// Test that updating existing transaction does not change smart time.315BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 1, 200s, 220), 100);317// New transaction should use clock time if there's no block time.318BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 2, 300s, 0), 300);320// New transaction should use block time if lower than clock time.321BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 3, 420s, 400), 400);323// New transaction should use latest entry time if higher than324// min(block time, clock time).325BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 4, 500s, 390), 400);327// If there are future entries, new transaction should use time of the328// newest entry that is no more than 300 seconds ahead of the clock time.329BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 5, 50s, 600), 300);330}332void TestLoadWallet(const std::string& name, DatabaseFormat format, std::function<void(std::shared_ptr<CWallet>)> f)333{334node::NodeContext node;335auto chain{interfaces::MakeChain(node)};336DatabaseOptions options;337options.require_format = format;338DatabaseStatus status;339bilingual_str error;340std::vector<bilingual_str> warnings;341auto database{MakeWalletDatabase(name, options, status, error)};342auto wallet{std::make_shared<CWallet>(chain.get(), "", std::move(database))};343BOOST_CHECK_EQUAL(wallet->PopulateWalletFromDB(error, warnings), DBErrors::LOAD_OK);344WITH_LOCK(wallet->cs_wallet, f(wallet));345}347BOOST_FIXTURE_TEST_CASE(LoadReceiveRequests, TestingSetup)348{349for (DatabaseFormat format : DATABASE_FORMATS) {350const std::string name{strprintf("receive-requests-%i", format)};351TestLoadWallet(name, format, [](std::shared_ptr<CWallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) {352BOOST_CHECK(!wallet->IsAddressPreviouslySpent(PKHash()));353WalletBatch batch{wallet->GetDatabase()};354BOOST_CHECK(batch.WriteAddressPreviouslySpent(PKHash(), true));355BOOST_CHECK(batch.WriteAddressPreviouslySpent(ScriptHash(), true));356BOOST_CHECK(wallet->SetAddressReceiveRequest(batch, PKHash(), "0", "val_rr00"));357BOOST_CHECK(wallet->EraseAddressReceiveRequest(batch, PKHash(), "0"));358BOOST_CHECK(wallet->SetAddressReceiveRequest(batch, PKHash(), "1", "val_rr10"));359BOOST_CHECK(wallet->SetAddressReceiveRequest(batch, PKHash(), "1", "val_rr11"));360BOOST_CHECK(wallet->SetAddressReceiveRequest(batch, ScriptHash(), "2", "val_rr20"));361});362TestLoadWallet(name, format, [](std::shared_ptr<CWallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) {363BOOST_CHECK(wallet->IsAddressPreviouslySpent(PKHash()));364BOOST_CHECK(wallet->IsAddressPreviouslySpent(ScriptHash()));365auto requests = wallet->GetAddressReceiveRequests();366auto erequests = {"val_rr11", "val_rr20"};367BOOST_CHECK_EQUAL_COLLECTIONS(requests.begin(), requests.end(), std::begin(erequests), std::end(erequests));368RunWithinTxn(wallet->GetDatabase(), /*process_desc=*/"test", [](WalletBatch& batch){369BOOST_CHECK(batch.WriteAddressPreviouslySpent(PKHash(), false));370BOOST_CHECK(batch.EraseAddressData(ScriptHash()));371return true;372});373});374TestLoadWallet(name, format, [](std::shared_ptr<CWallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) {375BOOST_CHECK(!wallet->IsAddressPreviouslySpent(PKHash()));376BOOST_CHECK(!wallet->IsAddressPreviouslySpent(ScriptHash()));377auto requests = wallet->GetAddressReceiveRequests();378auto erequests = {"val_rr11"};379BOOST_CHECK_EQUAL_COLLECTIONS(requests.begin(), requests.end(), std::begin(erequests), std::end(erequests));380});381}382}384class ListCoinsTestingSetup : public TestChain100Setup385{386public:387ListCoinsTestingSetup()388{389CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));390wallet = CreateSyncedWallet(*m_node.chain, WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain()), coinbaseKey);391}393~ListCoinsTestingSetup()394{395wallet.reset();396}398CWalletTx& AddTx(CRecipient recipient)399{400CTransactionRef tx;401CCoinControl dummy;402{403auto res = CreateTransaction(*wallet, {recipient}, /*change_pos=*/std::nullopt, dummy);404BOOST_CHECK(res);405tx = res->tx;406}407wallet->CommitTransaction(tx);408CMutableTransaction blocktx;409{410LOCK(wallet->cs_wallet);411blocktx = CMutableTransaction(*wallet->mapWallet.at(tx->GetHash()).GetTx());412}413CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));415LOCK(wallet->cs_wallet);416LOCK(Assert(m_node.chainman)->GetMutex());417wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash());418auto it = wallet->mapWallet.find(tx->GetHash());419BOOST_CHECK(it != wallet->mapWallet.end());420it->second.m_state = TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*index=*/1};421return it->second;422}424std::unique_ptr<CWallet> wallet;425};427BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)428{429std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString();431// Confirm ListCoins initially returns 1 coin grouped under coinbaseKey432// address.433std::map<CTxDestination, std::vector<COutput>> list;434{435LOCK(wallet->cs_wallet);436list = ListCoins(*wallet);437}438BOOST_CHECK_EQUAL(list.size(), 1U);439BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);440BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U);442// Check initial balance from one mature coinbase transaction.443BOOST_CHECK_EQUAL(50 * COIN, WITH_LOCK(wallet->cs_wallet, return AvailableCoins(*wallet).GetTotalAmount()));445// Add a transaction creating a change address, and confirm ListCoins still446// returns the coin associated with the change address underneath the447// coinbaseKey pubkey, even though the change address has a different448// pubkey.449AddTx(CRecipient{PubKeyDestination{{}}, 1 * COIN, /*subtract_fee=*/false});450{451LOCK(wallet->cs_wallet);452list = ListCoins(*wallet);453}454BOOST_CHECK_EQUAL(list.size(), 1U);455BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);456BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U);458// Lock both coins. Confirm number of available coins drops to 0.459{460LOCK(wallet->cs_wallet);461BOOST_CHECK_EQUAL(AvailableCoins(*wallet).Size(), 2U);462}463for (const auto& group : list) {464for (const auto& coin : group.second) {465LOCK(wallet->cs_wallet);466wallet->LockCoin(coin.outpoint, /*persist=*/false);467}468}469{470LOCK(wallet->cs_wallet);471BOOST_CHECK_EQUAL(AvailableCoins(*wallet).Size(), 0U);472}473// Confirm ListCoins still returns same result as before, despite coins474// being locked.475{476LOCK(wallet->cs_wallet);477list = ListCoins(*wallet);478}479BOOST_CHECK_EQUAL(list.size(), 1U);480BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);481BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U);482}484void TestCoinsResult(ListCoinsTest& context, OutputType out_type, CAmount amount,485std::map<OutputType, size_t>& expected_coins_sizes)486{487LOCK(context.wallet->cs_wallet);488util::Result<CTxDestination> dest = Assert(context.wallet->GetNewDestination(out_type, ""));489CWalletTx& wtx = context.AddTx(CRecipient{*dest, amount, /*fSubtractFeeFromAmount=*/true});490CoinFilterParams filter;491filter.skip_locked = false;492CoinsResult available_coins = AvailableCoins(*context.wallet, nullptr, std::nullopt, filter);493// Lock outputs so they are not spent in follow-up transactions494for (uint32_t i = 0; i < wtx.GetTx()->vout.size(); i++) context.wallet->LockCoin({wtx.GetHash(), i}, /*persist=*/false);495for (const auto& [type, size] : expected_coins_sizes) BOOST_CHECK_EQUAL(size, available_coins.coins[type].size());496}498BOOST_FIXTURE_TEST_CASE(BasicOutputTypesTest, ListCoinsTest)499{500std::map<OutputType, size_t> expected_coins_sizes;
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.