diff --git a/src/Makefile.am b/src/Makefile.am --- a/src/Makefile.am +++ b/src/Makefile.am @@ -168,6 +168,7 @@ netbase.h \ netmessagemaker.h \ noui.h \ + optional.h \ outputtype.h \ policy/fees.h \ policy/mempool.h \ diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -5,10 +5,16 @@ #ifndef BITCOIN_INTERFACES_CHAIN_H #define BITCOIN_INTERFACES_CHAIN_H +#include + +#include #include #include #include +struct BlockHash; +class CBlock; +struct CBlockLocator; class CChainParams; class CScheduler; @@ -27,6 +33,79 @@ class Lock { 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 BlockHash &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 BlockHash &hash) = 0; + + //! Get block hash. Height must be valid or this function will abort. + virtual BlockHash 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, + BlockHash *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 BlockHash &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 BlockHash &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 @@ -37,6 +116,20 @@ //! 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 BlockHash &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 BlockHash &block_hash) = 0; }; //! Interface to let node manage chain clients (wallets, or maybe tools for diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -4,6 +4,10 @@ #include +#include +#include +#include +#include #include #include #include @@ -14,7 +18,122 @@ namespace interfaces { namespace { - class LockImpl : public Chain::Lock {}; + class LockImpl : public Chain::Lock { + Optional getHeight() override { + int height = ::ChainActive().Height(); + if (height >= 0) { + return height; + } + return nullopt; + } + Optional getBlockHeight(const BlockHash &hash) override { + CBlockIndex *block = LookupBlockIndex(hash); + if (block && ::ChainActive().Contains(block)) { + return block->nHeight; + } + return nullopt; + } + int getBlockDepth(const BlockHash &hash) override { + const Optional tip_height = getHeight(); + const Optional height = getBlockHeight(hash); + return tip_height && height ? *tip_height - *height + 1 : 0; + } + BlockHash 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.hasData() != 0) && block->nTx > 0; + } + Optional findFirstBlockWithTime(int64_t time, + BlockHash *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.hasData() == 0) { + return block->nHeight; + } + block = block->pprev; + } + } + return nullopt; + } + Optional findFork(const BlockHash &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 BlockHash &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 { @@ -34,6 +153,33 @@ std::unique_ptr assumeLocked() override { return std::make_unique(); } + bool findBlock(const BlockHash &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 BlockHash &block_hash) override { + LOCK(cs_main); + return GuessVerificationProgress(Params().TxData(), + LookupBlockIndex(block_hash)); + } }; } // namespace diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -315,8 +315,13 @@ if (mi == m_wallet.mapWallet.end()) { return false; } - num_blocks = ::ChainActive().Height(); - block_time = ::ChainActive().Tip()->GetBlockTime(); + if (Optional height = locked_chain->getHeight()) { + num_blocks = *height; + block_time = locked_chain->getBlockTime(*height); + } else { + num_blocks = -1; + block_time = -1; + } tx_status = MakeWalletTxStatus(*locked_chain, mi->second); return true; } @@ -328,7 +333,7 @@ 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); @@ -363,7 +368,7 @@ return false; } balances = getBalances(); - num_blocks = ::ChainActive().Height(); + num_blocks = locked_chain->getHeight().value_or(-1); return true; } Amount getBalance() override { return m_wallet.GetBalance(); } diff --git a/src/optional.h b/src/optional.h new file mode 100644 --- /dev/null +++ b/src/optional.h @@ -0,0 +1,16 @@ +// 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 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -130,14 +130,12 @@ auto locked_chain = wallet->chain().lock(); 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), BlockHash(), 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 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -445,9 +445,8 @@ if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) { auto locked_chain = pwallet->chain().lock(); - 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"); } @@ -681,7 +680,8 @@ 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(1, file.tellg()); file.seekg(0, file.beg); @@ -969,12 +969,16 @@ // produce output file << strprintf("# Wallet dump created by Bitcoin %s\n", CLIENT_BUILD); file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime())); + const Optional tip_height = locked_chain->getHeight(); 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())); + 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"; // add the base58check encoded extended master if the wallet uses HD @@ -1592,16 +1596,17 @@ 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 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -126,8 +126,11 @@ if (confirms > 0) { 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)); } @@ -1936,27 +1939,21 @@ auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - // Block index of the specified block or the common ancestor, if the block + // Height of the specified block or the common ancestor, if the block // provided was in a deactivated chain. - const CBlockIndex *pindex = nullptr; - // Block index of the specified block, even if it's in a deactivated chain. - const CBlockIndex *paltindex = nullptr; + Optional height; + // Height of the specified block, even if it's in a deactivated chain. + Optional altheight; int target_confirms = 1; isminefilter filter = ISMINE_SPENDABLE; + BlockHash blockId; if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { - BlockHash blockId(ParseHashV(request.params[0], "blockhash")); - - paltindex = pindex = LookupBlockIndex(blockId); - if (!pindex) { + blockId = BlockHash(ParseHashV(request.params[0], "blockhash")); + height = locked_chain->findFork(blockId, &altheight); + 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()) { @@ -1974,7 +1971,8 @@ 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); @@ -1987,14 +1985,12 @@ } } - const Consensus::Params ¶ms = config.GetChainParams().GetConsensus(); - // 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)) { + if (!pwallet->chain().findBlock(blockId, &block) || block.IsNull()) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); } @@ -2009,12 +2005,14 @@ 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; + BlockHash lastblock = last_height >= 0 + ? locked_chain->getBlockHash(last_height) + : BlockHash(); UniValue ret(UniValue::VOBJ); ret.pushKV("transactions", transactions); @@ -4033,56 +4031,54 @@ "Wallet is currently rescanning. Abort existing rescan or wait."); } - CBlockIndex *pindexStart = nullptr; - CBlockIndex *pindexStop = nullptr; - CBlockIndex *pChainTip = nullptr; + int start_height = 0; + BlockHash 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.hasData()) { - 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) { + start_block, stop_block, reserver, true /* fUpdate */); + switch (result.status) { case CWallet::ScanResult::SUCCESS: - // stopBlock set by ScanForWalletTransactions break; case CWallet::ScanResult::FAILURE: throw JSONRPCError( @@ -4093,8 +4089,9 @@ // 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/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -35,7 +35,6 @@ auto chain = interfaces::MakeChain(); // 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())); @@ -51,14 +50,12 @@ 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( + BlockHash(), BlockHash(), 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.GetImmatureBalance(), Amount::zero()); } @@ -70,14 +67,12 @@ 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(), BlockHash(), 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.GetImmatureBalance(), 100 * COIN); } @@ -93,14 +88,12 @@ 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(), BlockHash(), 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.GetImmatureBalance(), 50 * COIN); } @@ -115,14 +108,12 @@ 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(), BlockHash(), 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.GetImmatureBalance(), Amount::zero()); } } @@ -315,7 +306,7 @@ CWalletTx wtx(&wallet, MakeTransactionRef(tx)); if (block) { - wtx.SetMerkleBranch(block, 0); + wtx.SetMerkleBranch(block->GetBlockHash(), 0); } { LOCK(cs_main); @@ -377,15 +368,14 @@ 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(), BlockHash(), 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() { wallet.reset(); } @@ -414,7 +404,7 @@ LOCK(wallet->cs_wallet); auto it = wallet->mapWallet.find(tx->GetId()); BOOST_CHECK(it != wallet->mapWallet.end()); - it->second.SetMerkleBranch(::ChainActive().Tip(), 1); + it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1); return it->second; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -92,7 +92,6 @@ static const bool DEFAULT_WALLETBROADCAST = true; static const bool DEFAULT_DISABLE_WALLET = false; -class CBlockIndex; class CChainParams; class CCoinControl; class COutput; @@ -301,7 +300,7 @@ READWRITE(nIndex); } - void SetMerkleBranch(const CBlockIndex *pIndex, int posInBlock); + void SetMerkleBranch(const BlockHash &block_hash, int posInBlock); /** * Return depth of transaction in blockchain: @@ -725,7 +724,7 @@ * when necessary. */ bool AddToWalletIfInvolvingMe(const CTransactionRef &tx, - const CBlockIndex *pIndex, int posInBlock, + const BlockHash &block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -748,11 +747,10 @@ /** * Used by * TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions. - * Should be called with pindexBlock and posInBlock if this is for a + * 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 CBlockIndex *pindex = nullptr, + void SyncTransaction(const CTransactionRef &tx, const BlockHash &block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -803,10 +801,8 @@ * 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; + BlockHash m_last_block_processed; public: const CChainParams &chainParams; @@ -1063,13 +1059,25 @@ int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver &reserver, bool update); - enum class ScanResult { SUCCESS, FAILURE, USER_ABORT }; - ScanResult ScanForWalletTransactions(const CBlockIndex *const pindexStart, - const CBlockIndex *const pindexStop, + 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. + BlockHash stop_block; + Optional stop_height; + + //! Hash 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. + BlockHash failed_block; + }; + ScanResult ScanForWalletTransactions(const BlockHash &first_block, + const BlockHash &last_block, const WalletRescanReserver &reserver, - const CBlockIndex *&failed_block, - const CBlockIndex *&stop_block, - bool fUpdate = false); + bool fUpdate); void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; void ReacceptWalletTransactions(); void ResendWalletTransactions(int64_t nBestBlockTime, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1041,12 +1041,12 @@ } bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef &ptx, - const CBlockIndex *pIndex, + const BlockHash &block_hash, int posInBlock, bool fUpdate) { const CTransaction &tx = *ptx; AssertLockHeld(cs_wallet); - if (pIndex != nullptr) { + if (!block_hash.IsNull()) { for (const CTxIn &txin : tx.vin) { std::pair range = mapTxSpends.equal_range(txin.prevout); @@ -1055,12 +1055,11 @@ WalletLogPrintf( "Transaction %s (in block %s) conflicts with wallet " "transaction %s (both spend %s:%i)\n", - tx.GetId().ToString(), - pIndex->GetBlockHash().ToString(), + tx.GetId().ToString(), block_hash.ToString(), range.first->second.ToString(), range.first->first.GetTxId().ToString(), range.first->first.GetN()); - MarkConflicted(pIndex->GetBlockHash(), range.first->second); + MarkConflicted(block_hash, range.first->second); } range.first++; } @@ -1107,8 +1106,8 @@ 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); @@ -1201,11 +1200,7 @@ auto locked_chain = chain().lock(); LOCK(cs_wallet); - 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 @@ -1256,9 +1251,9 @@ } void CWallet::SyncTransaction(const CTransactionRef &ptx, - const CBlockIndex *pindex, int posInBlock, + const BlockHash &block_hash, int posInBlock, bool update_tx) { - if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, update_tx)) { + if (!AddToWalletIfInvolvingMe(ptx, block_hash, posInBlock, update_tx)) { // Not one of ours return; } @@ -1272,7 +1267,7 @@ void CWallet::TransactionAddedToMempool(const CTransactionRef &ptx) { auto locked_chain = chain().lock(); LOCK(cs_wallet); - SyncTransaction(ptx); + SyncTransaction(ptx, BlockHash(), 0 /* position in block */); auto it = mapWallet.find(ptx->GetId()); if (it != mapWallet.end()) { @@ -1302,16 +1297,16 @@ // transaction and then have it inadvertently cleared by the notification // that the conflicted transaction was evicted. for (const CTransactionRef &ptx : vtxConflicted) { - SyncTransaction(ptx); + SyncTransaction(ptx, BlockHash(), 0 /* position in block */); TransactionRemovedFromMempool(ptx); } for (size_t i = 0; i < pblock->vtx.size(); i++) { - SyncTransaction(pblock->vtx[i], pindex, i); + SyncTransaction(pblock->vtx[i], pindex->GetBlockHash(), i); TransactionRemovedFromMempool(pblock->vtx[i]); } - m_last_block_processed = pindex; + m_last_block_processed = pindex->GetBlockHash(); } void CWallet::BlockDisconnected(const std::shared_ptr &pblock) { @@ -1319,7 +1314,7 @@ LOCK(cs_wallet); for (const CTransactionRef &ptx : pblock->vtx) { - SyncTransaction(ptx); + SyncTransaction(ptx, BlockHash(), 0 /* position in block */); } } @@ -1334,10 +1329,8 @@ // 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; } } @@ -1790,68 +1783,65 @@ // 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; + BlockHash 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, BlockHash(), + 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 from or to + * 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 + * @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) { + const BlockHash &start_block, const BlockHash &stop_block, + const WalletRescanReserver &reserver, bool fUpdate) { int64_t nNow = GetTime(); assert(reserver.isReserved()); - if (pindexStop) { - assert(pindexStop->nHeight >= pindexStart->nHeight); - } - const CBlockIndex *pindex = pindexStart; - failed_block = nullptr; - stop_block = nullptr; + BlockHash 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; @@ -1860,25 +1850,23 @@ // on startup. ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 0); - CBlockIndex *tip = nullptr; + BlockHash 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()) { - if (pindex->nHeight % 100 == 0 && + while (block_height && !fAbortRescan && !ShutdownRequested()) { + if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) { ShowProgress( strprintf("%s " + _("Rescanning..."), GetDisplayName()), @@ -1891,46 +1879,62 @@ if (GetTime() >= nNow + 60) { nNow = GetTime(); WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", - pindex->nHeight, progress_current); + *block_height, progress_current); } CBlock block; - if (ReadBlockFromDisk(block, pindex, chainParams.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); + 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 = - GuessVerificationProgress(chainParams.TxData(), pindex); - if (pindexStop == nullptr && tip != ::ChainActive().Tip()) { - tip = ::ChainActive().Tip(); + chain().guessVerificationProgress(block_hash); + + // handle updated tip hash + const BlockHash 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); } } } @@ -1938,19 +1942,19 @@ // Hide progress dialog in GUI. ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 100); - if (pindex && fAbortRescan) { + if (block_height && fAbortRescan) { WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", - pindex->nHeight, progress_current); - return ScanResult::USER_ABORT; - } else if (pindex && ShutdownRequested()) { + 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", - pindex->nHeight, progress_current); - return ScanResult::USER_ABORT; + block_height.value_or(0), progress_current); + result.status = ScanResult::USER_ABORT; } } - return failed_block ? ScanResult::FAILURE : ScanResult::SUCCESS; + return result; } void CWallet::ReacceptWalletTransactions() { @@ -2950,6 +2954,7 @@ */ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain::Lock &locked_chain) { + uint32_t const height = locked_chain.getHeight().value_or(-1); uint32_t locktime; // Discourage fee sniping. // @@ -2972,7 +2977,7 @@ // now we ensure code won't be written that makes assumptions about // nLockTime that preclude a fix later. if (IsCurrentForAntiFeeSniping(locked_chain)) { - locktime = ChainActive().Height(); + locktime = height; // Secondly occasionally randomly pick a nLockTime even further back, so // that transactions that are delayed after signing for whatever reason, @@ -2988,7 +2993,7 @@ // constant. locktime = 0; } - assert(locktime <= uint32_t(ChainActive().Height())); + assert(locktime <= height); assert(locktime < LOCKTIME_THRESHOLD); return locktime; } @@ -4180,14 +4185,15 @@ } } - // Map in which we'll infer heights of other keys the tip can be - // reorganized; use a 144-block safety margin. - CBlockIndex *pindexMax = - ::ChainActive()[std::max(0, ::ChainActive().Height() - 144)]; - std::map mapKeyFirstBlock; + // Map in which we'll infer heights of other keys + const Optional tip_height = locked_chain.getHeight(); + // the tip can be reorganized; use a 144-block safety margin + const int max_height = + tip_height && *tip_height > 144 ? *tip_height - 144 : 0; + std::map mapKeyFirstBlock; for (const CKeyID &keyid : GetKeys()) { if (mapKeyBirth.count(keyid) == 0) { - mapKeyFirstBlock[keyid] = pindexMax; + mapKeyFirstBlock[keyid] = max_height; } } @@ -4201,21 +4207,19 @@ for (const auto &entry : mapWallet) { // iterate over all wallet transactions... const CWalletTx &wtx = entry.second; - CBlockIndex *pindex = LookupBlockIndex(wtx.hashBlock); - if (pindex && ::ChainActive().Contains(pindex)) { + if (Optional 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 = + std::map::iterator rit = mapKeyFirstBlock.find(keyid); if (rit != mapKeyFirstBlock.end() && - nHeight < rit->second->nHeight) { - rit->second = pindex; + *height < rit->second) { + rit->second = *height; } } vAffected.clear(); @@ -4227,7 +4231,7 @@ for (const auto &entry : mapKeyFirstBlock) { // block times can be 2h off mapKeyBirth[entry.first] = - entry.second->GetBlockTime() - TIMESTAMP_WINDOW; + locked_chain.getBlockTime(entry.second) - TIMESTAMP_WINDOW; } } @@ -4255,7 +4259,8 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx &wtx) const { 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; @@ -4282,7 +4287,6 @@ } } - 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__, @@ -4594,7 +4598,7 @@ // Temporary. Removed in upcoming lock cleanup auto locked_chain = chain.assumeLocked(); - walletInstance->ChainStateFlushed(::ChainActive().GetLocator()); + walletInstance->ChainStateFlushed(locked_chain->getLocator()); } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { // Make it impossible to disable private keys after creation InitError(strprintf(_("Error loading %s: Private keys can only be " @@ -4677,35 +4681,43 @@ // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); - // Temporary, for FindForkInGlobalIndex below. Removed in upcoming commit. - LockAnnotation lock(::cs_main); 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 (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.hasData() && - 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) { InitError(_("Prune: last wallet synchronisation goes beyond " "pruned data. You need to -reindex (download the " "whole blockchain again in case of pruned node)")); @@ -4716,26 +4728,29 @@ uiInterface.InitMessage(_("Rescanning...")); walletInstance->WalletLogPrintf( "Rescanning last %i blocks (from block %i)...\n", - ::ChainActive().Height() - pindexRescan->nHeight, - pindexRescan->nHeight); + *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) - 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))) { + walletInstance + ->ScanForWalletTransactions( + locked_chain->getBlockHash(rescan_height), BlockHash(), + reserver, true /* update */) + .status)) { InitError( _("Failed to rescan the wallet during initialization")); return nullptr; @@ -4743,7 +4758,7 @@ } 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 @@ -4817,9 +4832,9 @@ nTimeExpires = nExpires; } -void CMerkleTx::SetMerkleBranch(const CBlockIndex *pindex, int posInBlock) { +void CMerkleTx::SetMerkleBranch(const BlockHash &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; @@ -4833,14 +4848,7 @@ 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); } int CMerkleTx::GetBlocksToMaturity(