diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 98dfe12f084..913a938e541 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -201,7 +201,7 @@ std::shared_ptr SetupLegacyWatchOnlyWallet(interfaces::Node& node, Test CPubKey pubKey = test.coinbaseKey.GetPubKey(); bool import_keys = wallet->ImportPubKeys({{pubKey.GetID(), false}}, {{pubKey.GetID(), pubKey}} , /*key_origins=*/{}, /*add_keypool=*/false, /*timestamp=*/1); assert(import_keys); - wallet->SetLastBlockProcessed(105, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash())); + wallet->SetBestBlock(105, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash())); } SyncUpWallet(wallet, node); return wallet; @@ -226,7 +226,7 @@ std::shared_ptr SetupDescriptorsWallet(interfaces::Node& node, TestChai if (!wallet->AddWalletDescriptor(w_desc, provider, "", false)) assert(false); CTxDestination dest = GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type); wallet->SetAddressBook(dest, "", wallet::AddressPurpose::RECEIVE); - wallet->SetLastBlockProcessed(105, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash())); + wallet->SetBestBlock(105, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash())); SyncUpWallet(wallet, node); wallet->SetBroadcastTransactions(true); return wallet; diff --git a/src/test/fuzz/util/wallet.h b/src/test/fuzz/util/wallet.h index 1f04b5dbf1a..7810dc6a1a5 100644 --- a/src/test/fuzz/util/wallet.h +++ b/src/test/fuzz/util/wallet.h @@ -29,7 +29,7 @@ struct FuzzedWallet { LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); auto height{*Assert(chain.getHeight())}; - wallet->SetLastBlockProcessed(height, chain.getBlockHash(height)); + wallet->SetBestBlock(height, chain.getBlockHash(height)); } wallet->m_keypool_size = 1; // Avoid timeout in TopUp() assert(wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 21e8a0b3bd2..b004a22b305 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -368,9 +368,9 @@ public: if (mi == m_wallet->mapWallet.end()) { return false; } - num_blocks = m_wallet->GetLastBlockHeight(); + num_blocks = m_wallet->GetBestBlockHeight(); block_time = -1; - CHECK_NONFATAL(m_wallet->chain().findBlock(m_wallet->GetLastBlockHash(), FoundBlock().time(block_time))); + CHECK_NONFATAL(m_wallet->chain().findBlock(m_wallet->GetBestBlockHash(), FoundBlock().time(block_time))); tx_status = MakeWalletTxStatus(*m_wallet, mi->second); return true; } @@ -383,7 +383,7 @@ public: LOCK(m_wallet->cs_wallet); auto mi = m_wallet->mapWallet.find(txid); if (mi != m_wallet->mapWallet.end()) { - num_blocks = m_wallet->GetLastBlockHeight(); + num_blocks = m_wallet->GetBestBlockHeight(); in_mempool = mi->second.InMempool(); order_form = mi->second.vOrderForm; tx_status = MakeWalletTxStatus(*m_wallet, mi->second); @@ -421,7 +421,7 @@ public: if (!locked_wallet) { return false; } - block_hash = m_wallet->GetLastBlockHash(); + block_hash = m_wallet->GetBestBlockHash(); balances = getBalances(); return true; } @@ -572,7 +572,7 @@ public: m_context.chain = &chain; m_context.args = &args; } - ~WalletLoaderImpl() override { UnloadWallets(m_context); } + ~WalletLoaderImpl() override { CloseWallets(m_context); } //! ChainClient methods void registerRpcs() override @@ -594,7 +594,7 @@ public: return StartWallets(m_context); } void flush() override { return FlushWallets(m_context); } - void stop() override { return StopWallets(m_context); } + void stop() override { return UnloadWallets(m_context); } void setMockTime(int64_t time) override { return SetMockTime(time); } void schedulerMockForward(std::chrono::seconds delta) override { Assert(m_context.scheduler)->MockForward(delta); } diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index e77999b1115..3685da59e71 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -173,7 +173,7 @@ void FlushWallets(WalletContext& context) } } -void StopWallets(WalletContext& context) +void CloseWallets(WalletContext& context) { for (const std::shared_ptr& pwallet : GetWallets(context)) { pwallet->Close(); diff --git a/src/wallet/load.h b/src/wallet/load.h index c079cad955b..67542c164f6 100644 --- a/src/wallet/load.h +++ b/src/wallet/load.h @@ -31,10 +31,10 @@ void StartWallets(WalletContext& context); //! Flush all wallets in preparation for shutdown. void FlushWallets(WalletContext& context); -//! Stop all wallets. Wallets will be flushed first. -void StopWallets(WalletContext& context); +//! Close all wallet databases. +void CloseWallets(WalletContext& context); -//! Close all wallets. +//! Flush and unload all wallets. void UnloadWallets(WalletContext& context); } // namespace wallet diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index ac23b092d40..4636fce78ae 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -107,7 +107,7 @@ static void EnsureBlockDataFromTime(const CWallet& wallet, int64_t timestamp) int height{0}; const bool found{chain.findFirstBlockWithTimeAndHeight(timestamp - TIMESTAMP_WINDOW, 0, FoundBlock().height(height))}; - uint256 tip_hash{WITH_LOCK(wallet.cs_wallet, return wallet.GetLastBlockHash())}; + uint256 tip_hash{WITH_LOCK(wallet.cs_wallet, return wallet.GetBestBlockHash())}; if (found && !chain.hasBlocks(tip_hash, height)) { throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Pruned blocks from height %d required to import keys. Use RPC call getblockchaininfo to determine your pruned height.", height)); } @@ -352,7 +352,7 @@ RPCHelpMan importprunedfunds() LOCK(pwallet->cs_wallet); int height; - if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) { + if (!pwallet->chain().findAncestorByHash(pwallet->GetBestBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); } @@ -527,7 +527,7 @@ RPCHelpMan importwallet() if (!file.is_open()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); } - CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nTimeBegin))); + CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetBestBlockHash(), FoundBlock().time(nTimeBegin))); int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); file.seekg(0, file.beg); @@ -738,7 +738,7 @@ RPCHelpMan dumpwallet() wallet.GetKeyBirthTimes(mapKeyBirth); int64_t block_time = 0; - CHECK_NONFATAL(wallet.chain().findBlock(wallet.GetLastBlockHash(), FoundBlock().time(block_time))); + CHECK_NONFATAL(wallet.chain().findBlock(wallet.GetBestBlockHash(), FoundBlock().time(block_time))); // Note: To avoid a lock order issue, access to cs_main must be locked before cs_KeyStore. // So we do the two things in this function that lock cs_main first: GetKeyBirthTimes, and findBlock. @@ -759,7 +759,7 @@ RPCHelpMan dumpwallet() // produce output file << strprintf("# Wallet dump created by %s %s\n", CLIENT_NAME, FormatFullVersion()); file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime())); - file << strprintf("# * Best block at time of backup was %i (%s),\n", wallet.GetLastBlockHeight(), wallet.GetLastBlockHash().ToString()); + file << strprintf("# * Best block at time of backup was %i (%s),\n", wallet.GetBestBlockHeight(), wallet.GetBestBlockHash().ToString()); file << strprintf("# mined on %s\n", FormatISO8601DateTime(block_time)); file << "\n"; @@ -1379,7 +1379,7 @@ RPCHelpMan importmulti() if (!is_watchonly) EnsureWalletIsUnlocked(wallet); // Verify all timestamps are present before importing any keys. - CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nLowestTimestamp).mtpTime(now))); + CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetBestBlockHash(), FoundBlock().time(nLowestTimestamp).mtpTime(now))); for (const UniValue& data : requests.getValues()) { GetImportTimestamp(data, now); } @@ -1699,7 +1699,7 @@ RPCHelpMan importdescriptors() LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(*pwallet); - CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now))); + CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetBestBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now))); // Get all timestamps and extract the lowest timestamp for (const UniValue& request : requests.getValues()) { diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp index f9a39f679e6..e531138df93 100644 --- a/src/wallet/rpc/transactions.cpp +++ b/src/wallet/rpc/transactions.cpp @@ -622,7 +622,7 @@ RPCHelpMan listsinceblock() blockId = ParseHashV(request.params[0], "blockhash"); height = int{}; altheight = int{}; - if (!wallet.chain().findCommonAncestor(blockId, wallet.GetLastBlockHash(), /*ancestor_out=*/FoundBlock().height(*height), /*block1_out=*/FoundBlock().height(*altheight))) { + if (!wallet.chain().findCommonAncestor(blockId, wallet.GetBestBlockHash(), /*ancestor_out=*/FoundBlock().height(*height), /*block1_out=*/FoundBlock().height(*altheight))) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } @@ -646,7 +646,7 @@ RPCHelpMan listsinceblock() std::optional filter_label; if (!request.params[5].isNull()) filter_label.emplace(LabelFromValue(request.params[5])); - int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1; + int depth = height ? wallet.GetBestBlockHeight() + 1 - *height : -1; UniValue transactions(UniValue::VARR); @@ -679,8 +679,8 @@ RPCHelpMan listsinceblock() } uint256 lastblock; - target_confirms = std::min(target_confirms, wallet.GetLastBlockHeight() + 1); - CHECK_NONFATAL(wallet.chain().findAncestorByHeight(wallet.GetLastBlockHash(), wallet.GetLastBlockHeight() + 1 - target_confirms, FoundBlock().hash(lastblock))); + target_confirms = std::min(target_confirms, wallet.GetBestBlockHeight() + 1); + CHECK_NONFATAL(wallet.chain().findAncestorByHeight(wallet.GetBestBlockHash(), wallet.GetBestBlockHeight() + 1 - target_confirms, FoundBlock().hash(lastblock))); UniValue ret(UniValue::VOBJ); ret.pushKV("transactions", std::move(transactions)); @@ -892,7 +892,7 @@ RPCHelpMan rescanblockchain() { LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(*pwallet); - int tip_height = pwallet->GetLastBlockHeight(); + int tip_height = pwallet->GetBestBlockHeight(); if (!request.params[0].isNull()) { start_height = request.params[0].getInt(); @@ -911,7 +911,7 @@ RPCHelpMan rescanblockchain() } // We can't rescan unavailable blocks, stop and throw an error - if (!pwallet->chain().hasBlocks(pwallet->GetLastBlockHash(), start_height, stop_height)) { + if (!pwallet->chain().hasBlocks(pwallet->GetBestBlockHash(), start_height, stop_height)) { if (pwallet->chain().havePruned() && pwallet->chain().getPruneHeight() >= start_height) { throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height."); } @@ -921,7 +921,7 @@ RPCHelpMan rescanblockchain() throw JSONRPCError(RPC_MISC_ERROR, "Failed to rescan unavailable blocks, potentially caused by data corruption. If the issue persists you may want to reindex (see -reindex option)."); } - CHECK_NONFATAL(pwallet->chain().findAncestorByHeight(pwallet->GetLastBlockHash(), start_height, FoundBlock().hash(start_block))); + CHECK_NONFATAL(pwallet->chain().findAncestorByHeight(pwallet->GetBestBlockHash(), start_height, FoundBlock().hash(start_block))); } CWallet::ScanResult result = diff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp index 414f0deeb2d..c92f73957f5 100644 --- a/src/wallet/rpc/util.cpp +++ b/src/wallet/rpc/util.cpp @@ -167,8 +167,8 @@ void AppendLastProcessedBlock(UniValue& entry, const CWallet& wallet) { AssertLockHeld(wallet.cs_wallet); UniValue lastprocessedblock{UniValue::VOBJ}; - lastprocessedblock.pushKV("hash", wallet.GetLastBlockHash().GetHex()); - lastprocessedblock.pushKV("height", wallet.GetLastBlockHeight()); + lastprocessedblock.pushKV("hash", wallet.GetBestBlockHash().GetHex()); + lastprocessedblock.pushKV("height", wallet.GetBestBlockHeight()); entry.pushKV("lastprocessedblock", std::move(lastprocessedblock)); } diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 7b26cf15bd3..13d931570eb 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -1246,7 +1246,7 @@ static util::Result CreateTransactionInternal( use_anti_fee_sniping = false; } if (use_anti_fee_sniping) { - DiscourageFeeSniping(txNew, rng_fast, wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight()); + DiscourageFeeSniping(txNew, rng_fast, wallet.chain(), wallet.GetBestBlockHash(), wallet.GetBestBlockHeight()); } // Calculate the transaction fee diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index eb9c349c22e..2f440abc4cb 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -460,7 +460,7 @@ BOOST_AUTO_TEST_CASE(bnb_sffo_restriction) // Verify the coin selection process does not produce a BnB solution when SFFO is enabled. // This is currently problematic because it could require a change output. And BnB is specialized on changeless solutions. std::unique_ptr wallet = NewWallet(m_node); - WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(300, uint256{})); // set a high block so internal UTXOs are selectable + WITH_LOCK(wallet->cs_wallet, wallet->SetBestBlock(300, uint256{})); // set a high block so internal UTXOs are selectable FastRandomContext rand{}; CoinSelectionParams params{ diff --git a/src/wallet/test/fuzz/fees.cpp b/src/wallet/test/fuzz/fees.cpp index c2e785651ad..20458cdeb25 100644 --- a/src/wallet/test/fuzz/fees.cpp +++ b/src/wallet/test/fuzz/fees.cpp @@ -33,7 +33,7 @@ FUZZ_TARGET(wallet_fees, .init = initialize_setup) CWallet& wallet = *g_wallet_ptr; { LOCK(wallet.cs_wallet); - wallet.SetLastBlockProcessed(chainstate->m_chain.Height(), chainstate->m_chain.Tip()->GetBlockHash()); + wallet.SetBestBlock(chainstate->m_chain.Height(), chainstate->m_chain.Tip()->GetBlockHash()); } if (fuzzed_data_provider.ConsumeBool()) { diff --git a/src/wallet/test/fuzz/scriptpubkeyman.cpp b/src/wallet/test/fuzz/scriptpubkeyman.cpp index 0c17a6bf7a9..35e4a1f4646 100644 --- a/src/wallet/test/fuzz/scriptpubkeyman.cpp +++ b/src/wallet/test/fuzz/scriptpubkeyman.cpp @@ -96,7 +96,7 @@ FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm) { LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet.SetLastBlockProcessed(chainstate.m_chain.Height(), chainstate.m_chain.Tip()->GetBlockHash()); + wallet.SetBestBlock(chainstate.m_chain.Height(), chainstate.m_chain.Tip()->GetBlockHash()); wallet.m_keypool_size = 1; } diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp index bc53510fe49..b50d3e40f43 100644 --- a/src/wallet/test/util.cpp +++ b/src/wallet/test/util.cpp @@ -22,7 +22,7 @@ std::unique_ptr CreateSyncedWallet(interfaces::Chain& chain, CChain& cc auto wallet = std::make_unique(&chain, "", CreateMockableWalletDatabase()); { LOCK2(wallet->cs_wallet, ::cs_main); - wallet->SetLastBlockProcessed(cchain.Height(), cchain.Tip()->GetBlockHash()); + wallet->SetBestBlock(cchain.Height(), cchain.Tip()->GetBlockHash()); } { LOCK(wallet->cs_wallet); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index b5de4b4b3d3..35e4ce2e2ca 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -87,7 +87,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) LOCK(wallet.cs_wallet); LOCK(Assert(m_node.chainman)->GetMutex()); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); + wallet.SetBestBlock(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); @@ -108,7 +108,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) LOCK(wallet.cs_wallet); LOCK(Assert(m_node.chainman)->GetMutex()); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); + wallet.SetBestBlock(newTip->nHeight, newTip->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); @@ -117,9 +117,9 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) reserver.reserve(); { - CBlockLocator locator; - BOOST_CHECK(!WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator)); - BOOST_CHECK(locator.IsNull()); + BestBlock best_block; + BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(best_block)); + BOOST_CHECK(!best_block.IsNull() && best_block.m_locator.vHave.front() == newTip->GetBlockHash() && best_block.m_hash == newTip->GetBlockHash()); } CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/true); @@ -130,9 +130,9 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 100 * COIN); { - CBlockLocator locator; - BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator)); - BOOST_CHECK(!locator.IsNull()); + BestBlock best_block; + BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(best_block)); + BOOST_CHECK(!best_block.IsNull() && best_block.m_locator.vHave.front() == newTip->GetBlockHash() && best_block.m_hash == newTip->GetBlockHash()); } } @@ -153,7 +153,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) LOCK(wallet.cs_wallet); LOCK(Assert(m_node.chainman)->GetMutex()); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); + wallet.SetBestBlock(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); @@ -181,7 +181,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) LOCK(wallet.cs_wallet); LOCK(Assert(m_node.chainman)->GetMutex()); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); - wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); + wallet.SetBestBlock(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); @@ -218,7 +218,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) { const std::shared_ptr wallet = std::make_shared(m_node.chain.get(), "", CreateMockableWalletDatabase()); wallet->SetupLegacyScriptPubKeyMan(); - WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash())); + WITH_LOCK(wallet->cs_wallet, wallet->SetBestBlock(newTip->nHeight, newTip->GetBlockHash())); WalletContext context; context.args = &m_args; AddWallet(context, wallet); @@ -290,7 +290,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) AddWallet(context, wallet); LOCK(Assert(m_node.chainman)->GetMutex()); - wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); + wallet->SetBestBlock(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } JSONRPCRequest request; request.context = &context; @@ -316,7 +316,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) request.params.push_back(backup_file); AddWallet(context, wallet); LOCK(Assert(m_node.chainman)->GetMutex()); - wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); + wallet->SetBestBlock(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); wallet::importwallet().HandleRequest(request); RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt); @@ -379,7 +379,7 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet.SetupDescriptorScriptPubKeyMans(); - wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); + wallet.SetBestBlock(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. @@ -609,7 +609,7 @@ public: LOCK(wallet->cs_wallet); LOCK(Assert(m_node.chainman)->GetMutex()); - wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash()); + wallet->SetBestBlock(wallet->GetBestBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash()); auto it = wallet->mapWallet.find(tx->GetHash()); BOOST_CHECK(it != wallet->mapWallet.end()); it->second.m_state = TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*index=*/1}; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7abd17d31e9..0ffdab98aa1 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -164,6 +164,7 @@ bool RemoveWallet(WalletContext& context, const std::shared_ptr& wallet interfaces::Chain& chain = wallet->chain(); std::string name = wallet->GetName(); + wallet->WriteBestBlock(); // Unregister with the validation interface which also drops shared pointers. wallet->m_chain_notifications_handler.reset(); @@ -648,15 +649,29 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, return false; } -void CWallet::chainStateFlushed(ChainstateRole role, const CBlockLocator& loc) +void CWallet::SetBestBlockInMem(int block_height, uint256 block_hash) { - // Don't update the best block until the chain is attached so that in case of a shutdown, - // the rescan will be restarted at next startup. - if (m_attaching_chain || role == ChainstateRole::BACKGROUND) { - return; - } + AssertLockHeld(cs_wallet); + + m_best_block.m_hash = block_hash; + m_best_block.m_height = block_height; + + chain().findBlock(m_best_block.m_hash, FoundBlock().locator(m_best_block.m_locator)); +} + +void CWallet::SetBestBlock(int block_height, uint256 block_hash) +{ + AssertLockHeld(cs_wallet); + + SetBestBlockInMem(block_height, block_hash); + WalletBatch batch(GetDatabase()); - batch.WriteBestBlock(loc); + batch.WriteBestBlock(m_best_block); +} + +void CWallet::LoadBestBlock(const BestBlock& best_block) +{ + m_best_block = best_block; } void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in) @@ -1358,10 +1373,10 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c // that the block is still unknown or not yet part of the main chain, // for example when loading the wallet during a reindex. Do nothing in that // case. - if (m_last_block_processed_height < 0 || conflicting_height < 0) { + if (!m_best_block.m_height.has_value() || conflicting_height < 0) { return; } - int conflictconfirms = (m_last_block_processed_height - conflicting_height + 1) * -1; + int conflictconfirms = (m_best_block.m_height.value() - conflicting_height + 1) * -1; if (conflictconfirms >= 0) return; @@ -1425,15 +1440,16 @@ void CWallet::RecursiveUpdateTxState(WalletBatch* batch, const uint256& tx_hash, } } -void CWallet::SyncTransaction(const CTransactionRef& ptx, const SyncTxState& state, bool update_tx, bool rescanning_old_block) +bool CWallet::SyncTransaction(const CTransactionRef& ptx, const SyncTxState& state, bool update_tx, bool rescanning_old_block) { if (!AddToWalletIfInvolvingMe(ptx, state, update_tx, rescanning_old_block)) - return; // Not one of ours + return false; // Not one of ours // If a transaction changes 'conflicted' state, that changes the balance // available of the outputs it spends. So force those to be // recomputed, also: MarkInputsDirty(ptx); + return true; } void CWallet::transactionAddedToMempool(const CTransactionRef& tx) { @@ -1520,18 +1536,25 @@ void CWallet::blockConnected(ChainstateRole role, const interfaces::BlockInfo& b assert(block.data); LOCK(cs_wallet); - m_last_block_processed_height = block.height; - m_last_block_processed = block.hash; + // Update the best block in memory first. This will set the best block's height, which is + // needed by MarkConflicted. + SetBestBlockInMem(block.height, block.hash); // No need to scan block if it was created before the wallet birthday. // Uses chain max time and twice the grace period to adjust time for block time variability. if (block.chain_time_max < m_birth_time.load() - (TIMESTAMP_WINDOW * 2)) return; // Scan block + bool wallet_updated = false; for (size_t index = 0; index < block.data->vtx.size(); index++) { - SyncTransaction(block.data->vtx[index], TxStateConfirmed{block.hash, block.height, static_cast(index)}); + wallet_updated |= SyncTransaction(block.data->vtx[index], TxStateConfirmed{block.hash, block.height, static_cast(index)}); transactionRemovedFromMempool(block.data->vtx[index], MemPoolRemovalReason::BLOCK); } + + // Update on disk if this block resulted in us updating a tx, or periodically every 144 blocks (~1 day) + if (wallet_updated || block.height % 144 == 0) { + SetBestBlock(block.height, block.hash); + } } void CWallet::blockDisconnected(const interfaces::BlockInfo& block) @@ -1543,11 +1566,7 @@ void CWallet::blockDisconnected(const interfaces::BlockInfo& block) // be unconfirmed, whether or not the transaction is added back to the mempool. // User may have to call abandontransaction again. It may be addressed in the // future with a stickier abandoned state or even removing abandontransaction call. - m_last_block_processed_height = block.height - 1; - m_last_block_processed = *Assert(block.prev_hash); - int disconnect_height = block.height; - for (const CTransactionRef& ptx : Assert(block.data)->vtx) { SyncTransaction(ptx, TxStateInactive{}); @@ -1576,6 +1595,9 @@ void CWallet::blockDisconnected(const interfaces::BlockInfo& block) } } } + + // Update the best block + SetBestBlock(block.height - 1, *Assert(block.prev_hash)); } void CWallet::updatedBlockTip() @@ -1589,7 +1611,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() const { // chain().Tip(), otherwise put a callback in the validation interface queue and wait // for the queue to drain enough to execute it (indicating we are caught up // at least with the time we entered this function). - uint256 last_block_hash = WITH_LOCK(cs_wallet, return m_last_block_processed); + uint256 last_block_hash = WITH_LOCK(cs_wallet, return m_best_block.m_hash); chain().waitForNotificationsIfTipChanged(last_block_hash); } @@ -1852,7 +1874,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r int start_height = 0; uint256 start_block; bool start = chain().findFirstBlockWithTimeAndHeight(startTime - TIMESTAMP_WINDOW, 0, FoundBlock().hash(start_block).height(start_height)); - WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, start ? WITH_LOCK(cs_wallet, return GetLastBlockHeight()) - start_height + 1 : 0); + WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, start ? WITH_LOCK(cs_wallet, return GetBestBlockHeight()) - start_height + 1 : 0); if (start) { // TODO: this should take into account failure by ScanResult::USER_ABORT @@ -1907,7 +1929,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc fAbortRescan = false; ShowProgress(strprintf("%s %s", GetDisplayName(), _("Rescanning…")), 0); // show rescan progress in GUI as dialog or on splashscreen, if rescan required on startup (e.g. due to corruption) - uint256 tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash()); + uint256 tip_hash = WITH_LOCK(cs_wallet, return GetBestBlockHash()); uint256 end_hash = tip_hash; if (max_height) chain().findAncestorByHeight(tip_hash, *max_height, FoundBlock().hash(end_hash)); double progress_begin = chain().guessVerificationProgress(block_hash); @@ -1981,7 +2003,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc if (!loc.IsNull()) { WalletLogPrintf("Saving scan progress %d.\n", block_height); WalletBatch batch(GetDatabase()); - batch.WriteBestBlock(loc); + batch.WriteBestBlock(BestBlock{loc, block_hash, block_height}); } } } else { @@ -1997,7 +2019,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc // aren't processed here but will be processed with the pending blockConnected notifications after the lock is released. // If rescanning without a permanent cs_wallet lock, additional blocks that were added during the rescan will be re-processed if // the notification was processed and the last block height was updated. - if (block_height >= WITH_LOCK(cs_wallet, return GetLastBlockHeight())) { + if (block_height >= WITH_LOCK(cs_wallet, return GetBestBlockHeight())) { break; } @@ -2015,7 +2037,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc // handle updated tip hash const uint256 prev_tip_hash = tip_hash; - tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash()); + tip_hash = WITH_LOCK(cs_wallet, return GetBestBlockHash()); if (!max_height && prev_tip_hash != tip_hash) { // in case the tip has changed, update progress max progress_end = chain().guessVerificationProgress(tip_hash); @@ -2782,8 +2804,8 @@ void CWallet::GetKeyBirthTimes(std::map& mapKeyBirth) const { // map in which we'll infer heights of other keys std::map mapKeyFirstBlock; TxStateConfirmed max_confirm{uint256{}, /*height=*/-1, /*index=*/-1}; - max_confirm.confirmed_block_height = GetLastBlockHeight() > 144 ? GetLastBlockHeight() - 144 : 0; // the tip can be reorganized; use a 144-block safety margin - CHECK_NONFATAL(chain().findAncestorByHeight(GetLastBlockHash(), max_confirm.confirmed_block_height, FoundBlock().hash(max_confirm.confirmed_block_hash))); + max_confirm.confirmed_block_height = GetBestBlockHeight() > 144 ? GetBestBlockHeight() - 144 : 0; // the tip can be reorganized; use a 144-block safety margin + CHECK_NONFATAL(chain().findAncestorByHeight(GetBestBlockHash(), max_confirm.confirmed_block_height, FoundBlock().hash(max_confirm.confirmed_block_hash))); { LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan(); @@ -3088,7 +3110,10 @@ std::shared_ptr CWallet::Create(WalletContext& context, const std::stri } if (chain) { - walletInstance->chainStateFlushed(ChainstateRole::NORMAL, chain->getTipLocator()); + const std::optional tip_height = chain->getHeight(); + if (tip_height) { + WITH_LOCK(walletInstance->cs_wallet, walletInstance->SetBestBlock(*tip_height, chain->getBlockHash(*tip_height))); + } } } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { // Make it impossible to disable private keys after creation @@ -3267,15 +3292,14 @@ bool CWallet::AttachChain(const std::shared_ptr& walletInstance, interf // allow setting the chain if it hasn't been set already but prevent changing it assert(!walletInstance->m_chain || walletInstance->m_chain == &chain); walletInstance->m_chain = &chain; + CBlockLocator best_block_locator = walletInstance->GetBestBlockLocator(); // Unless allowed, ensure wallet files are not reused across chains: if (!gArgs.GetBoolArg("-walletcrosschain", DEFAULT_WALLETCROSSCHAIN)) { - WalletBatch batch(walletInstance->GetDatabase()); - CBlockLocator locator; - if (batch.ReadBestBlock(locator) && locator.vHave.size() > 0 && chain.getHeight()) { + if (best_block_locator.vHave.size() > 0 && chain.getHeight()) { // Wallet is assumed to be from another chain, if genesis block in the active // chain differs from the genesis block known to the wallet. - if (chain.getBlockHash(0) != locator.vHave.back()) { + if (chain.getBlockHash(0) != best_block_locator.vHave.back()) { error = Untranslated("Wallet files should not be reused across chains. Restart bitcoind with -walletcrosschain to override."); return false; } @@ -3288,33 +3312,33 @@ bool CWallet::AttachChain(const std::shared_ptr& walletInstance, interf // be pending on the validation-side until lock release. Blocks that are connected while the // rescan is ongoing will not be processed in the rescan but with the block connected notifications, // so the wallet will only be completeley synced after the notifications delivery. - // chainStateFlushed notifications are ignored until the rescan is finished - // so that in case of a shutdown event, the rescan will be repeated at the next start. - // This is temporary until rescan and notifications delivery are unified under same - // interface. - walletInstance->m_attaching_chain = true; //ignores chainStateFlushed notifications walletInstance->m_chain_notifications_handler = walletInstance->chain().handleNotifications(walletInstance); - // If rescan_required = true, rescan_height remains equal to 0 - int rescan_height = 0; - if (!rescan_required) - { - WalletBatch batch(walletInstance->GetDatabase()); - CBlockLocator locator; - if (batch.ReadBestBlock(locator)) { - if (const std::optional fork_height = chain.findLocatorFork(locator)) { - rescan_height = *fork_height; - } + // Update the best block locator if it is missing the height. + // If the chain has already processed the genesis block, this will be immediately overwritten with the current tip. + if (!walletInstance->HasBestBlockHeight()) { + // Best block record without height info, lookup height and rewrite the record + // Also sets for this wallet + int found_height; + if (chain.findBlock(walletInstance->GetBestBlockHash(), FoundBlock().height(found_height))) { + walletInstance->SetBestBlock(found_height, walletInstance->GetBestBlockHash()); } } + // If rescan_required = true, rescan_height remains equal to 0 + int rescan_height = 0; + if (!rescan_required) { + if (const std::optional fork_height = chain.findLocatorFork(walletInstance->GetBestBlockLocator())) { + rescan_height = *fork_height; + } + } + + // If we have a tip, always update the best block to the current tip. const std::optional tip_height = chain.getHeight(); if (tip_height) { - walletInstance->m_last_block_processed = chain.getBlockHash(*tip_height); - walletInstance->m_last_block_processed_height = *tip_height; - } else { - walletInstance->m_last_block_processed.SetNull(); - walletInstance->m_last_block_processed_height = -1; + // Always update best block to the current tip. If the wallet isn't actually at the tip yet, a rescan + // is already planned + walletInstance->SetBestBlock(*tip_height, chain.getBlockHash(*tip_height)); } if (tip_height && *tip_height != rescan_height) @@ -3369,16 +3393,22 @@ bool CWallet::AttachChain(const std::shared_ptr& walletInstance, interf { WalletRescanReserver reserver(*walletInstance); - if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, /*max_height=*/{}, reserver, /*fUpdate=*/true, /*save_progress=*/true).status)) { + if (!reserver.reserve()) { + error = _("Failed to acquire rescan reserver during wallet initialization"); + return false; + } + ScanResult scan_res = walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, /*max_height=*/{}, reserver, /*fUpdate=*/true, /*save_progress=*/true); + if (ScanResult::SUCCESS != scan_res.status) { error = _("Failed to rescan the wallet during initialization"); return false; } + // Set and update the best block record + // Although ScanForWalletTransactions will have stopped at the best block that was set prior to the rescan, + // we still need to make sure that the best block on disk is set correctly as rescanning may overwrite it. + walletInstance->SetBestBlock(*scan_res.last_scanned_height, scan_res.last_scanned_block); } - walletInstance->m_attaching_chain = false; - walletInstance->chainStateFlushed(ChainstateRole::NORMAL, chain.getTipLocator()); walletInstance->GetDatabase().IncrementUpdateCounter(); } - walletInstance->m_attaching_chain = false; return true; } @@ -3438,13 +3468,10 @@ void CWallet::postInitProcess() bool CWallet::BackupWallet(const std::string& strDest) const { - if (m_chain) { - CBlockLocator loc; - WITH_LOCK(cs_wallet, chain().findBlock(m_last_block_processed, FoundBlock().locator(loc))); - if (!loc.IsNull()) { - WalletBatch batch(GetDatabase()); - batch.WriteBestBlock(loc); - } + LOCK(cs_wallet); + if (!m_best_block.IsNull()) { + WalletBatch batch(GetDatabase()); + batch.WriteBestBlock(m_best_block); } return GetDatabase().Backup(strDest); } @@ -3469,10 +3496,10 @@ int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const AssertLockHeld(cs_wallet); if (auto* conf = wtx.state()) { assert(conf->confirmed_block_height >= 0); - return GetLastBlockHeight() - conf->confirmed_block_height + 1; + return GetBestBlockHeight() - conf->confirmed_block_height + 1; } else if (auto* conf = wtx.state()) { assert(conf->conflicting_block_height >= 0); - return -1 * (GetLastBlockHeight() - conf->conflicting_block_height + 1); + return -1 * (GetBestBlockHeight() - conf->conflicting_block_height + 1); } else { return 0; } @@ -4146,12 +4173,6 @@ util::Result CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch, } } - // Get best block locator so that we can copy it to the watchonly and solvables - CBlockLocator best_block_locator; - if (!local_wallet_batch.ReadBestBlock(best_block_locator)) { - return util::Error{_("Error: Unable to read wallet's best block locator record")}; - } - // Check if the transactions in the wallet are still ours. Either they belong here, or they belong in the watchonly wallet. // We need to go through these in the tx insertion order so that lookups to spends works. std::vector txids_to_delete; @@ -4164,7 +4185,7 @@ util::Result CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch, data.watchonly_wallet->nOrderPosNext = nOrderPosNext; watchonly_batch->WriteOrderPosNext(data.watchonly_wallet->nOrderPosNext); // Write the best block locator to avoid rescanning on reload - if (!watchonly_batch->WriteBestBlock(best_block_locator)) { + if (!watchonly_batch->WriteBestBlock(m_best_block)) { return util::Error{_("Error: Unable to write watchonly wallet best block locator record")}; } } @@ -4173,7 +4194,7 @@ util::Result CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch, solvables_batch = std::make_unique(data.solvable_wallet->GetDatabase()); if (!solvables_batch->TxnBegin()) return util::Error{strprintf(_("Error: database transaction cannot be executed for wallet %s"), data.solvable_wallet->GetName())}; // Write the best block locator to avoid rescanning on reload - if (!solvables_batch->WriteBestBlock(best_block_locator)) { + if (!solvables_batch->WriteBestBlock(m_best_block)) { return util::Error{_("Error: Unable to write solvable wallet best block locator record")}; } } @@ -4421,7 +4442,7 @@ util::Result MigrateLegacyToDescriptor(const std::string& walle // Flush chain state before unloading wallet CBlockLocator locator; - WITH_LOCK(wallet->cs_wallet, context.chain->findBlock(wallet->GetLastBlockHash(), FoundBlock().locator(locator))); + WITH_LOCK(wallet->cs_wallet, context.chain->findBlock(wallet->GetBestBlockHash(), FoundBlock().locator(locator))); if (!locator.IsNull()) wallet->chainStateFlushed(ChainstateRole::NORMAL, locator); if (!RemoveWallet(context, wallet, /*load_on_start=*/std::nullopt, warnings)) { @@ -4672,4 +4693,13 @@ std::optional CWallet::GetKey(const CKeyID& keyid) const } return std::nullopt; } + +void CWallet::WriteBestBlock() const +{ + LOCK(cs_wallet); + if (!m_best_block.IsNull()) { + WalletBatch batch(GetDatabase()); + batch.WriteBestBlock(m_best_block); + } +} } // namespace wallet diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index c475718eb94..18c7a83b81a 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -292,6 +292,39 @@ struct CRecipient bool fSubtractFeeFromAmount; }; +struct BestBlock +{ + CBlockLocator m_locator; + uint256 m_hash; + std::optional m_height; + + bool IsNull() const { return m_locator.IsNull(); } + + template + void Serialize(Stream& s) const + { + s << m_locator; + // No need to write m_hash since it's the first thing in m_locator + if (m_height.has_value()) { + s << m_height.value(); + } + } + + template + void Unserialize(Stream& s) + { + s >> m_locator; + if (!s.eof()) { + int height; + s >> height; + m_height = height; + } + if (!m_locator.IsNull()) { + m_hash = m_locator.vHave[0]; + } + } +}; + class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime /** * A CWallet maintains a set of transactions and balances, and provides the ability to create new transactions. @@ -305,7 +338,6 @@ private: std::atomic fAbortRescan{false}; std::atomic fScanningWallet{false}; // controlled by WalletRescanReserver - std::atomic m_attaching_chain{false}; std::atomic m_scanning_with_passphrase{false}; std::atomic m_scanning_start{SteadyClock::time_point{}}; std::atomic m_scanning_progress{0}; @@ -370,7 +402,7 @@ private: void SyncMetaData(std::pair) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void SyncTransaction(const CTransactionRef& tx, const SyncTxState& state, bool update_tx = true, bool rescanning_old_block = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool SyncTransaction(const CTransactionRef& tx, const SyncTxState& state, bool update_tx = true, bool rescanning_old_block = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** WalletFlags set on this wallet. */ std::atomic m_wallet_flags{0}; @@ -393,20 +425,11 @@ private: std::unique_ptr m_database; /** - * The following is used to keep track of how far behind the wallet is + * m_best_block is used to keep track of how far behind the wallet is * from the chain sync, and to allow clients to block on us being caught up. - * - * Processed hash is a pointer on node's tip and doesn't imply that the wallet - * has scanned sequentially all blocks up to this one. + * The wallet is guaranteed to have completed scanning up to this block. */ - uint256 m_last_block_processed GUARDED_BY(cs_wallet); - - /** Height of last block processed is used by wallet to know depth of transactions - * without relying on Chain interface beyond asynchronous updates. For safety, we - * initialize it to -1. Height is a pointer on node's tip and doesn't imply - * that the wallet has scanned sequentially all blocks up to this one. - */ - int m_last_block_processed_height GUARDED_BY(cs_wallet) = -1; + BestBlock m_best_block GUARDED_BY(cs_wallet); std::map m_external_spk_managers; std::map m_internal_spk_managers; @@ -430,13 +453,16 @@ private: /** * Catch wallet up to current chain, scanning new blocks, updating the best - * block locator and m_last_block_processed, and registering for + * block locator, and registering for * notifications about new blocks and transactions. */ static bool AttachChain(const std::shared_ptr& wallet, interfaces::Chain& chain, const bool rescan_required, bilingual_str& error, std::vector& warnings); static NodeClock::time_point GetDefaultNextResend(); + // Update last block processed in memory only + void SetBestBlockInMem(int block_height, uint256 block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + public: /** * Main wallet lock. @@ -788,7 +814,6 @@ public: /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction& tx) const; CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const; - void chainStateFlushed(ChainstateRole role, const CBlockLocator& loc) override; DBErrors LoadWallet(); @@ -977,25 +1002,31 @@ public: bool HaveCryptedKeys() const; /** Get last block processed height */ - int GetLastBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) + int GetBestBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); - assert(m_last_block_processed_height >= 0); - return m_last_block_processed_height; + assert(m_best_block.m_height.has_value()); + return m_best_block.m_height.value(); }; - uint256 GetLastBlockHash() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) + bool HasBestBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { - AssertLockHeld(cs_wallet); - assert(m_last_block_processed_height >= 0); - return m_last_block_processed; + return !m_best_block.IsNull() && m_best_block.m_height.has_value(); } - /** Set last block processed height, currently only use in unit test */ - void SetLastBlockProcessed(int block_height, uint256 block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) + uint256 GetBestBlockHash() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); - m_last_block_processed_height = block_height; - m_last_block_processed = block_hash; - }; + return m_best_block.m_hash; + } + CBlockLocator GetBestBlockLocator() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) + { + AssertLockHeld(cs_wallet); + return m_best_block.m_locator; + } + /** Set last block processed height, and write to database */ + void SetBestBlock(int block_height, uint256 block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void LoadBestBlock(const BestBlock& best_block) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + /** Write the current best block to database */ + void WriteBestBlock() const; //! Connect the signals from ScriptPubKeyMans to the signals in CWallet void ConnectScriptPubKeyManNotifiers(); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index c939ebb1fd4..4339d2e5e36 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -180,16 +180,21 @@ bool WalletBatch::EraseWatchOnly(const CScript &dest) return EraseIC(std::make_pair(DBKeys::WATCHS, dest)); } -bool WalletBatch::WriteBestBlock(const CBlockLocator& locator) +bool WalletBatch::WriteBestBlock(const BestBlock& best_block) { WriteIC(DBKeys::BESTBLOCK, CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan - return WriteIC(DBKeys::BESTBLOCK_NOMERKLE, locator); + return WriteIC(DBKeys::BESTBLOCK_NOMERKLE, best_block); } -bool WalletBatch::ReadBestBlock(CBlockLocator& locator) +bool WalletBatch::ReadBestBlock(BestBlock& best_block) { - if (m_batch->Read(DBKeys::BESTBLOCK, locator) && !locator.vHave.empty()) return true; - return m_batch->Read(DBKeys::BESTBLOCK_NOMERKLE, locator); + CBlockLocator locator; + if (m_batch->Read(DBKeys::BESTBLOCK, locator) && !locator.vHave.empty()) { + best_block.m_locator = locator; + best_block.m_hash = locator.vHave[0]; + return true; + } + return m_batch->Read(DBKeys::BESTBLOCK_NOMERKLE, best_block); } bool WalletBatch::IsEncrypted() @@ -491,6 +496,16 @@ static DBErrors LoadWalletFlags(CWallet* pwallet, DatabaseBatch& batch) EXCLUSIV return DBErrors::LOAD_OK; } +static DBErrors LoadBestBlock(CWallet* pwallet, WalletBatch& batch) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +{ + AssertLockHeld(pwallet->cs_wallet); + BestBlock best_block; + if (batch.ReadBestBlock(best_block) && !best_block.IsNull()) { + pwallet->LoadBestBlock(best_block); + } + return DBErrors::LOAD_OK; +} + struct LoadResult { DBErrors m_result{DBErrors::LOAD_OK}; @@ -1196,6 +1211,8 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) return DBErrors::EXTERNAL_SIGNER_SUPPORT_REQUIRED; } #endif + // Load the best block + if ((result = LoadBestBlock(pwallet, *this)) != DBErrors::LOAD_OK) return result; // Load legacy wallet keys result = std::max(LoadLegacyWalletRecords(pwallet, *m_batch, last_client), result); diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 70d69870126..11940d33785 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -21,6 +21,7 @@ class uint256; struct CBlockLocator; namespace wallet { +struct BestBlock; class CKeyPool; class CMasterKey; class CWallet; @@ -250,8 +251,8 @@ public: bool WriteWatchOnly(const CScript &script, const CKeyMetadata &keymeta); bool EraseWatchOnly(const CScript &script); - bool WriteBestBlock(const CBlockLocator& locator); - bool ReadBestBlock(CBlockLocator& locator); + bool WriteBestBlock(const BestBlock& best_block); + bool ReadBestBlock(BestBlock& best_block); // Returns true if wallet stores encryption keys bool IsEncrypted();