From 48826b429d65c9a70ea47c36cb71f68959f9caa4 Mon Sep 17 00:00:00 2001 From: MeshCollider Date: Wed, 30 Jan 2019 13:02:27 +1300 Subject: [PATCH] Merge #14711: Remove uses of chainActive and mapBlockIndex in wallet code 44de1561a Remove remaining chainActive references from CWallet (Russell Yanofsky) db21f0264 Convert CWallet::ScanForWalletTransactions and SyncTransaction to the new Chain apis (Russell Yanofsky) 2ffb07929 Add findFork and findBlock to the Chain interface (Russell Yanofsky) d93c4c1d6 Add time methods to the Chain interface (Russell Yanofsky) 700c42b85 Add height, depth, and hash methods to the Chain interface (Russell Yanofsky) Pull request description: This change removes uses of `chainActive` and `mapBlockIndex` globals in wallet code. It is a refactoring change which does not affect external behavior. This is the next step in the larger #10973 refactoring change, which removes all other accesses to node global variables from wallet code. Doing this is useful to provide a better defined interface between the wallet and node, and necessary to allow wallet and node code to run in separate processes in #10102. Tree-SHA512: 4dcec8a31c458f54e2ea6ecf01e430469b0994c5b41a21a2d150efa67cd209f4c93ae210a101e064b3a87c52c6edfc70b070e979992be0e3a00fd425de6230a8 --- src/Makefile.am | 1 + src/interfaces/chain.cpp | 142 ++++++++++++++++ src/interfaces/chain.h | 89 ++++++++++ src/interfaces/wallet.cpp | 10 +- src/optional.h | 17 ++ src/qt/test/wallettests.cpp | 12 +- src/wallet/rpcdump.cpp | 40 +++-- src/wallet/rpcwallet.cpp | 90 +++++----- src/wallet/test/coinjoin_tests.cpp | 10 +- src/wallet/test/wallet_tests.cpp | 65 +++---- src/wallet/wallet.cpp | 262 +++++++++++++++-------------- src/wallet/wallet.h | 33 ++-- 12 files changed, 525 insertions(+), 246 deletions(-) create mode 100644 src/optional.h diff --git a/src/Makefile.am b/src/Makefile.am index 2ca2bf7cde..e14ea60f29 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -229,6 +229,7 @@ BITCOIN_CORE_H = \ node/coinstats.h \ node/transaction.h \ noui.h \ + optional.h \ policy/feerate.h \ policy/fees.h \ policy/policy.h \ diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index 2571a91031..c50a1bd8cd 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -4,7 +4,11 @@ #include +#include +#include +#include #include +#include #include #include @@ -16,6 +20,118 @@ namespace { class LockImpl : public Chain::Lock { + Optional getHeight() override + { + int height = ::ChainActive().Height(); + if (height >= 0) { + return height; + } + return nullopt; + } + Optional getBlockHeight(const uint256& hash) override + { + CBlockIndex* block = LookupBlockIndex(hash); + if (block && ::ChainActive().Contains(block)) { + return block->nHeight; + } + return nullopt; + } + int getBlockDepth(const uint256& hash) override + { + const Optional tip_height = getHeight(); + const Optional height = getBlockHeight(hash); + return tip_height && height ? *tip_height - *height + 1 : 0; + } + uint256 getBlockHash(int height) override + { + CBlockIndex* block = ::ChainActive()[height]; + assert(block != nullptr); + return block->GetBlockHash(); + } + int64_t getBlockTime(int height) override + { + CBlockIndex* block = ::ChainActive()[height]; + assert(block != nullptr); + return block->GetBlockTime(); + } + int64_t getBlockMedianTimePast(int height) override + { + CBlockIndex* block = ::ChainActive()[height]; + assert(block != nullptr); + return block->GetMedianTimePast(); + } + bool haveBlockOnDisk(int height) override + { + CBlockIndex* block = ::ChainActive()[height]; + return block && ((block->nStatus & BLOCK_HAVE_DATA) != 0) && block->nTx > 0; + } + Optional findFirstBlockWithTime(int64_t time, uint256* hash) override + { + CBlockIndex* block = ::ChainActive().FindEarliestAtLeast(time); + if (block) { + if (hash) *hash = block->GetBlockHash(); + return block->nHeight; + } + return nullopt; + } + Optional findFirstBlockWithTimeAndHeight(int64_t time, int height) override + { + // TODO: Could update CChain::FindEarliestAtLeast() to take a height + // parameter and use it with std::lower_bound() to make this + // implementation more efficient and allow combining + // findFirstBlockWithTime and findFirstBlockWithTimeAndHeight into one + // method. + for (CBlockIndex* block = ::ChainActive()[height]; block; block = ::ChainActive().Next(block)) { + if (block->GetBlockTime() >= time) { + return block->nHeight; + } + } + return nullopt; + } + Optional findPruned(int start_height, Optional stop_height) override + { + if (::fPruneMode) { + CBlockIndex* block = stop_height ? ::ChainActive()[*stop_height] : ::ChainActive().Tip(); + while (block && block->nHeight >= start_height) { + if ((block->nStatus & BLOCK_HAVE_DATA) == 0) { + return block->nHeight; + } + block = block->pprev; + } + } + return nullopt; + } + Optional findFork(const uint256& hash, Optional* height) override + { + const CBlockIndex* block = LookupBlockIndex(hash); + const CBlockIndex* fork = block ? ::ChainActive().FindFork(block) : nullptr; + if (height) { + if (block) { + *height = block->nHeight; + } else { + height->reset(); + } + } + if (fork) { + return fork->nHeight; + } + return nullopt; + } + bool isPotentialTip(const uint256& hash) override + { + if (::ChainActive().Tip()->GetBlockHash() == hash) return true; + CBlockIndex* block = LookupBlockIndex(hash); + return block && block->GetAncestor(::ChainActive().Height()) == ::ChainActive().Tip(); + } + CBlockLocator getLocator() override { return ::ChainActive().GetLocator(); } + Optional findLocatorFork(const CBlockLocator& locator) override + { + LockAnnotation lock(::cs_main); + if (CBlockIndex* fork = FindForkInGlobalIndex(::ChainActive(), locator)) { + return fork->nHeight; + } + return nullopt; + } }; class LockingStateImpl : public LockImpl, public UniqueLock @@ -35,6 +151,32 @@ public: return std::move(result); } std::unique_ptr assumeLocked() override { return MakeUnique(); } + bool findBlock(const uint256& hash, CBlock* block, int64_t* time, int64_t* time_max) override + { + CBlockIndex* index; + { + LOCK(cs_main); + index = LookupBlockIndex(hash); + if (!index) { + return false; + } + if (time) { + *time = index->GetBlockTime(); + } + if (time_max) { + *time_max = index->GetBlockTimeMax(); + } + } + if (block && !ReadBlockFromDisk(*block, index, Params().GetConsensus())) { + block->SetNull(); + } + return true; + } + double guessVerificationProgress(const uint256& block_hash) override + { + LOCK(cs_main); + return GuessVerificationProgress(Params().TxData(), LookupBlockIndex(block_hash)); + } }; } // namespace diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index fe5658de4b..735d5b60df 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -5,11 +5,17 @@ #ifndef BITCOIN_INTERFACES_CHAIN_H #define BITCOIN_INTERFACES_CHAIN_H +#include + #include +#include #include #include +class CBlock; class CScheduler; +class uint256; +struct CBlockLocator; namespace interfaces { @@ -28,6 +34,74 @@ public: { public: virtual ~Lock() {} + + //! Get current chain height, not including genesis block (returns 0 if + //! chain only contains genesis block, nullopt if chain does not contain + //! any blocks). + virtual Optional getHeight() = 0; + + //! Get block height above genesis block. Returns 0 for genesis block, + //! 1 for following block, and so on. Returns nullopt for a block not + //! included in the current chain. + virtual Optional getBlockHeight(const uint256& hash) = 0; + + //! Get block depth. Returns 1 for chain tip, 2 for preceding block, and + //! so on. Returns 0 for a block not included in the current chain. + virtual int getBlockDepth(const uint256& hash) = 0; + + //! Get block hash. Height must be valid or this function will abort. + virtual uint256 getBlockHash(int height) = 0; + + //! Get block time. Height must be valid or this function will abort. + virtual int64_t getBlockTime(int height) = 0; + + //! Get block median time past. Height must be valid or this function + //! will abort. + virtual int64_t getBlockMedianTimePast(int height) = 0; + + //! Check that the block is available on disk (i.e. has not been + //! pruned), and contains transactions. + virtual bool haveBlockOnDisk(int height) = 0; + + //! Return height of the first block in the chain with timestamp equal + //! or greater than the given time, or nullopt if there is no block with + //! a high enough timestamp. Also return the block hash as an optional + //! output parameter (to avoid the cost of a second lookup in case this + //! information is needed.) + virtual Optional findFirstBlockWithTime(int64_t time, uint256* hash) = 0; + + //! Return height of the first block in the chain with timestamp equal + //! or greater than the given time and height equal or greater than the + //! given height, or nullopt if there is no such block. + //! + //! Calling this with height 0 is equivalent to calling + //! findFirstBlockWithTime, but less efficient because it requires a + //! linear instead of a binary search. + virtual Optional findFirstBlockWithTimeAndHeight(int64_t time, int height) = 0; + + //! Return height of last block in the specified range which is pruned, or + //! nullopt if no block in the range is pruned. Range is inclusive. + virtual Optional findPruned(int start_height = 0, Optional stop_height = nullopt) = 0; + + //! Return height of the highest block on the chain that is an ancestor + //! of the specified block, or nullopt if no common ancestor is found. + //! Also return the height of the specified block as an optional output + //! parameter (to avoid the cost of a second hash lookup in case this + //! information is desired). + virtual Optional findFork(const uint256& hash, Optional* height) = 0; + + //! Return true if block hash points to the current chain tip, or to a + //! possible descendant of the current chain tip that isn't currently + //! connected. + virtual bool isPotentialTip(const uint256& hash) = 0; + + //! Get locator for the current chain tip. + virtual CBlockLocator getLocator() = 0; + + //! Return height of the latest block common to locator and chain, which + //! is guaranteed to be an ancestor of the block used to create the + //! locator. + virtual Optional findLocatorFork(const CBlockLocator& locator) = 0; }; //! Return Lock interface. Chain is locked when this is called, and @@ -38,6 +112,21 @@ public: //! method is temporary and is only used in a few places to avoid changing //! behavior while code is transitioned to use the Chain::Lock interface. virtual std::unique_ptr assumeLocked() = 0; + + //! Return whether node has the block and optionally return block metadata + //! or contents. + //! + //! If a block pointer is provided to retrieve the block contents, and the + //! block exists but doesn't have data (for example due to pruning), the + //! block will be empty and all fields set to null. + virtual bool findBlock(const uint256& hash, + CBlock* block = nullptr, + int64_t* time = nullptr, + int64_t* max_time = nullptr) = 0; + + //! Estimate fraction of total transactions verified if blocks up to + //! the specified block hash are verified. + virtual double guessVerificationProgress(const uint256& block_hash) = 0; }; //! Interface to let node manage chain clients (wallets, or maybe tools for diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 622485aed9..437b3f1147 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -382,8 +382,12 @@ public: if (mi == m_wallet->mapWallet.end()) { return false; } + if (Optional height = locked_chain->getHeight()) { + block_time = locked_chain->getBlockTime(*height); + } else { + block_time = -1; + } tx_status = MakeWalletTxStatus(*locked_chain, mi->second); - block_time = ::ChainActive().Tip()->GetBlockTime(); return true; } WalletTx getWalletTxDetails(const uint256& txid, @@ -396,7 +400,7 @@ public: LOCK(m_wallet->cs_wallet); auto mi = m_wallet->mapWallet.find(txid); if (mi != m_wallet->mapWallet.end()) { - num_blocks = ::ChainActive().Height(); + num_blocks = locked_chain->getHeight().value_or(-1); in_mempool = mi->second.InMempool(); order_form = mi->second.vOrderForm; tx_status = MakeWalletTxStatus(*locked_chain, mi->second); @@ -431,7 +435,7 @@ public: return false; } balances = getBalances(); - num_blocks = ::ChainActive().Height(); + num_blocks = locked_chain->getHeight().value_or(-1); return true; } CAmount getBalance() override diff --git a/src/optional.h b/src/optional.h new file mode 100644 index 0000000000..1614c89718 --- /dev/null +++ b/src/optional.h @@ -0,0 +1,17 @@ +// Copyright (c) 2017 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_OPTIONAL_H +#define BITCOIN_OPTIONAL_H + +#include + +//! Substitute for C++17 std::optional +template +using Optional = boost::optional; + +//! Substitute for C++17 std::nullopt +static auto& nullopt = boost::none; + +#endif // BITCOIN_OPTIONAL_H diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 48c0bb0222..e8514a405d 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -121,13 +122,10 @@ void TestGUI() WalletRescanReserver reserver(wallet.get()); reserver.reserve(); - const CBlockIndex* const null_block = nullptr; - const CBlockIndex *stop_block, *failed_block; - QCOMPARE( - wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block, true /* fUpdate */), - CWallet::ScanResult::SUCCESS); - QCOMPARE(stop_block, ::ChainActive().Tip()); - QCOMPARE(failed_block, null_block); + CWallet::ScanResult result = wallet->ScanForWalletTransactions(locked_chain->getBlockHash(0), {} /* stop_block */, reserver, true /* fUpdate */); + QCOMPARE(result.status, CWallet::ScanResult::SUCCESS); + QCOMPARE(result.stop_block, ::ChainActive().Tip()->GetBlockHash()); + QVERIFY(result.failed_block.IsNull()); } wallet->SetBroadcastTransactions(true); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 54c33420ae..22dcaafc1e 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -354,8 +354,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request) auto locked_chain = pwallet->chain().lock(); LockAnnotation lock(::cs_main); - const CBlockIndex* pindex = LookupBlockIndex(merkleBlock.header.GetHash()); - if (!pindex || !::ChainActive().Contains(pindex)) { + if (locked_chain->getBlockHeight(merkleBlock.header.GetHash()) == nullopt) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); } @@ -544,7 +543,8 @@ UniValue importwallet(const JSONRPCRequest& request) if (!file.is_open()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); } - nTimeBegin = ::ChainActive().Tip()->GetBlockTime(); + Optional tip_height = locked_chain->getHeight(); + nTimeBegin = tip_height ? locked_chain->getBlockTime(*tip_height) : 0; int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); file.seekg(0, file.beg); @@ -688,7 +688,8 @@ UniValue importelectrumwallet(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -777,24 +778,25 @@ UniValue importelectrumwallet(const JSONRPCRequest& request) file.close(); pwallet->ShowProgress("", 100); // hide progress dialog in GUI + const uint32_t tip_height = locked_chain->getHeight().value_or(-1); + // Whether to perform rescan after import int nStartHeight = 0; if (!request.params[1].isNull()) nStartHeight = request.params[1].get_int(); - if (::ChainActive().Height() < nStartHeight) - nStartHeight = ::ChainActive().Height(); + if (tip_height < nStartHeight) + nStartHeight = tip_height; // Assume that electrum wallet was created at that block - int nTimeBegin = ::ChainActive()[nStartHeight]->GetBlockTime(); + int nTimeBegin = locked_chain->getBlockTime(nStartHeight); pwallet->UpdateTimeFirstKey(nTimeBegin); - pwallet->WalletLogPrintf("Rescanning %i blocks\n", ::ChainActive().Height() - nStartHeight + 1); + pwallet->WalletLogPrintf("Rescanning %i blocks\n", tip_height - nStartHeight + 1); WalletRescanReserver reserver(pwallet); if (!reserver.reserve()) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } - const CBlockIndex *stop_block, *failed_block; - pwallet->ScanForWalletTransactions(::ChainActive()[nStartHeight], nullptr, reserver, failed_block, stop_block, true); + pwallet->ScanForWalletTransactions(locked_chain->getBlockHash(nStartHeight), {}, reserver, true); if (!fGood) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet"); @@ -968,15 +970,16 @@ UniValue dumpwallet(const JSONRPCRequest& request) // produce output file << strprintf("# Wallet dump created by Dash Core %s\n", CLIENT_BUILD); file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime())); - file << strprintf("# * Best block at time of backup was %i (%s),\n", ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash().ToString()); - file << strprintf("# mined on %s\n", FormatISO8601DateTime(::ChainActive().Tip()->GetBlockTime())); + const Optional tip_height = locked_chain->getHeight(); + file << strprintf("# * Best block at time of backup was %i (%s),\n", tip_height.value_or(-1), tip_height ? locked_chain->getBlockHash(*tip_height).ToString() : "(missing block hash)"); + file << strprintf("# mined on %s\n", tip_height ? FormatISO8601DateTime(locked_chain->getBlockTime(*tip_height)) : "(missing block time)"); file << "\n"; UniValue obj(UniValue::VOBJ); obj.pushKV("dashcoreversion", CLIENT_BUILD); - obj.pushKV("lastblockheight", ::ChainActive().Height()); - obj.pushKV("lastblockhash", ::ChainActive().Tip()->GetBlockHash().ToString()); - obj.pushKV("lastblocktime", FormatISO8601DateTime(::ChainActive().Tip()->GetBlockTime())); + obj.pushKV("lastblockheight", tip_height.value_or(-1)); + obj.pushKV("lastblockhash", tip_height ? locked_chain->getBlockHash(*tip_height).ToString() : NullUniValue); + obj.pushKV("lastblocktime", tip_height ? FormatISO8601DateTime(locked_chain->getBlockTime(*tip_height)) : NullUniValue); // add the base58check encoded extended master if the wallet uses HD CHDChain hdChainCurrent; @@ -1552,15 +1555,16 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) EnsureWalletIsUnlocked(pwallet); // Verify all timestamps are present before importing any keys. - now = ::ChainActive().Tip() ? ::ChainActive().Tip()->GetMedianTimePast() : 0; + const Optional tip_height = locked_chain->getHeight(); + now = tip_height ? locked_chain->getBlockMedianTimePast(*tip_height) : 0; for (const UniValue& data : requests.getValues()) { GetImportTimestamp(data, now); } const int64_t minimumTimestamp = 1; - if (fRescan && ::ChainActive().Tip()) { - nLowestTimestamp = ::ChainActive().Tip()->GetBlockTime(); + if (fRescan && tip_height) { + nLowestTimestamp = locked_chain->getBlockTime(*tip_height); } else { fRescan = false; } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e4d70de876..98759aece1 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -119,7 +119,10 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo { entry.pushKV("blockhash", wtx.hashBlock.GetHex()); entry.pushKV("blockindex", wtx.nIndex); - entry.pushKV("blocktime", LookupBlockIndex(wtx.hashBlock)->GetBlockTime()); + int64_t block_time; + bool found_block = chain.findBlock(wtx.hashBlock, nullptr /* block */, &block_time); + assert(found_block); + entry.pushKV("blocktime", block_time); } else { entry.pushKV("trusted", wtx.IsTrusted(locked_chain)); } @@ -1634,26 +1637,19 @@ static UniValue listsinceblock(const JSONRPCRequest& request) LockAnnotation lock(::cs_main); LOCK(pwallet->cs_wallet); - const CBlockIndex* pindex = nullptr; // Block index of the specified block or the common ancestor, if the block provided was in a deactivated chain. - const CBlockIndex* paltindex = nullptr; // Block index of the specified block, even if it's in a deactivated chain. + Optional height; // Height of the specified block or the common ancestor, if the block provided was in a deactivated chain. + Optional altheight; // Height of the specified block, even if it's in a deactivated chain. int target_confirms = 1; isminefilter filter = ISMINE_SPENDABLE; + uint256 blockId; if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { - uint256 blockId; - blockId.SetHex(request.params[0].get_str()); - paltindex = pindex = LookupBlockIndex(blockId); + height = locked_chain->findFork(blockId, &altheight); - if (!pindex) { + if (!height) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } - if (::ChainActive()[pindex->nHeight] != pindex) { - // the block being asked for is a part of a deactivated chain; - // we don't want to depend on its perceived height in the block - // chain, we want to instead use the last common ancestor - pindex = ::ChainActive().FindFork(pindex); - } } if (!request.params[1].isNull()) { @@ -1670,7 +1666,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request) bool include_removed = (request.params[3].isNull() || request.params[3].get_bool()); - int depth = pindex ? (1 + ::ChainActive().Height() - pindex->nHeight) : -1; + const Optional tip_height = locked_chain->getHeight(); + int depth = tip_height && height ? (1 + *tip_height - *height) : -1; UniValue transactions(UniValue::VARR); @@ -1685,9 +1682,9 @@ static UniValue listsinceblock(const JSONRPCRequest& request) // when a reorg'd block is requested, we also list any relevant transactions // in the blocks of the chain that was detached UniValue removed(UniValue::VARR); - while (include_removed && paltindex && paltindex != pindex) { + while (include_removed && altheight && *altheight > *height) { CBlock block; - if (!ReadBlockFromDisk(block, paltindex, Params().GetConsensus())) { + if (!pwallet->chain().findBlock(blockId, &block) || block.IsNull()) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); } for (const CTransactionRef& tx : block.vtx) { @@ -1698,11 +1695,12 @@ static UniValue listsinceblock(const JSONRPCRequest& request) ListTransactions(*locked_chain, pwallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */); } } - paltindex = paltindex->pprev; + blockId = block.hashPrevBlock; + --*altheight; } - CBlockIndex *pblockLast = ::ChainActive()[::ChainActive().Height() + 1 - target_confirms]; - uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : uint256(); + int last_height = tip_height ? *tip_height + 1 - target_confirms : -1; + uint256 lastblock = last_height >= 0 ? locked_chain->getBlockHash(last_height) : uint256(); UniValue ret(UniValue::VOBJ); ret.pushKV("transactions", transactions); @@ -2701,7 +2699,8 @@ static UniValue upgradetohd(const JSONRPCRequest& request) + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\"") + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\" \"walletpassphrase\"")); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); // Do not do anything to HD wallets if (pwallet->IsHDEnabled()) { @@ -2772,8 +2771,7 @@ static UniValue upgradetohd(const JSONRPCRequest& request) if (!reserver.reserve()) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } - const CBlockIndex *stop_block, *failed_block; - pwallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block, true); + pwallet->ScanForWalletTransactions(locked_chain->getBlockHash(0), {}, reserver, true); } return true; @@ -3579,50 +3577,48 @@ static UniValue rescanblockchain(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } - CBlockIndex *pindexStart = nullptr; - CBlockIndex *pindexStop = nullptr; - CBlockIndex *pChainTip = nullptr; + int start_height = 0; + uint256 start_block, stop_block; { auto locked_chain = pwallet->chain().lock(); - pindexStart = ::ChainActive().Genesis(); - pChainTip = ::ChainActive().Tip(); + Optional tip_height = locked_chain->getHeight(); if (!request.params[0].isNull()) { - pindexStart = ::ChainActive()[request.params[0].get_int()]; - if (!pindexStart) { + start_height = request.params[0].get_int(); + if (start_height < 0 || !tip_height || start_height > *tip_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); } } + Optional stop_height; if (!request.params[1].isNull()) { - pindexStop = ::ChainActive()[request.params[1].get_int()]; - if (!pindexStop) { + stop_height = request.params[1].get_int(); + if (*stop_height < 0 || !tip_height || *stop_height > *tip_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); } - else if (pindexStop->nHeight < pindexStart->nHeight) { + else if (*stop_height < start_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater than start_height"); } } - } - // We can't rescan beyond non-pruned blocks, stop and throw an error - if (fPruneMode) { - auto locked_chain = pwallet->chain().lock(); - CBlockIndex *block = pindexStop ? pindexStop : pChainTip; - while (block && block->nHeight >= pindexStart->nHeight) { - if (!(block->nStatus & BLOCK_HAVE_DATA)) { - throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height."); + // We can't rescan beyond non-pruned blocks, stop and throw an error + if (locked_chain->findPruned(start_height, stop_height)) { + throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height."); + } + + if (tip_height) { + start_block = locked_chain->getBlockHash(start_height); + if (stop_height) { + stop_block = locked_chain->getBlockHash(*stop_height); } - block = block->pprev; } } - const CBlockIndex *failed_block, *stopBlock; CWallet::ScanResult result = - pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, failed_block, stopBlock, true); - switch (result) { + pwallet->ScanForWalletTransactions(start_block, stop_block, reserver, true /* fUpdate */); + switch (result.status) { case CWallet::ScanResult::SUCCESS: - break; // stopBlock set by ScanForWalletTransactions + break; case CWallet::ScanResult::FAILURE: throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files."); case CWallet::ScanResult::USER_ABORT: @@ -3630,8 +3626,8 @@ static UniValue rescanblockchain(const JSONRPCRequest& request) // no default case, so the compiler can warn about missing cases } UniValue response(UniValue::VOBJ); - response.pushKV("start_height", pindexStart->nHeight); - response.pushKV("stop_height", stopBlock->nHeight); + response.pushKV("start_height", start_height); + response.pushKV("stop_height", result.stop_height ? *result.stop_height : UniValue()); return response; } diff --git a/src/wallet/test/coinjoin_tests.cpp b/src/wallet/test/coinjoin_tests.cpp index 0f6795249c..03f5ff0a83 100644 --- a/src/wallet/test/coinjoin_tests.cpp +++ b/src/wallet/test/coinjoin_tests.cpp @@ -47,8 +47,9 @@ public: } WalletRescanReserver reserver(wallet.get()); reserver.reserve(); - const CBlockIndex *stop_block, *failed_block; - wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block); + + CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), {} /* stop_block */, reserver, true /* fUpdate */); + BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); } ~CTransactionBuilderTestSetup() @@ -70,8 +71,9 @@ public: blocktx = CMutableTransaction(*it->second.tx); } CreateAndProcessBlock({blocktx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - LOCK2(cs_main, wallet->cs_wallet); - it->second.SetMerkleBranch(::ChainActive().Tip(), 1); + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); + it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1); return it->second; } CompactTallyItem GetTallyItem(const std::vector& vecAmounts) diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 36a80461a6..d62f9d4a46 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -39,7 +39,6 @@ static void AddKey(CWallet& wallet, const CKey& key) BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) { // Cap last block file size, and mine new block in a new block file. - const CBlockIndex* const null_block = nullptr; CBlockIndex* oldTip = ::ChainActive().Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); @@ -49,16 +48,17 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) auto locked_chain = chain->lock(); LockAnnotation lock(::cs_main); // for PruneOneBlockFile - // Verify ScanForWalletTransactions accomodates a null start block. + // Verify ScanForWalletTransactions accommodates a null start block. { CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); - const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; - BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(nullptr, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); - BOOST_CHECK_EQUAL(failed_block, null_block); - BOOST_CHECK_EQUAL(stop_block, null_block); + CWallet::ScanResult result = wallet.ScanForWalletTransactions({} /* start_block */, {} /* stop_block */, reserver, false /* update */); + BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); + BOOST_CHECK(result.failed_block.IsNull()); + BOOST_CHECK(result.stop_block.IsNull()); + BOOST_CHECK(!result.stop_height); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); } @@ -69,10 +69,11 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); - const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; - BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); - BOOST_CHECK_EQUAL(failed_block, null_block); - BOOST_CHECK_EQUAL(stop_block, newTip); + CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */); + BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); + BOOST_CHECK(result.failed_block.IsNull()); + BOOST_CHECK_EQUAL(result.stop_block, newTip->GetBlockHash()); + BOOST_CHECK_EQUAL(*result.stop_height, newTip->nHeight); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 1000 * COIN); } @@ -87,10 +88,11 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); - const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; - BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE); - BOOST_CHECK_EQUAL(failed_block, oldTip); - BOOST_CHECK_EQUAL(stop_block, newTip); + CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */); + BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); + BOOST_CHECK_EQUAL(result.failed_block, oldTip->GetBlockHash()); + BOOST_CHECK_EQUAL(result.stop_block, newTip->GetBlockHash()); + BOOST_CHECK_EQUAL(*result.stop_height, newTip->nHeight); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 500 * COIN); } @@ -104,10 +106,11 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); - const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; - BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE); - BOOST_CHECK_EQUAL(failed_block, newTip); - BOOST_CHECK_EQUAL(stop_block, null_block); + CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */); + BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); + BOOST_CHECK_EQUAL(result.failed_block, newTip->GetBlockHash()); + BOOST_CHECK(result.stop_block.IsNull()); + BOOST_CHECK(!result.stop_height); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); } } @@ -122,8 +125,8 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex* newTip = ::ChainActive().Tip(); - LockAnnotation lock(::cs_main); auto locked_chain = chain->lock(); + LockAnnotation lock(::cs_main); // Prune the older block file. PruneOneBlockFile(oldTip->GetBlockPos().nFile); @@ -284,7 +287,7 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 CWalletTx wtx(&wallet, MakeTransactionRef(tx)); if (block) { - wtx.SetMerkleBranch(block, 0); + wtx.SetMerkleBranch(block->GetBlockHash(), 0); } { LOCK(cs_main); @@ -348,11 +351,11 @@ public: AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); - const CBlockIndex* const null_block = nullptr; - const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; - BOOST_CHECK_EQUAL(wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); - BOOST_CHECK_EQUAL(stop_block, ::ChainActive().Tip()); - BOOST_CHECK_EQUAL(failed_block, null_block); + CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), {} /* stop_block */, reserver, false /* update */); + BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); + BOOST_CHECK_EQUAL(result.stop_block, ::ChainActive().Tip()->GetBlockHash()); + BOOST_CHECK_EQUAL(*result.stop_height, ::ChainActive().Height()); + BOOST_CHECK(result.failed_block.IsNull()); } ~ListCoinsTestingSetup() @@ -382,7 +385,7 @@ public: LOCK(wallet->cs_wallet); auto it = wallet->mapWallet.find(tx->GetHash()); BOOST_CHECK(it != wallet->mapWallet.end()); - it->second.SetMerkleBranch(::ChainActive().Tip(), 1); + it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1); return it->second; } @@ -485,8 +488,8 @@ public: AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); - const CBlockIndex *stop_block, *failed_block; - wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block); + CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash() /* start_block */, {} /* stop_block */, reserver, false /* update */); + BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); } std::unique_ptr m_chain = interfaces::MakeChain(); @@ -588,7 +591,7 @@ public: LOCK2(cs_main, wallet->cs_wallet); auto it = wallet->mapWallet.find(tx->GetHash()); BOOST_CHECK(it != wallet->mapWallet.end()); - it->second.SetMerkleBranch(::ChainActive().Tip(), 1); + it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1); std::vector vecOutpoints; size_t n; @@ -932,8 +935,8 @@ BOOST_FIXTURE_TEST_CASE(select_coins_grouped_by_addresses, ListCoinsTestingSetup // Reveal the mined tx, it should conflict with the one we have in the wallet already. WalletRescanReserver reserver(wallet.get()); reserver.reserve(); - const CBlockIndex *stop_block, *failed_block; - BOOST_CHECK_EQUAL(wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); + auto result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), {}, reserver, false); + BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); { LOCK(wallet->cs_wallet); const auto& conflicts = wallet->GetConflicts(tx2->GetHash()); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0a1ceadcc4..3e05144ad3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1185,20 +1185,20 @@ void CWallet::LoadToWallet(const CWalletTx& wtxIn) } } -bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate) +bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool fUpdate) { const CTransaction& tx = *ptx; { AssertLockHeld(cs_main); // because of AddToWallet AssertLockHeld(cs_wallet); - if (pIndex != nullptr) { + if (!block_hash.IsNull()) { for (const CTxIn& txin : tx.vin) { std::pair range = mapTxSpends.equal_range(txin.prevout); while (range.first != range.second) { if (range.first->second != tx.GetHash()) { - WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), pIndex->GetBlockHash().ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); - MarkConflicted(pIndex->GetBlockHash(), range.first->second); + WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), block_hash.ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); + MarkConflicted(block_hash, range.first->second); } range.first++; } @@ -1231,14 +1231,19 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__); } } - if (pIndex != nullptr && mapKeyMetadata[keyid].nCreateTime > pIndex->nTime) { - WalletLogPrintf("%s: Found a key which appears to be used earlier than we expected, updating metadata\n", __func__); - CPubKey vchPubKey; - bool res = GetPubKey(keyid, vchPubKey); - assert(res); // this should never fail - mapKeyMetadata[keyid].nCreateTime = pIndex->nTime; - batch.WriteKeyMeta(vchPubKey, mapKeyMetadata[keyid]); - UpdateTimeFirstKey(pIndex->nTime); + if (!block_hash.IsNull()) { + int64_t block_time; + bool found_block = chain().findBlock(block_hash, nullptr /* block */, &block_time); + assert(found_block); + if (mapKeyMetadata[keyid].nCreateTime > block_time) { + WalletLogPrintf("%s: Found a key which appears to be used earlier than we expected, updating metadata\n", __func__); + CPubKey vchPubKey; + bool res = GetPubKey(keyid, vchPubKey); + assert(res); // this should never fail + mapKeyMetadata[keyid].nCreateTime = block_time; + batch.WriteKeyMeta(vchPubKey, mapKeyMetadata[keyid]); + UpdateTimeFirstKey(block_time); + } } } } @@ -1246,8 +1251,8 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI CWalletTx wtx(this, ptx); // Get merkle branch if transaction was found in a block - if (pIndex != nullptr) - wtx.SetMerkleBranch(pIndex, posInBlock); + if (!block_hash.IsNull()) + wtx.SetMerkleBranch(block_hash, posInBlock); return AddToWallet(wtx, false); } @@ -1338,11 +1343,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) LockAnnotation lock(::cs_main); LOCK(cs_wallet); // check WalletBatch::LoadWallet() - int conflictconfirms = 0; - CBlockIndex* pindex = LookupBlockIndex(hashBlock); - if (pindex && ::ChainActive().Contains(pindex)) { - conflictconfirms = -(::ChainActive().Height() - pindex->nHeight + 1); - } + int conflictconfirms = -locked_chain->getBlockDepth(hashBlock); // If number of conflict confirms cannot be determined, this means // 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 @@ -1391,8 +1392,8 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) fAnonymizableTallyCachedNonDenom = false; } -void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pindex, int posInBlock, bool update_tx) { - if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, update_tx)) +void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool update_tx) { + if (!AddToWalletIfInvolvingMe(ptx, block_hash, posInBlock, update_tx)) return; // Not one of ours // If a transaction changes 'conflicted' state, that changes the balance @@ -1407,7 +1408,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pin void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx, int64_t nAcceptTime) { auto locked_chain = chain().lock(); LOCK(cs_wallet); - SyncTransaction(ptx); + SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); auto it = mapWallet.find(ptx->GetHash()); if (it != mapWallet.end()) { @@ -1437,17 +1438,17 @@ void CWallet::BlockConnected(const std::shared_ptr& pblock, const // the notification that the conflicted transaction was evicted. for (const CTransactionRef& ptx : vtxConflicted) { - SyncTransaction(ptx); + SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); // UNKNOWN because it's a manual removal, not using mempool logic TransactionRemovedFromMempool(ptx, MemPoolRemovalReason::UNKNOWN); } for (size_t i = 0; i < pblock->vtx.size(); i++) { - SyncTransaction(pblock->vtx[i], pindex, i); + SyncTransaction(pblock->vtx[i], pindex->GetBlockHash(), i); // UNKNOWN because it's a manual removal, not using mempool logic TransactionRemovedFromMempool(pblock->vtx[i], MemPoolRemovalReason::UNKNOWN); } - m_last_block_processed = pindex; + m_last_block_processed = pindex->GetBlockHash(); // The GUI expects a NotifyTransactionChanged when a coinbase tx // which is in our wallet moves from in-the-best-block to @@ -1487,7 +1488,7 @@ void CWallet::BlockDisconnected(const std::shared_ptr& pblock, con for (const CTransactionRef& ptx : pblock->vtx) { // NOTE: do NOT pass pindex here - SyncTransaction(ptx); + SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); } // reset cache to make sure no longer mature coins are excluded @@ -1508,9 +1509,8 @@ void CWallet::BlockUntilSyncedToCurrentChain() { // protected by cs_wallet instead of cs_main, but as long as we need // cs_main here anyway, it's easier to just call it cs_main-protected. auto locked_chain = chain().lock(); - const CBlockIndex* initialChainTip = ::ChainActive().Tip(); - if (m_last_block_processed && m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) { + if (!m_last_block_processed.IsNull() && locked_chain->isPotentialTip(m_last_block_processed)) { return; } } @@ -2132,133 +2132,144 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r // Find starting block. May be null if nCreateTime is greater than the // highest blockchain timestamp, in which case there is nothing that needs // to be scanned. - CBlockIndex* startBlock = nullptr; + uint256 start_block; { auto locked_chain = chain().lock(); - startBlock = ::ChainActive().FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); - WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? ::ChainActive().Height() - startBlock->nHeight + 1 : 0); + const Optional start_height = locked_chain->findFirstBlockWithTime(startTime - TIMESTAMP_WINDOW, &start_block); + const Optional tip_height = locked_chain->getHeight(); + WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, tip_height && start_height ? *tip_height - *start_height + 1 : 0); } - if (startBlock) { - const CBlockIndex *failedBlock, *stop_block; + if (!start_block.IsNull()) { // TODO: this should take into account failure by ScanResult::USER_ABORT - if (ScanResult::FAILURE == ScanForWalletTransactions(startBlock, nullptr, reserver, failedBlock, stop_block, update)) { - return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1; + ScanResult result = ScanForWalletTransactions(start_block, {} /* stop_block */, reserver, update); + if (result.status == ScanResult::FAILURE) { + int64_t time_max; + if (!chain().findBlock(result.failed_block, nullptr /* block */, nullptr /* time */, &time_max)) { + throw std::logic_error("ScanForWalletTransactions returned invalid block hash"); + } + return time_max + TIMESTAMP_WINDOW + 1; } } return startTime; } /** - * Scan the block chain (starting in pindexStart) for transactions + * Scan the block chain (starting in start_block) for transactions * from or to us. If fUpdate is true, found transactions that already * exist in the wallet will be updated. * - * @param[in] pindexStop if not a nullptr, the scan will stop at this block-index - * @param[out] failed_block if FAILURE is returned, the most recent block - * that could not be scanned, otherwise nullptr - * @param[out] stop_block the most recent block that could be scanned, - * otherwise nullptr if no block could be scanned + * @param[in] start_block if not null, the scan will start at this block instead + * of the genesis block + * @param[in] stop_block if not null, the scan will stop at this block instead + * of the chain tip * * @return ScanResult indicating success or failure of the scan. SUCCESS if * scan was successful. FAILURE if a complete rescan was not possible (due to * pruning or corruption). USER_ABORT if the rescan was aborted before it * could complete. * - * @pre Caller needs to make sure pindexStop (and the optional pindexStart) are on + * @pre Caller needs to make sure start_block (and the optional stop_block) are on * the main chain after to the addition of any new keys you want to detect * transactions for. */ -CWallet::ScanResult CWallet::ScanForWalletTransactions(const CBlockIndex* const pindexStart, const CBlockIndex* const pindexStop, const WalletRescanReserver& reserver, const CBlockIndex*& failed_block, const CBlockIndex*& stop_block, bool fUpdate) +CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, const uint256& stop_block, const WalletRescanReserver& reserver, bool fUpdate) { int64_t nNow = GetTime(); - const CChainParams& chainParams = Params(); assert(reserver.isReserved()); - if (pindexStop) { - assert(pindexStop->nHeight >= pindexStart->nHeight); - } - const CBlockIndex* pindex = pindexStart; - failed_block = nullptr; - stop_block = nullptr; + uint256 block_hash = start_block; + ScanResult result; - if (pindex) WalletLogPrintf("Rescan started from block %d...\n", pindex->nHeight); + WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString()); { fAbortRescan = false; ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup - CBlockIndex* tip = nullptr; + uint256 tip_hash; + Optional block_height; double progress_begin; double progress_end; { auto locked_chain = chain().lock(); - progress_begin = GuessVerificationProgress(chainParams.TxData(), pindex); - if (pindexStop == nullptr) { - tip = ::ChainActive().Tip(); - progress_end = GuessVerificationProgress(chainParams.TxData(), tip); - } else { - progress_end = GuessVerificationProgress(chainParams.TxData(), pindexStop); + if (Optional tip_height = locked_chain->getHeight()) { + tip_hash = locked_chain->getBlockHash(*tip_height); } + block_height = locked_chain->getBlockHeight(block_hash); + progress_begin = chain().guessVerificationProgress(block_hash); + progress_end = chain().guessVerificationProgress(stop_block.IsNull() ? tip_hash : stop_block); } double progress_current = progress_begin; - while (pindex && !fAbortRescan && !ShutdownRequested()) { + while (block_height && !fAbortRescan && !ShutdownRequested()) { m_scanning_progress = (progress_current - progress_begin) / (progress_end - progress_begin); - if (pindex->nHeight % 100 == 0 && progress_end - progress_begin > 0.0) { + if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) { ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100)))); } if (GetTime() >= nNow + 60) { nNow = GetTime(); - WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, progress_current); + WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", *block_height, progress_current); } CBlock block; - if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) { + if (chain().findBlock(block_hash, &block) && !block.IsNull()) { auto locked_chain = chain().lock(); LOCK(cs_wallet); - if (pindex && !::ChainActive().Contains(pindex)) { + if (!locked_chain->getBlockHeight(block_hash)) { // Abort scan if current block is no longer active, to prevent // marking transactions as coming from the wrong block. - failed_block = pindex; + // TODO: This should return success instead of failure, see + // https://github.com/bitcoin/bitcoin/pull/14711#issuecomment-458342518 + result.failed_block = block_hash; + result.status = ScanResult::FAILURE; break; } for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { - SyncTransaction(block.vtx[posInBlock], pindex, posInBlock, fUpdate); + SyncTransaction(block.vtx[posInBlock], block_hash, posInBlock, fUpdate); } // scan succeeded, record block as most recent successfully scanned - stop_block = pindex; + result.stop_block = block_hash; + result.stop_height = *block_height; } else { // could not scan block, keep scanning but record this block as the most recent failure - failed_block = pindex; + result.failed_block = block_hash; + result.status = ScanResult::FAILURE; } - if (pindex == pindexStop) { + if (block_hash == stop_block) { break; } { auto locked_chain = chain().lock(); - pindex = ::ChainActive().Next(pindex); - progress_current = GuessVerificationProgress(chainParams.TxData(), pindex); - if (pindexStop == nullptr && tip != ::ChainActive().Tip()) { - tip = ::ChainActive().Tip(); + Optional tip_height = locked_chain->getHeight(); + if (!tip_height || *tip_height <= block_height || !locked_chain->getBlockHeight(block_hash)) { + // break successfully when rescan has reached the tip, or + // previous block is no longer on the chain due to a reorg + break; + } + + // increment block and verification progress + block_hash = locked_chain->getBlockHash(++*block_height); + progress_current = chain().guessVerificationProgress(block_hash); + + // handle updated tip hash + const uint256 prev_tip_hash = tip_hash; + tip_hash = locked_chain->getBlockHash(*tip_height); + if (stop_block.IsNull() && prev_tip_hash != tip_hash) { // in case the tip has changed, update progress max - progress_end = GuessVerificationProgress(chainParams.TxData(), tip); + progress_end = chain().guessVerificationProgress(tip_hash); } } } ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 100); // hide progress dialog in GUI - if (pindex && fAbortRescan) { - WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, progress_current); - return ScanResult::USER_ABORT; - } else if (pindex && ShutdownRequested()) { - WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", pindex->nHeight, progress_current); - return ScanResult::USER_ABORT; + if (block_height && fAbortRescan) { + WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", block_height.value_or(0), progress_current); + result.status = ScanResult::USER_ABORT; + } else if (block_height && ShutdownRequested()) { + WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", block_height.value_or(0), progress_current); + result.status = ScanResult::USER_ABORT; } } - if (failed_block) { - return ScanResult::FAILURE; - } else { - return ScanResult::SUCCESS; - } + return result; } void CWallet::ReacceptWalletTransactions() @@ -3474,6 +3485,8 @@ bool CWallet::GetBudgetSystemCollateralTX(interfaces::Chain::Lock& locked_chain, bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign, int nExtraPayloadSize) { + uint32_t const height = locked_chain.getHeight().value_or(-1); + CAmount nValue = 0; int nChangePosRequest = nChangePosInOut; unsigned int nSubtractFeeFromAmount = 0; @@ -3518,7 +3531,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // now we ensure code won't be written that makes assumptions about // nLockTime that preclude a fix later. - txNew.nLockTime = ::ChainActive().Height(); + txNew.nLockTime = height; // Secondly occasionally randomly pick a nLockTime even further back, so // that transactions that are delayed after signing for whatever reason, @@ -3527,7 +3540,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std if (GetRandInt(10) == 0) txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100)); - assert(txNew.nLockTime <= (unsigned int)::ChainActive().Height()); + assert(txNew.nLockTime <= height); assert(txNew.nLockTime < LOCKTIME_THRESHOLD); FeeCalculation feeCalc; CFeeRate discard_rate = coin_control.m_discard_feerate ? *coin_control.m_discard_feerate : GetDiscardRate(*this, ::feeEstimator); @@ -4673,11 +4686,12 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map mapKeyFirstBlock; + const Optional tip_height = locked_chain.getHeight(); + const int max_height = tip_height && *tip_height > 144 ? *tip_height - 144 : 0; // the tip can be reorganized; use a 144-block safety margin + std::map mapKeyFirstBlock; for (const CKeyID &keyid : GetKeys()) { if (mapKeyBirth.count(keyid) == 0) - mapKeyFirstBlock[keyid] = pindexMax; + mapKeyFirstBlock[keyid] = max_height; } // if there are no such keys, we're done @@ -4689,18 +4703,16 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map height = locked_chain.getBlockHeight(wtx.hashBlock)) { // ... which are already in a block - int nHeight = pindex->nHeight; for (const CTxOut &txout : wtx.tx->vout) { // iterate over all their outputs CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey); for (const CKeyID &keyid : vAffected) { // ... and all their affected keys - std::map::iterator rit = mapKeyFirstBlock.find(keyid); - if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight) - rit->second = pindex; + std::map::iterator rit = mapKeyFirstBlock.find(keyid); + if (rit != mapKeyFirstBlock.end() && *height < rit->second) + rit->second = *height; } vAffected.clear(); } @@ -4709,7 +4721,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::mapGetBlockTime() - TIMESTAMP_WINDOW; // block times can be 2h off + mapKeyBirth[entry.first] = locked_chain.getBlockTime(entry.second) - TIMESTAMP_WINDOW; // block times can be 2h off } /** @@ -4738,7 +4750,8 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const AssertLockHeld(::cs_main); unsigned int nTimeSmart = wtx.nTimeReceived; if (!wtx.hashUnset()) { - if (const CBlockIndex* pindex = LookupBlockIndex(wtx.hashBlock)) { + int64_t blocktime; + if (chain().findBlock(wtx.hashBlock, nullptr /* block */, &blocktime)) { int64_t latestNow = wtx.nTimeReceived; int64_t latestEntry = 0; @@ -4764,7 +4777,6 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const } } - int64_t blocktime = pindex->GetBlockTime(); nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); } else { WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString()); @@ -5007,7 +5019,7 @@ std::shared_ptr CWallet::CreateWalletFromFile(interfaces::Chain& chain, } auto locked_chain = chain.assumeLocked(); // Temporary. Removed in upcoming lock cleanup - walletInstance->ChainStateFlushed(::ChainActive().GetLocator()); + walletInstance->ChainStateFlushed(locked_chain->getLocator()); // Try to create wallet backup right after new wallet was created std::string strBackupWarning; @@ -5113,59 +5125,68 @@ std::shared_ptr CWallet::CreateWalletFromFile(interfaces::Chain& chain, // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); - LockAnnotation lock(::cs_main); // Temporary, for FindForkInGlobalIndex below. Removed in upcoming commit. auto locked_chain = chain.lock(); LOCK(walletInstance->cs_wallet); - CBlockIndex *pindexRescan = ::ChainActive().Genesis(); + int rescan_height = 0; if (!gArgs.GetBoolArg("-rescan", false)) { WalletBatch batch(*walletInstance->database); CBlockLocator locator; - if (batch.ReadBestBlock(locator)) - pindexRescan = FindForkInGlobalIndex(::ChainActive(), locator); + if (batch.ReadBestBlock(locator)) { + if (const Optional fork_height = locked_chain->findLocatorFork(locator)) { + rescan_height = *fork_height; + } + } } - walletInstance->m_last_block_processed = ::ChainActive().Tip(); + const Optional tip_height = locked_chain->getHeight(); + if (tip_height) { + walletInstance->m_last_block_processed = locked_chain->getBlockHash(*tip_height); + } else { + walletInstance->m_last_block_processed.SetNull(); + } - if (::ChainActive().Tip() && ::ChainActive().Tip() != pindexRescan) + if (tip_height && *tip_height != rescan_height) { //We can't rescan beyond non-pruned blocks, stop and throw an error //this might happen if a user uses an old wallet within a pruned node // or if he ran -disablewallet for a longer time, then decided to re-enable if (fPruneMode) { - CBlockIndex *block = ::ChainActive().Tip(); - while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA) && block->pprev->nTx > 0 && pindexRescan != block) - block = block->pprev; + int block_height = *tip_height; + while (block_height > 0 && locked_chain->haveBlockOnDisk(block_height - 1) && rescan_height != block_height) { + --block_height; + } - if (pindexRescan != block) { + if (rescan_height != block_height) { return error(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)")); } } uiInterface.InitMessage(_("Rescanning...")); - walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", ::ChainActive().Height() - pindexRescan->nHeight, pindexRescan->nHeight); + walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", *tip_height - rescan_height, rescan_height); // No need to read and scan block if block was created before // our wallet birthday (as adjusted for block time variability) // unless a full rescan was requested if (gArgs.GetArg("-rescan", 0) != 2) { - while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) { - pindexRescan = ::ChainActive().Next(pindexRescan); + if (walletInstance->nTimeFirstKey) { + if (Optional first_block = locked_chain->findFirstBlockWithTimeAndHeight(walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW, rescan_height)) { + rescan_height = *first_block; + } } } nStart = GetTimeMillis(); { WalletRescanReserver reserver(walletInstance.get()); - const CBlockIndex *stop_block, *failed_block; - if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, failed_block, stop_block, true))) { + if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(locked_chain->getBlockHash(rescan_height), {} /* stop block */, reserver, true /* update */).status)) { return error(_("Failed to rescan the wallet during initialization")); } } walletInstance->WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - nStart); - walletInstance->ChainStateFlushed(::ChainActive().GetLocator()); + walletInstance->ChainStateFlushed(locked_chain->getLocator()); walletInstance->database->IncrementUpdateCounter(); // Restore wallet transaction metadata after -zapwallettxes=1 @@ -5438,10 +5459,10 @@ CWalletKey::CWalletKey(int64_t nExpires) nTimeExpires = nExpires; } -void CMerkleTx::SetMerkleBranch(const CBlockIndex* pindex, int posInBlock) +void CMerkleTx::SetMerkleBranch(const uint256& block_hash, int posInBlock) { // Update the tx's hashBlock - hashBlock = pindex->GetBlockHash(); + hashBlock = block_hash; // set the position of the transaction in the block nIndex = posInBlock; @@ -5454,12 +5475,7 @@ int CMerkleTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const AssertLockHeld(cs_main); - // Find the block it claims to be in - CBlockIndex* pindex = LookupBlockIndex(hashBlock); - if (!pindex || !::ChainActive().Contains(pindex)) - return 0; - - return ((nIndex == -1) ? (-1) : 1) * (::ChainActive().Height() - pindex->nHeight + 1); + return locked_chain.getBlockDepth(hashBlock) * (nIndex == -1 ? -1 : 1); } bool CMerkleTx::IsLockedByInstantSend() const diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d01d5c4512..3ae8728a18 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -97,7 +97,6 @@ static const bool DEFAULT_DISABLE_WALLET = false; //! if set, all keys will be derived by using BIP39/BIP44 static const bool DEFAULT_USE_HD_WALLET = false; -class CBlockIndex; class CCoinControl; class CKey; class COutput; @@ -283,7 +282,7 @@ public: READWRITE(obj.tx, obj.hashBlock, vMerkleBranch, obj.nIndex); } - void SetMerkleBranch(const CBlockIndex* pIndex, int posInBlock); + void SetMerkleBranch(const uint256& block_hash, int posInBlock); /** * Return depth of transaction in blockchain: @@ -711,7 +710,7 @@ private: * Abandoned state should probably be more carefully tracked via different * posInBlock signals or by checking mempool presence when necessary. */ - bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */ void MarkConflicted(const uint256& hashBlock, const uint256& hashTx); @@ -722,8 +721,8 @@ private: void SyncMetaData(std::pair) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions. - * Should be called with pindexBlock and posInBlock if this is for a transaction that is included in a block. */ - void SyncTransaction(const CTransactionRef& tx, const CBlockIndex *pindex = nullptr, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + * Should be called with non-zero block_hash and posInBlock if this is for a transaction that is included in a block. */ + void SyncTransaction(const CTransactionRef& tx, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* HD derive new child key (on internal or external chain) */ void DeriveNewChildKey(WalletBatch &batch, const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal /*= false*/) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -770,10 +769,8 @@ private: * Note that this is *not* how far we've processed, we may need some rescan * to have seen all transactions in the chain, but is only used to track * live BlockConnected callbacks. - * - * Protected by cs_main (see BlockUntilSyncedToCurrentChain) */ - const CBlockIndex* m_last_block_processed = nullptr; + uint256 m_last_block_processed; /** Pulled from wallet DB ("ps_salt") and used when mixing a random number of rounds. * This salt is needed to prevent an attacker from learning how many extra times @@ -1004,12 +1001,22 @@ public: void BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected) override; int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); - enum class ScanResult { - SUCCESS, - FAILURE, - USER_ABORT + struct ScanResult { + enum { SUCCESS, FAILURE, USER_ABORT } status = SUCCESS; + + //! Hash and height of most recent block that was successfully scanned. + //! Unset if no blocks were scanned due to read errors or the chain + //! being empty. + uint256 stop_block; + Optional stop_height; + + //! Height of the most recent block that could not be scanned due to + //! read errors or pruning. Will be set if status is FAILURE, unset if + //! status is SUCCESS, and may or may not be set if status is + //! USER_ABORT. + uint256 failed_block; }; - ScanResult ScanForWalletTransactions(const CBlockIndex* const pindexStart, const CBlockIndex* const pindexStop, const WalletRescanReserver& reserver, const CBlockIndex*& failed_block, const CBlockIndex*& stop_block, bool fUpdate = false); + ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate); void TransactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) override; void ReacceptWalletTransactions(); void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override EXCLUSIVE_LOCKS_REQUIRED(cs_main);