aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am1
-rw-r--r--src/interfaces/chain.cpp142
-rw-r--r--src/interfaces/chain.h89
-rw-r--r--src/interfaces/wallet.cpp13
-rw-r--r--src/optional.h17
-rw-r--r--src/qt/test/wallettests.cpp12
-rw-r--r--src/wallet/rpcdump.cpp18
-rw-r--r--src/wallet/rpcwallet.cpp85
-rw-r--r--src/wallet/test/wallet_tests.cpp51
-rw-r--r--src/wallet/wallet.cpp240
-rw-r--r--src/wallet/wallet.h33
11 files changed, 486 insertions, 215 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 4b07f06c95..efac2b4695 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -146,6 +146,7 @@ BITCOIN_CORE_H = \
netbase.h \
netmessagemaker.h \
noui.h \
+ optional.h \
outputtype.h \
policy/feerate.h \
policy/fees.h \
diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp
index 2571a91031..38888be8a3 100644
--- a/src/interfaces/chain.cpp
+++ b/src/interfaces/chain.cpp
@@ -4,7 +4,11 @@
#include <interfaces/chain.h>
+#include <chain.h>
+#include <chainparams.h>
+#include <primitives/block.h>
#include <sync.h>
+#include <uint256.h>
#include <util/system.h>
#include <validation.h>
@@ -16,6 +20,118 @@ namespace {
class LockImpl : public Chain::Lock
{
+ Optional<int> getHeight() override
+ {
+ int height = ::chainActive.Height();
+ if (height >= 0) {
+ return height;
+ }
+ return nullopt;
+ }
+ Optional<int> 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<int> tip_height = getHeight();
+ const Optional<int> 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<int> 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<int> 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<int> findPruned(int start_height, Optional<int> 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<int> findFork(const uint256& hash, Optional<int>* 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<int> 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<CCriticalSection>
@@ -35,6 +151,32 @@ public:
return std::move(result);
}
std::unique_ptr<Chain::Lock> assumeLocked() override { return MakeUnique<LockImpl>(); }
+ 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 <optional.h>
+
#include <memory>
+#include <stdint.h>
#include <string>
#include <vector>
+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<int> 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<int> 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<int> 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<int> 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<int> findPruned(int start_height = 0, Optional<int> 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<int> findFork(const uint256& hash, Optional<int>* 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<int> 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<Lock> 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 8db34ed759..88c5db73a1 100644
--- a/src/interfaces/wallet.cpp
+++ b/src/interfaces/wallet.cpp
@@ -333,8 +333,13 @@ public:
if (mi == m_wallet.mapWallet.end()) {
return false;
}
- num_blocks = ::chainActive.Height();
- block_time = ::chainActive.Tip()->GetBlockTime();
+ if (Optional<int> 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;
}
@@ -348,7 +353,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);
@@ -379,7 +384,7 @@ public:
return false;
}
balances = getBalances();
- num_blocks = ::chainActive.Height();
+ num_blocks = locked_chain->getHeight().value_or(-1);
return true;
}
CAmount getBalance() override { return m_wallet.GetBalance(); }
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 <boost/optional.hpp>
+
+//! Substitute for C++17 std::optional
+template <typename T>
+using Optional = boost::optional<T>;
+
+//! 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 610d83acb6..ee84da0cdf 100644
--- a/src/qt/test/wallettests.cpp
+++ b/src/qt/test/wallettests.cpp
@@ -1,6 +1,7 @@
#include <qt/test/wallettests.h>
#include <qt/test/util.h>
+#include <init.h>
#include <interfaces/chain.h>
#include <interfaces/node.h>
#include <base58.h>
@@ -146,13 +147,10 @@ void TestGUI()
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), {} /* 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 41b72870c7..2e62a6979f 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -385,8 +385,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
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");
}
@@ -579,7 +578,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<int> 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);
@@ -784,8 +784,9 @@ UniValue dumpwallet(const JSONRPCRequest& request)
// produce output
file << strprintf("# Wallet dump created by Bitcoin %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<int> 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";
// add the base58check encoded extended master if the wallet uses HD
@@ -1248,15 +1249,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<int> 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 4529e39124..270a61ade8 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -102,7 +102,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));
}
@@ -1588,24 +1591,18 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
auto locked_chain = pwallet->chain().lock();
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<int> height; // Height of the specified block or the common ancestor, if the block provided was in a deactivated chain.
+ Optional<int> 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(ParseHashV(request.params[0], "blockhash"));
-
- paltindex = pindex = LookupBlockIndex(blockId);
- if (!pindex) {
+ blockId = 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()) {
@@ -1622,7 +1619,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<int> tip_height = locked_chain->getHeight();
+ int depth = tip_height && height ? (1 + *tip_height - *height) : -1;
UniValue transactions(UniValue::VARR);
@@ -1637,9 +1635,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) {
@@ -1650,11 +1648,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);
@@ -3428,50 +3427,48 @@ 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<int> 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<int> 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:
@@ -3479,8 +3476,8 @@ 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/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 0db22cf6fe..8c380f1257 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -39,7 +39,6 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
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()));
@@ -53,10 +52,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(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.GetImmatureBalance(), 0);
}
@@ -67,10 +67,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.GetImmatureBalance(), 100 * COIN);
}
@@ -85,10 +86,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.GetImmatureBalance(), 50 * COIN);
}
@@ -102,10 +104,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.GetImmatureBalance(), 0);
}
}
@@ -276,7 +279,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);
@@ -340,11 +343,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()
@@ -372,7 +375,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;
}
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 74deb2dddc..ddeff8ba09 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -935,19 +935,19 @@ 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_wallet);
- if (pIndex != nullptr) {
+ if (!block_hash.IsNull()) {
for (const CTxIn& txin : tx.vin) {
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> 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++;
}
@@ -983,8 +983,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);
}
@@ -1071,11 +1071,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
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 when loading the wallet during a reindex. Do nothing in that
@@ -1121,8 +1117,8 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
}
}
-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
@@ -1134,7 +1130,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pin
void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) {
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()) {
@@ -1162,15 +1158,15 @@ void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& 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 */);
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<const CBlock>& pblock) {
@@ -1178,7 +1174,7 @@ void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) {
LOCK(cs_wallet);
for (const CTransactionRef& ptx : pblock->vtx) {
- SyncTransaction(ptx);
+ SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
}
}
@@ -1195,9 +1191,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;
}
}
@@ -1591,132 +1586,143 @@ 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<int> start_height = locked_chain->findFirstBlockWithTime(startTime - TIMESTAMP_WINDOW, &start_block);
+ const Optional<int> 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<int> 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<int> 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 && progress_end - progress_begin > 0.0) {
+ while (block_height && !fAbortRescan && !ShutdownRequested()) {
+ if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) {
ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)((progress_current - progress_begin) / (progress_end - progress_begin) * 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<int> 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()
@@ -2573,6 +2579,7 @@ static bool IsCurrentForAntiFeeSniping(interfaces::Chain::Lock& locked_chain)
*/
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.
//
@@ -2595,7 +2602,7 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain::Lock& locked_cha
// 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,
@@ -2609,7 +2616,7 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain::Lock& locked_cha
// unique "nLockTime fingerprint", set nLockTime to a constant.
locktime = 0;
}
- assert(locktime <= (unsigned int)chainActive.Height());
+ assert(locktime <= height);
assert(locktime < LOCKTIME_THRESHOLD);
return locktime;
}
@@ -3718,11 +3725,12 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
}
// map in which we'll infer heights of other keys
- CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; // the tip can be reorganized; use a 144-block safety margin
- std::map<CKeyID, CBlockIndex*> mapKeyFirstBlock;
+ const Optional<int> 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<CKeyID, int> 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
@@ -3733,17 +3741,15 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
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<int> 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
for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *this)) {
// ... and all their affected keys
- std::map<CKeyID, CBlockIndex*>::iterator rit = mapKeyFirstBlock.find(keyid);
- if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight)
- rit->second = pindex;
+ std::map<CKeyID, int>::iterator rit = mapKeyFirstBlock.find(keyid);
+ if (rit != mapKeyFirstBlock.end() && *height < rit->second)
+ rit->second = *height;
}
}
}
@@ -3751,7 +3757,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
// Extract block timestamps for those keys
for (const auto& entry : mapKeyFirstBlock)
- mapKeyBirth[entry.first] = entry.second->GetBlockTime() - TIMESTAMP_WINDOW; // block times can be 2h off
+ mapKeyBirth[entry.first] = locked_chain.getBlockTime(entry.second) - TIMESTAMP_WINDOW; // block times can be 2h off
}
/**
@@ -3779,7 +3785,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;
@@ -3805,7 +3812,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());
@@ -4067,7 +4073,7 @@ std::shared_ptr<CWallet> 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());
} 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 disabled during creation"), walletFile));
@@ -4154,58 +4160,67 @@ std::shared_ptr<CWallet> 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<int> fork_height = locked_chain->findLocatorFork(locator)) {
+ rescan_height = *fork_height;
+ }
+ }
}
- walletInstance->m_last_block_processed = chainActive.Tip();
+ const Optional<int> 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) {
InitError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"));
return nullptr;
}
}
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)
- while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) {
- pindexRescan = chainActive.Next(pindexRescan);
+ if (walletInstance->nTimeFirstKey) {
+ if (Optional<int> 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)) {
InitError(_("Failed to rescan the wallet during initialization"));
return nullptr;
}
}
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
@@ -4282,10 +4297,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;
@@ -4298,12 +4313,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);
}
int CMerkleTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 937b727793..6f787416dd 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -95,7 +95,6 @@ static const bool DEFAULT_DISABLE_WALLET = false;
//! Pre-calculated constants for input size estimation in *virtual size*
static constexpr size_t DUMMY_NESTED_P2WPKH_INPUT_SIZE = 91;
-class CBlockIndex;
class CCoinControl;
class COutput;
class CReserveKey;
@@ -287,7 +286,7 @@ public:
READWRITE(nIndex);
}
- void SetMerkleBranch(const CBlockIndex* pIndex, int posInBlock);
+ void SetMerkleBranch(const uint256& block_hash, int posInBlock);
/**
* Return depth of transaction in blockchain:
@@ -667,7 +666,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);
@@ -678,8 +677,8 @@ private:
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) 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);
/* the HD chain data model (external chain counters) */
CHDChain hdChain;
@@ -723,10 +722,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;
public:
/*
@@ -909,12 +906,22 @@ public:
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) 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<int> 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) override;
void ReacceptWalletTransactions();
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override EXCLUSIVE_LOCKS_REQUIRED(cs_main);