Merge #14711: Remove uses of chainActive and mapBlockIndex in wallet code

44de1561a Remove remaining chainActive references from CWallet (Russell Yanofsky)
db21f0264 Convert CWallet::ScanForWalletTransactions and SyncTransaction to the new Chain apis (Russell Yanofsky)
2ffb07929 Add findFork and findBlock to the Chain interface (Russell Yanofsky)
d93c4c1d6 Add time methods to the Chain interface (Russell Yanofsky)
700c42b85 Add height, depth, and hash methods to the Chain interface (Russell Yanofsky)

Pull request description:

  This change removes uses of `chainActive` and `mapBlockIndex` globals in wallet code. It is a refactoring change which does not affect external behavior.

  This is the next step in the larger #10973 refactoring change, which removes all other accesses to node global variables from wallet code. Doing this is useful to provide a better defined interface between the wallet and node, and necessary to allow wallet and node code to run in separate processes in #10102.

Tree-SHA512: 4dcec8a31c458f54e2ea6ecf01e430469b0994c5b41a21a2d150efa67cd209f4c93ae210a101e064b3a87c52c6edfc70b070e979992be0e3a00fd425de6230a8
This commit is contained in:
MeshCollider 2019-01-30 13:02:27 +13:00 committed by Pasta
parent 2204c85370
commit 48826b429d
No known key found for this signature in database
GPG Key ID: 52527BEDABE87984
12 changed files with 525 additions and 246 deletions

View File

@ -229,6 +229,7 @@ BITCOIN_CORE_H = \
node/coinstats.h \ node/coinstats.h \
node/transaction.h \ node/transaction.h \
noui.h \ noui.h \
optional.h \
policy/feerate.h \ policy/feerate.h \
policy/fees.h \ policy/fees.h \
policy/policy.h \ policy/policy.h \

View File

@ -4,7 +4,11 @@
#include <interfaces/chain.h> #include <interfaces/chain.h>
#include <chain.h>
#include <chainparams.h>
#include <primitives/block.h>
#include <sync.h> #include <sync.h>
#include <uint256.h>
#include <util/system.h> #include <util/system.h>
#include <validation.h> #include <validation.h>
@ -16,6 +20,118 @@ namespace {
class LockImpl : public Chain::Lock 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> class LockingStateImpl : public LockImpl, public UniqueLock<CCriticalSection>
@ -35,6 +151,32 @@ public:
return std::move(result); return std::move(result);
} }
std::unique_ptr<Chain::Lock> assumeLocked() override { return MakeUnique<LockImpl>(); } 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 } // namespace

View File

@ -5,11 +5,17 @@
#ifndef BITCOIN_INTERFACES_CHAIN_H #ifndef BITCOIN_INTERFACES_CHAIN_H
#define BITCOIN_INTERFACES_CHAIN_H #define BITCOIN_INTERFACES_CHAIN_H
#include <optional.h>
#include <memory> #include <memory>
#include <stdint.h>
#include <string> #include <string>
#include <vector> #include <vector>
class CBlock;
class CScheduler; class CScheduler;
class uint256;
struct CBlockLocator;
namespace interfaces { namespace interfaces {
@ -28,6 +34,74 @@ public:
{ {
public: public:
virtual ~Lock() {} 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 //! 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 //! 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. //! behavior while code is transitioned to use the Chain::Lock interface.
virtual std::unique_ptr<Lock> assumeLocked() = 0; 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 //! Interface to let node manage chain clients (wallets, or maybe tools for

View File

@ -382,8 +382,12 @@ public:
if (mi == m_wallet->mapWallet.end()) { if (mi == m_wallet->mapWallet.end()) {
return false; return false;
} }
if (Optional<int> height = locked_chain->getHeight()) {
block_time = locked_chain->getBlockTime(*height);
} else {
block_time = -1;
}
tx_status = MakeWalletTxStatus(*locked_chain, mi->second); tx_status = MakeWalletTxStatus(*locked_chain, mi->second);
block_time = ::ChainActive().Tip()->GetBlockTime();
return true; return true;
} }
WalletTx getWalletTxDetails(const uint256& txid, WalletTx getWalletTxDetails(const uint256& txid,
@ -396,7 +400,7 @@ public:
LOCK(m_wallet->cs_wallet); LOCK(m_wallet->cs_wallet);
auto mi = m_wallet->mapWallet.find(txid); auto mi = m_wallet->mapWallet.find(txid);
if (mi != m_wallet->mapWallet.end()) { if (mi != m_wallet->mapWallet.end()) {
num_blocks = ::ChainActive().Height(); num_blocks = locked_chain->getHeight().value_or(-1);
in_mempool = mi->second.InMempool(); in_mempool = mi->second.InMempool();
order_form = mi->second.vOrderForm; order_form = mi->second.vOrderForm;
tx_status = MakeWalletTxStatus(*locked_chain, mi->second); tx_status = MakeWalletTxStatus(*locked_chain, mi->second);
@ -431,7 +435,7 @@ public:
return false; return false;
} }
balances = getBalances(); balances = getBalances();
num_blocks = ::ChainActive().Height(); num_blocks = locked_chain->getHeight().value_or(-1);
return true; return true;
} }
CAmount getBalance() override CAmount getBalance() override

17
src/optional.h Normal file
View File

@ -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

View File

@ -2,6 +2,7 @@
#include <qt/test/util.h> #include <qt/test/util.h>
#include <coinjoin/client.h> #include <coinjoin/client.h>
#include <init.h>
#include <interfaces/chain.h> #include <interfaces/chain.h>
#include <interfaces/node.h> #include <interfaces/node.h>
#include <base58.h> #include <base58.h>
@ -121,13 +122,10 @@ void TestGUI()
WalletRescanReserver reserver(wallet.get()); WalletRescanReserver reserver(wallet.get());
reserver.reserve(); reserver.reserve();
const CBlockIndex* const null_block = nullptr; CWallet::ScanResult result = wallet->ScanForWalletTransactions(locked_chain->getBlockHash(0), {} /* stop_block */, reserver, true /* fUpdate */);
const CBlockIndex *stop_block, *failed_block; QCOMPARE(result.status, CWallet::ScanResult::SUCCESS);
QCOMPARE( QCOMPARE(result.stop_block, ::ChainActive().Tip()->GetBlockHash());
wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block, true /* fUpdate */), QVERIFY(result.failed_block.IsNull());
CWallet::ScanResult::SUCCESS);
QCOMPARE(stop_block, ::ChainActive().Tip());
QCOMPARE(failed_block, null_block);
} }
wallet->SetBroadcastTransactions(true); wallet->SetBroadcastTransactions(true);

View File

@ -354,8 +354,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
auto locked_chain = pwallet->chain().lock(); auto locked_chain = pwallet->chain().lock();
LockAnnotation lock(::cs_main); LockAnnotation lock(::cs_main);
const CBlockIndex* pindex = LookupBlockIndex(merkleBlock.header.GetHash()); if (locked_chain->getBlockHeight(merkleBlock.header.GetHash()) == nullopt) {
if (!pindex || !::ChainActive().Contains(pindex)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
} }
@ -544,7 +543,8 @@ UniValue importwallet(const JSONRPCRequest& request)
if (!file.is_open()) { if (!file.is_open()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); 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()); int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
file.seekg(0, file.beg); file.seekg(0, file.beg);
@ -688,7 +688,8 @@ UniValue importelectrumwallet(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
} }
LOCK2(cs_main, pwallet->cs_wallet); auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet); EnsureWalletIsUnlocked(pwallet);
@ -777,24 +778,25 @@ UniValue importelectrumwallet(const JSONRPCRequest& request)
file.close(); file.close();
pwallet->ShowProgress("", 100); // hide progress dialog in GUI pwallet->ShowProgress("", 100); // hide progress dialog in GUI
const uint32_t tip_height = locked_chain->getHeight().value_or(-1);
// Whether to perform rescan after import // Whether to perform rescan after import
int nStartHeight = 0; int nStartHeight = 0;
if (!request.params[1].isNull()) if (!request.params[1].isNull())
nStartHeight = request.params[1].get_int(); nStartHeight = request.params[1].get_int();
if (::ChainActive().Height() < nStartHeight) if (tip_height < nStartHeight)
nStartHeight = ::ChainActive().Height(); nStartHeight = tip_height;
// Assume that electrum wallet was created at that block // Assume that electrum wallet was created at that block
int nTimeBegin = ::ChainActive()[nStartHeight]->GetBlockTime(); int nTimeBegin = locked_chain->getBlockTime(nStartHeight);
pwallet->UpdateTimeFirstKey(nTimeBegin); pwallet->UpdateTimeFirstKey(nTimeBegin);
pwallet->WalletLogPrintf("Rescanning %i blocks\n", ::ChainActive().Height() - nStartHeight + 1); pwallet->WalletLogPrintf("Rescanning %i blocks\n", tip_height - nStartHeight + 1);
WalletRescanReserver reserver(pwallet); WalletRescanReserver reserver(pwallet);
if (!reserver.reserve()) { if (!reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
} }
const CBlockIndex *stop_block, *failed_block; pwallet->ScanForWalletTransactions(locked_chain->getBlockHash(nStartHeight), {}, reserver, true);
pwallet->ScanForWalletTransactions(::ChainActive()[nStartHeight], nullptr, reserver, failed_block, stop_block, true);
if (!fGood) if (!fGood)
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet");
@ -968,15 +970,16 @@ UniValue dumpwallet(const JSONRPCRequest& request)
// produce output // produce output
file << strprintf("# Wallet dump created by Dash Core %s\n", CLIENT_BUILD); file << strprintf("# Wallet dump created by Dash Core %s\n", CLIENT_BUILD);
file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime())); 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()); const Optional<int> tip_height = locked_chain->getHeight();
file << strprintf("# mined on %s\n", FormatISO8601DateTime(::ChainActive().Tip()->GetBlockTime())); 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"; file << "\n";
UniValue obj(UniValue::VOBJ); UniValue obj(UniValue::VOBJ);
obj.pushKV("dashcoreversion", CLIENT_BUILD); obj.pushKV("dashcoreversion", CLIENT_BUILD);
obj.pushKV("lastblockheight", ::ChainActive().Height()); obj.pushKV("lastblockheight", tip_height.value_or(-1));
obj.pushKV("lastblockhash", ::ChainActive().Tip()->GetBlockHash().ToString()); obj.pushKV("lastblockhash", tip_height ? locked_chain->getBlockHash(*tip_height).ToString() : NullUniValue);
obj.pushKV("lastblocktime", FormatISO8601DateTime(::ChainActive().Tip()->GetBlockTime())); obj.pushKV("lastblocktime", tip_height ? FormatISO8601DateTime(locked_chain->getBlockTime(*tip_height)) : NullUniValue);
// add the base58check encoded extended master if the wallet uses HD // add the base58check encoded extended master if the wallet uses HD
CHDChain hdChainCurrent; CHDChain hdChainCurrent;
@ -1552,15 +1555,16 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
EnsureWalletIsUnlocked(pwallet); EnsureWalletIsUnlocked(pwallet);
// Verify all timestamps are present before importing any keys. // 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()) { for (const UniValue& data : requests.getValues()) {
GetImportTimestamp(data, now); GetImportTimestamp(data, now);
} }
const int64_t minimumTimestamp = 1; const int64_t minimumTimestamp = 1;
if (fRescan && ::ChainActive().Tip()) { if (fRescan && tip_height) {
nLowestTimestamp = ::ChainActive().Tip()->GetBlockTime(); nLowestTimestamp = locked_chain->getBlockTime(*tip_height);
} else { } else {
fRescan = false; fRescan = false;
} }

View File

@ -119,7 +119,10 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo
{ {
entry.pushKV("blockhash", wtx.hashBlock.GetHex()); entry.pushKV("blockhash", wtx.hashBlock.GetHex());
entry.pushKV("blockindex", wtx.nIndex); 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 { } else {
entry.pushKV("trusted", wtx.IsTrusted(locked_chain)); entry.pushKV("trusted", wtx.IsTrusted(locked_chain));
} }
@ -1634,26 +1637,19 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
LockAnnotation lock(::cs_main); LockAnnotation lock(::cs_main);
LOCK(pwallet->cs_wallet); 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. Optional<int> height; // Height 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> altheight; // Height of the specified block, even if it's in a deactivated chain.
int target_confirms = 1; int target_confirms = 1;
isminefilter filter = ISMINE_SPENDABLE; isminefilter filter = ISMINE_SPENDABLE;
uint256 blockId;
if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
uint256 blockId;
blockId.SetHex(request.params[0].get_str()); blockId.SetHex(request.params[0].get_str());
paltindex = pindex = LookupBlockIndex(blockId); height = locked_chain->findFork(blockId, &altheight);
if (!pindex) { if (!height) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); 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()) { if (!request.params[1].isNull()) {
@ -1670,7 +1666,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
bool include_removed = (request.params[3].isNull() || request.params[3].get_bool()); 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); UniValue transactions(UniValue::VARR);
@ -1685,9 +1682,9 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
// when a reorg'd block is requested, we also list any relevant transactions // when a reorg'd block is requested, we also list any relevant transactions
// in the blocks of the chain that was detached // in the blocks of the chain that was detached
UniValue removed(UniValue::VARR); UniValue removed(UniValue::VARR);
while (include_removed && paltindex && paltindex != pindex) { while (include_removed && altheight && *altheight > *height) {
CBlock block; 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"); throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
} }
for (const CTransactionRef& tx : block.vtx) { for (const CTransactionRef& tx : block.vtx) {
@ -1698,11 +1695,12 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
ListTransactions(*locked_chain, pwallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */); 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]; int last_height = tip_height ? *tip_height + 1 - target_confirms : -1;
uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : uint256(); uint256 lastblock = last_height >= 0 ? locked_chain->getBlockHash(last_height) : uint256();
UniValue ret(UniValue::VOBJ); UniValue ret(UniValue::VOBJ);
ret.pushKV("transactions", transactions); ret.pushKV("transactions", transactions);
@ -2701,7 +2699,8 @@ static UniValue upgradetohd(const JSONRPCRequest& request)
+ HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\"") + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\"")
+ HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\" \"walletpassphrase\"")); + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\" \"walletpassphrase\""));
LOCK2(cs_main, pwallet->cs_wallet); auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
// Do not do anything to HD wallets // Do not do anything to HD wallets
if (pwallet->IsHDEnabled()) { if (pwallet->IsHDEnabled()) {
@ -2772,8 +2771,7 @@ static UniValue upgradetohd(const JSONRPCRequest& request)
if (!reserver.reserve()) { if (!reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
} }
const CBlockIndex *stop_block, *failed_block; pwallet->ScanForWalletTransactions(locked_chain->getBlockHash(0), {}, reserver, true);
pwallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block, true);
} }
return true; return true;
@ -3579,50 +3577,48 @@ static UniValue rescanblockchain(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
} }
CBlockIndex *pindexStart = nullptr; int start_height = 0;
CBlockIndex *pindexStop = nullptr; uint256 start_block, stop_block;
CBlockIndex *pChainTip = nullptr;
{ {
auto locked_chain = pwallet->chain().lock(); auto locked_chain = pwallet->chain().lock();
pindexStart = ::ChainActive().Genesis(); Optional<int> tip_height = locked_chain->getHeight();
pChainTip = ::ChainActive().Tip();
if (!request.params[0].isNull()) { if (!request.params[0].isNull()) {
pindexStart = ::ChainActive()[request.params[0].get_int()]; start_height = request.params[0].get_int();
if (!pindexStart) { if (start_height < 0 || !tip_height || start_height > *tip_height) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
} }
} }
Optional<int> stop_height;
if (!request.params[1].isNull()) { if (!request.params[1].isNull()) {
pindexStop = ::ChainActive()[request.params[1].get_int()]; stop_height = request.params[1].get_int();
if (!pindexStop) { if (*stop_height < 0 || !tip_height || *stop_height > *tip_height) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_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"); 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 // We can't rescan beyond non-pruned blocks, stop and throw an error
if (fPruneMode) { if (locked_chain->findPruned(start_height, stop_height)) {
auto locked_chain = pwallet->chain().lock(); throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
CBlockIndex *block = pindexStop ? pindexStop : pChainTip; }
while (block && block->nHeight >= pindexStart->nHeight) {
if (!(block->nStatus & BLOCK_HAVE_DATA)) { if (tip_height) {
throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned 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 = CWallet::ScanResult result =
pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, failed_block, stopBlock, true); pwallet->ScanForWalletTransactions(start_block, stop_block, reserver, true /* fUpdate */);
switch (result) { switch (result.status) {
case CWallet::ScanResult::SUCCESS: case CWallet::ScanResult::SUCCESS:
break; // stopBlock set by ScanForWalletTransactions break;
case CWallet::ScanResult::FAILURE: case CWallet::ScanResult::FAILURE:
throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files."); throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
case CWallet::ScanResult::USER_ABORT: case CWallet::ScanResult::USER_ABORT:
@ -3630,8 +3626,8 @@ static UniValue rescanblockchain(const JSONRPCRequest& request)
// no default case, so the compiler can warn about missing cases // no default case, so the compiler can warn about missing cases
} }
UniValue response(UniValue::VOBJ); UniValue response(UniValue::VOBJ);
response.pushKV("start_height", pindexStart->nHeight); response.pushKV("start_height", start_height);
response.pushKV("stop_height", stopBlock->nHeight); response.pushKV("stop_height", result.stop_height ? *result.stop_height : UniValue());
return response; return response;
} }

View File

@ -47,8 +47,9 @@ public:
} }
WalletRescanReserver reserver(wallet.get()); WalletRescanReserver reserver(wallet.get());
reserver.reserve(); reserver.reserve();
const CBlockIndex *stop_block, *failed_block;
wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block); CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), {} /* stop_block */, reserver, true /* fUpdate */);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
} }
~CTransactionBuilderTestSetup() ~CTransactionBuilderTestSetup()
@ -70,8 +71,9 @@ public:
blocktx = CMutableTransaction(*it->second.tx); blocktx = CMutableTransaction(*it->second.tx);
} }
CreateAndProcessBlock({blocktx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CreateAndProcessBlock({blocktx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
LOCK2(cs_main, wallet->cs_wallet); auto locked_chain = wallet->chain().lock();
it->second.SetMerkleBranch(::ChainActive().Tip(), 1); LOCK(wallet->cs_wallet);
it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1);
return it->second; return it->second;
} }
CompactTallyItem GetTallyItem(const std::vector<CAmount>& vecAmounts) CompactTallyItem GetTallyItem(const std::vector<CAmount>& vecAmounts)

View File

@ -39,7 +39,6 @@ static void AddKey(CWallet& wallet, const CKey& key)
BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
{ {
// Cap last block file size, and mine new block in a new block file. // Cap last block file size, and mine new block in a new block file.
const CBlockIndex* const null_block = nullptr;
CBlockIndex* oldTip = ::ChainActive().Tip(); CBlockIndex* oldTip = ::ChainActive().Tip();
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
@ -49,16 +48,17 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
auto locked_chain = chain->lock(); auto locked_chain = chain->lock();
LockAnnotation lock(::cs_main); // for PruneOneBlockFile LockAnnotation lock(::cs_main); // for PruneOneBlockFile
// Verify ScanForWalletTransactions accomodates a null start block. // Verify ScanForWalletTransactions accommodates a null start block.
{ {
CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy());
AddKey(wallet, coinbaseKey); AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet); WalletRescanReserver reserver(&wallet);
reserver.reserve(); reserver.reserve();
const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; CWallet::ScanResult result = wallet.ScanForWalletTransactions({} /* start_block */, {} /* stop_block */, reserver, false /* update */);
BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(nullptr, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
BOOST_CHECK_EQUAL(failed_block, null_block); BOOST_CHECK(result.failed_block.IsNull());
BOOST_CHECK_EQUAL(stop_block, null_block); BOOST_CHECK(result.stop_block.IsNull());
BOOST_CHECK(!result.stop_height);
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0);
} }
@ -69,10 +69,11 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
AddKey(wallet, coinbaseKey); AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet); WalletRescanReserver reserver(&wallet);
reserver.reserve(); reserver.reserve();
const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */);
BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
BOOST_CHECK_EQUAL(failed_block, null_block); BOOST_CHECK(result.failed_block.IsNull());
BOOST_CHECK_EQUAL(stop_block, newTip); BOOST_CHECK_EQUAL(result.stop_block, newTip->GetBlockHash());
BOOST_CHECK_EQUAL(*result.stop_height, newTip->nHeight);
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 1000 * COIN); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 1000 * COIN);
} }
@ -87,10 +88,11 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
AddKey(wallet, coinbaseKey); AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet); WalletRescanReserver reserver(&wallet);
reserver.reserve(); reserver.reserve();
const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */);
BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
BOOST_CHECK_EQUAL(failed_block, oldTip); BOOST_CHECK_EQUAL(result.failed_block, oldTip->GetBlockHash());
BOOST_CHECK_EQUAL(stop_block, newTip); BOOST_CHECK_EQUAL(result.stop_block, newTip->GetBlockHash());
BOOST_CHECK_EQUAL(*result.stop_height, newTip->nHeight);
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 500 * COIN); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 500 * COIN);
} }
@ -104,10 +106,11 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
AddKey(wallet, coinbaseKey); AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet); WalletRescanReserver reserver(&wallet);
reserver.reserve(); reserver.reserve();
const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */);
BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
BOOST_CHECK_EQUAL(failed_block, newTip); BOOST_CHECK_EQUAL(result.failed_block, newTip->GetBlockHash());
BOOST_CHECK_EQUAL(stop_block, null_block); BOOST_CHECK(result.stop_block.IsNull());
BOOST_CHECK(!result.stop_height);
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0);
} }
} }
@ -122,8 +125,8 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
CBlockIndex* newTip = ::ChainActive().Tip(); CBlockIndex* newTip = ::ChainActive().Tip();
LockAnnotation lock(::cs_main);
auto locked_chain = chain->lock(); auto locked_chain = chain->lock();
LockAnnotation lock(::cs_main);
// Prune the older block file. // Prune the older block file.
PruneOneBlockFile(oldTip->GetBlockPos().nFile); PruneOneBlockFile(oldTip->GetBlockPos().nFile);
@ -284,7 +287,7 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
CWalletTx wtx(&wallet, MakeTransactionRef(tx)); CWalletTx wtx(&wallet, MakeTransactionRef(tx));
if (block) { if (block) {
wtx.SetMerkleBranch(block, 0); wtx.SetMerkleBranch(block->GetBlockHash(), 0);
} }
{ {
LOCK(cs_main); LOCK(cs_main);
@ -348,11 +351,11 @@ public:
AddKey(*wallet, coinbaseKey); AddKey(*wallet, coinbaseKey);
WalletRescanReserver reserver(wallet.get()); WalletRescanReserver reserver(wallet.get());
reserver.reserve(); reserver.reserve();
const CBlockIndex* const null_block = nullptr; CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), {} /* stop_block */, reserver, false /* update */);
const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
BOOST_CHECK_EQUAL(wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(result.stop_block, ::ChainActive().Tip()->GetBlockHash());
BOOST_CHECK_EQUAL(stop_block, ::ChainActive().Tip()); BOOST_CHECK_EQUAL(*result.stop_height, ::ChainActive().Height());
BOOST_CHECK_EQUAL(failed_block, null_block); BOOST_CHECK(result.failed_block.IsNull());
} }
~ListCoinsTestingSetup() ~ListCoinsTestingSetup()
@ -382,7 +385,7 @@ public:
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
auto it = wallet->mapWallet.find(tx->GetHash()); auto it = wallet->mapWallet.find(tx->GetHash());
BOOST_CHECK(it != wallet->mapWallet.end()); BOOST_CHECK(it != wallet->mapWallet.end());
it->second.SetMerkleBranch(::ChainActive().Tip(), 1); it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1);
return it->second; return it->second;
} }
@ -485,8 +488,8 @@ public:
AddKey(*wallet, coinbaseKey); AddKey(*wallet, coinbaseKey);
WalletRescanReserver reserver(wallet.get()); WalletRescanReserver reserver(wallet.get());
reserver.reserve(); reserver.reserve();
const CBlockIndex *stop_block, *failed_block; CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash() /* start_block */, {} /* stop_block */, reserver, false /* update */);
wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
} }
std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain();
@ -588,7 +591,7 @@ public:
LOCK2(cs_main, wallet->cs_wallet); LOCK2(cs_main, wallet->cs_wallet);
auto it = wallet->mapWallet.find(tx->GetHash()); auto it = wallet->mapWallet.find(tx->GetHash());
BOOST_CHECK(it != wallet->mapWallet.end()); BOOST_CHECK(it != wallet->mapWallet.end());
it->second.SetMerkleBranch(::ChainActive().Tip(), 1); it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1);
std::vector<COutPoint> vecOutpoints; std::vector<COutPoint> vecOutpoints;
size_t n; size_t n;
@ -932,8 +935,8 @@ BOOST_FIXTURE_TEST_CASE(select_coins_grouped_by_addresses, ListCoinsTestingSetup
// Reveal the mined tx, it should conflict with the one we have in the wallet already. // Reveal the mined tx, it should conflict with the one we have in the wallet already.
WalletRescanReserver reserver(wallet.get()); WalletRescanReserver reserver(wallet.get());
reserver.reserve(); reserver.reserve();
const CBlockIndex *stop_block, *failed_block; auto result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), {}, reserver, false);
BOOST_CHECK_EQUAL(wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
const auto& conflicts = wallet->GetConflicts(tx2->GetHash()); const auto& conflicts = wallet->GetConflicts(tx2->GetHash());

View File

@ -1185,20 +1185,20 @@ void CWallet::LoadToWallet(const CWalletTx& wtxIn)
} }
} }
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate) bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool fUpdate)
{ {
const CTransaction& tx = *ptx; const CTransaction& tx = *ptx;
{ {
AssertLockHeld(cs_main); // because of AddToWallet AssertLockHeld(cs_main); // because of AddToWallet
AssertLockHeld(cs_wallet); AssertLockHeld(cs_wallet);
if (pIndex != nullptr) { if (!block_hash.IsNull()) {
for (const CTxIn& txin : tx.vin) { for (const CTxIn& txin : tx.vin) {
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout); std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout);
while (range.first != range.second) { while (range.first != range.second) {
if (range.first->second != tx.GetHash()) { 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); 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(pIndex->GetBlockHash(), range.first->second); MarkConflicted(block_hash, range.first->second);
} }
range.first++; range.first++;
} }
@ -1231,14 +1231,19 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI
WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__); WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__);
} }
} }
if (pIndex != nullptr && mapKeyMetadata[keyid].nCreateTime > pIndex->nTime) { if (!block_hash.IsNull()) {
WalletLogPrintf("%s: Found a key which appears to be used earlier than we expected, updating metadata\n", __func__); int64_t block_time;
CPubKey vchPubKey; bool found_block = chain().findBlock(block_hash, nullptr /* block */, &block_time);
bool res = GetPubKey(keyid, vchPubKey); assert(found_block);
assert(res); // this should never fail if (mapKeyMetadata[keyid].nCreateTime > block_time) {
mapKeyMetadata[keyid].nCreateTime = pIndex->nTime; WalletLogPrintf("%s: Found a key which appears to be used earlier than we expected, updating metadata\n", __func__);
batch.WriteKeyMeta(vchPubKey, mapKeyMetadata[keyid]); CPubKey vchPubKey;
UpdateTimeFirstKey(pIndex->nTime); bool res = GetPubKey(keyid, vchPubKey);
assert(res); // this should never fail
mapKeyMetadata[keyid].nCreateTime = block_time;
batch.WriteKeyMeta(vchPubKey, mapKeyMetadata[keyid]);
UpdateTimeFirstKey(block_time);
}
} }
} }
} }
@ -1246,8 +1251,8 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI
CWalletTx wtx(this, ptx); CWalletTx wtx(this, ptx);
// Get merkle branch if transaction was found in a block // Get merkle branch if transaction was found in a block
if (pIndex != nullptr) if (!block_hash.IsNull())
wtx.SetMerkleBranch(pIndex, posInBlock); wtx.SetMerkleBranch(block_hash, posInBlock);
return AddToWallet(wtx, false); return AddToWallet(wtx, false);
} }
@ -1338,11 +1343,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
LockAnnotation lock(::cs_main); LockAnnotation lock(::cs_main);
LOCK(cs_wallet); // check WalletBatch::LoadWallet() LOCK(cs_wallet); // check WalletBatch::LoadWallet()
int conflictconfirms = 0; int conflictconfirms = -locked_chain->getBlockDepth(hashBlock);
CBlockIndex* pindex = LookupBlockIndex(hashBlock);
if (pindex && ::ChainActive().Contains(pindex)) {
conflictconfirms = -(::ChainActive().Height() - pindex->nHeight + 1);
}
// If number of conflict confirms cannot be determined, this means // If number of conflict confirms cannot be determined, this means
// that the block is still unknown or not yet part of the main chain, // 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 // for example when loading the wallet during a reindex. Do nothing in that
@ -1391,8 +1392,8 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
fAnonymizableTallyCachedNonDenom = false; fAnonymizableTallyCachedNonDenom = false;
} }
void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pindex, int posInBlock, bool update_tx) { void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool update_tx) {
if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, update_tx)) if (!AddToWalletIfInvolvingMe(ptx, block_hash, posInBlock, update_tx))
return; // Not one of ours return; // Not one of ours
// If a transaction changes 'conflicted' state, that changes the balance // If a transaction changes 'conflicted' state, that changes the balance
@ -1407,7 +1408,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pin
void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx, int64_t nAcceptTime) { void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx, int64_t nAcceptTime) {
auto locked_chain = chain().lock(); auto locked_chain = chain().lock();
LOCK(cs_wallet); LOCK(cs_wallet);
SyncTransaction(ptx); SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
auto it = mapWallet.find(ptx->GetHash()); auto it = mapWallet.find(ptx->GetHash());
if (it != mapWallet.end()) { if (it != mapWallet.end()) {
@ -1437,17 +1438,17 @@ void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const
// the notification that the conflicted transaction was evicted. // the notification that the conflicted transaction was evicted.
for (const CTransactionRef& ptx : vtxConflicted) { for (const CTransactionRef& ptx : vtxConflicted) {
SyncTransaction(ptx); SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
// UNKNOWN because it's a manual removal, not using mempool logic // UNKNOWN because it's a manual removal, not using mempool logic
TransactionRemovedFromMempool(ptx, MemPoolRemovalReason::UNKNOWN); TransactionRemovedFromMempool(ptx, MemPoolRemovalReason::UNKNOWN);
} }
for (size_t i = 0; i < pblock->vtx.size(); i++) { for (size_t i = 0; i < pblock->vtx.size(); i++) {
SyncTransaction(pblock->vtx[i], pindex, i); SyncTransaction(pblock->vtx[i], pindex->GetBlockHash(), i);
// UNKNOWN because it's a manual removal, not using mempool logic // UNKNOWN because it's a manual removal, not using mempool logic
TransactionRemovedFromMempool(pblock->vtx[i], MemPoolRemovalReason::UNKNOWN); TransactionRemovedFromMempool(pblock->vtx[i], MemPoolRemovalReason::UNKNOWN);
} }
m_last_block_processed = pindex; m_last_block_processed = pindex->GetBlockHash();
// The GUI expects a NotifyTransactionChanged when a coinbase tx // The GUI expects a NotifyTransactionChanged when a coinbase tx
// which is in our wallet moves from in-the-best-block to // which is in our wallet moves from in-the-best-block to
@ -1487,7 +1488,7 @@ void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, con
for (const CTransactionRef& ptx : pblock->vtx) { for (const CTransactionRef& ptx : pblock->vtx) {
// NOTE: do NOT pass pindex here // NOTE: do NOT pass pindex here
SyncTransaction(ptx); SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
} }
// reset cache to make sure no longer mature coins are excluded // reset cache to make sure no longer mature coins are excluded
@ -1508,9 +1509,8 @@ void CWallet::BlockUntilSyncedToCurrentChain() {
// protected by cs_wallet instead of cs_main, but as long as we need // 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. // cs_main here anyway, it's easier to just call it cs_main-protected.
auto locked_chain = chain().lock(); 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; return;
} }
} }
@ -2132,133 +2132,144 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
// Find starting block. May be null if nCreateTime is greater than the // Find starting block. May be null if nCreateTime is greater than the
// highest blockchain timestamp, in which case there is nothing that needs // highest blockchain timestamp, in which case there is nothing that needs
// to be scanned. // to be scanned.
CBlockIndex* startBlock = nullptr; uint256 start_block;
{ {
auto locked_chain = chain().lock(); auto locked_chain = chain().lock();
startBlock = ::ChainActive().FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); const Optional<int> start_height = locked_chain->findFirstBlockWithTime(startTime - TIMESTAMP_WINDOW, &start_block);
WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? ::ChainActive().Height() - startBlock->nHeight + 1 : 0); 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) { if (!start_block.IsNull()) {
const CBlockIndex *failedBlock, *stop_block;
// TODO: this should take into account failure by ScanResult::USER_ABORT // TODO: this should take into account failure by ScanResult::USER_ABORT
if (ScanResult::FAILURE == ScanForWalletTransactions(startBlock, nullptr, reserver, failedBlock, stop_block, update)) { ScanResult result = ScanForWalletTransactions(start_block, {} /* stop_block */, reserver, update);
return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1; 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; 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 * from or to us. If fUpdate is true, found transactions that already
* exist in the wallet will be updated. * exist in the wallet will be updated.
* *
* @param[in] pindexStop if not a nullptr, the scan will stop at this block-index * @param[in] start_block if not null, the scan will start at this block instead
* @param[out] failed_block if FAILURE is returned, the most recent block * of the genesis block
* that could not be scanned, otherwise nullptr * @param[in] stop_block if not null, the scan will stop at this block instead
* @param[out] stop_block the most recent block that could be scanned, * of the chain tip
* otherwise nullptr if no block could be scanned
* *
* @return ScanResult indicating success or failure of the scan. SUCCESS if * @return ScanResult indicating success or failure of the scan. SUCCESS if
* scan was successful. FAILURE if a complete rescan was not possible (due to * 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 * pruning or corruption). USER_ABORT if the rescan was aborted before it
* could complete. * 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 * the main chain after to the addition of any new keys you want to detect
* transactions for. * 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(); int64_t nNow = GetTime();
const CChainParams& chainParams = Params();
assert(reserver.isReserved()); assert(reserver.isReserved());
if (pindexStop) {
assert(pindexStop->nHeight >= pindexStart->nHeight);
}
const CBlockIndex* pindex = pindexStart; uint256 block_hash = start_block;
failed_block = nullptr; ScanResult result;
stop_block = nullptr;
if (pindex) WalletLogPrintf("Rescan started from block %d...\n", pindex->nHeight); WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString());
{ {
fAbortRescan = false; fAbortRescan = false;
ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup 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_begin;
double progress_end; double progress_end;
{ {
auto locked_chain = chain().lock(); auto locked_chain = chain().lock();
progress_begin = GuessVerificationProgress(chainParams.TxData(), pindex); if (Optional<int> tip_height = locked_chain->getHeight()) {
if (pindexStop == nullptr) { tip_hash = locked_chain->getBlockHash(*tip_height);
tip = ::ChainActive().Tip();
progress_end = GuessVerificationProgress(chainParams.TxData(), tip);
} else {
progress_end = GuessVerificationProgress(chainParams.TxData(), pindexStop);
} }
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; double progress_current = progress_begin;
while (pindex && !fAbortRescan && !ShutdownRequested()) { while (block_height && !fAbortRescan && !ShutdownRequested()) {
m_scanning_progress = (progress_current - progress_begin) / (progress_end - progress_begin); m_scanning_progress = (progress_current - progress_begin) / (progress_end - progress_begin);
if (pindex->nHeight % 100 == 0 && progress_end - progress_begin > 0.0) { if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) {
ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100)))); ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100))));
} }
if (GetTime() >= nNow + 60) { if (GetTime() >= nNow + 60) {
nNow = GetTime(); 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; CBlock block;
if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) { if (chain().findBlock(block_hash, &block) && !block.IsNull()) {
auto locked_chain = chain().lock(); auto locked_chain = chain().lock();
LOCK(cs_wallet); 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 // Abort scan if current block is no longer active, to prevent
// marking transactions as coming from the wrong block. // 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; break;
} }
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { 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 // scan succeeded, record block as most recent successfully scanned
stop_block = pindex; result.stop_block = block_hash;
result.stop_height = *block_height;
} else { } else {
// could not scan block, keep scanning but record this block as the most recent failure // 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; break;
} }
{ {
auto locked_chain = chain().lock(); auto locked_chain = chain().lock();
pindex = ::ChainActive().Next(pindex); Optional<int> tip_height = locked_chain->getHeight();
progress_current = GuessVerificationProgress(chainParams.TxData(), pindex); if (!tip_height || *tip_height <= block_height || !locked_chain->getBlockHeight(block_hash)) {
if (pindexStop == nullptr && tip != ::ChainActive().Tip()) { // break successfully when rescan has reached the tip, or
tip = ::ChainActive().Tip(); // 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 // 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 ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 100); // hide progress dialog in GUI
if (pindex && fAbortRescan) { if (block_height && fAbortRescan) {
WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, progress_current); WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", block_height.value_or(0), progress_current);
return ScanResult::USER_ABORT; result.status = ScanResult::USER_ABORT;
} else if (pindex && ShutdownRequested()) { } else if (block_height && ShutdownRequested()) {
WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", pindex->nHeight, progress_current); WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", block_height.value_or(0), progress_current);
return ScanResult::USER_ABORT; result.status = ScanResult::USER_ABORT;
} }
} }
if (failed_block) { return result;
return ScanResult::FAILURE;
} else {
return ScanResult::SUCCESS;
}
} }
void CWallet::ReacceptWalletTransactions() void CWallet::ReacceptWalletTransactions()
@ -3474,6 +3485,8 @@ bool CWallet::GetBudgetSystemCollateralTX(interfaces::Chain::Lock& locked_chain,
bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet,
int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign, int nExtraPayloadSize) int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign, int nExtraPayloadSize)
{ {
uint32_t const height = locked_chain.getHeight().value_or(-1);
CAmount nValue = 0; CAmount nValue = 0;
int nChangePosRequest = nChangePosInOut; int nChangePosRequest = nChangePosInOut;
unsigned int nSubtractFeeFromAmount = 0; unsigned int nSubtractFeeFromAmount = 0;
@ -3518,7 +3531,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
// now we ensure code won't be written that makes assumptions about // now we ensure code won't be written that makes assumptions about
// nLockTime that preclude a fix later. // nLockTime that preclude a fix later.
txNew.nLockTime = ::ChainActive().Height(); txNew.nLockTime = height;
// Secondly occasionally randomly pick a nLockTime even further back, so // Secondly occasionally randomly pick a nLockTime even further back, so
// that transactions that are delayed after signing for whatever reason, // that transactions that are delayed after signing for whatever reason,
@ -3527,7 +3540,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
if (GetRandInt(10) == 0) if (GetRandInt(10) == 0)
txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100)); txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100));
assert(txNew.nLockTime <= (unsigned int)::ChainActive().Height()); assert(txNew.nLockTime <= height);
assert(txNew.nLockTime < LOCKTIME_THRESHOLD); assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
FeeCalculation feeCalc; FeeCalculation feeCalc;
CFeeRate discard_rate = coin_control.m_discard_feerate ? *coin_control.m_discard_feerate : GetDiscardRate(*this, ::feeEstimator); CFeeRate discard_rate = coin_control.m_discard_feerate ? *coin_control.m_discard_feerate : GetDiscardRate(*this, ::feeEstimator);
@ -4673,11 +4686,12 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
} }
// map in which we'll infer heights of other keys // 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 const Optional<int> tip_height = locked_chain.getHeight();
std::map<CKeyID, CBlockIndex*> mapKeyFirstBlock; 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()) { for (const CKeyID &keyid : GetKeys()) {
if (mapKeyBirth.count(keyid) == 0) if (mapKeyBirth.count(keyid) == 0)
mapKeyFirstBlock[keyid] = pindexMax; mapKeyFirstBlock[keyid] = max_height;
} }
// if there are no such keys, we're done // if there are no such keys, we're done
@ -4689,18 +4703,16 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
for (const auto& entry : mapWallet) { for (const auto& entry : mapWallet) {
// iterate over all wallet transactions... // iterate over all wallet transactions...
const CWalletTx &wtx = entry.second; const CWalletTx &wtx = entry.second;
CBlockIndex* pindex = LookupBlockIndex(wtx.hashBlock); if (Optional<int> height = locked_chain.getBlockHeight(wtx.hashBlock)) {
if (pindex && ::ChainActive().Contains(pindex)) {
// ... which are already in a block // ... which are already in a block
int nHeight = pindex->nHeight;
for (const CTxOut &txout : wtx.tx->vout) { for (const CTxOut &txout : wtx.tx->vout) {
// iterate over all their outputs // iterate over all their outputs
CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey); CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey);
for (const CKeyID &keyid : vAffected) { for (const CKeyID &keyid : vAffected) {
// ... and all their affected keys // ... and all their affected keys
std::map<CKeyID, CBlockIndex*>::iterator rit = mapKeyFirstBlock.find(keyid); std::map<CKeyID, int>::iterator rit = mapKeyFirstBlock.find(keyid);
if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight) if (rit != mapKeyFirstBlock.end() && *height < rit->second)
rit->second = pindex; rit->second = *height;
} }
vAffected.clear(); vAffected.clear();
} }
@ -4709,7 +4721,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
// Extract block timestamps for those keys // Extract block timestamps for those keys
for (const auto& entry : mapKeyFirstBlock) 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
} }
/** /**
@ -4738,7 +4750,8 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
AssertLockHeld(::cs_main); AssertLockHeld(::cs_main);
unsigned int nTimeSmart = wtx.nTimeReceived; unsigned int nTimeSmart = wtx.nTimeReceived;
if (!wtx.hashUnset()) { 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 latestNow = wtx.nTimeReceived;
int64_t latestEntry = 0; int64_t latestEntry = 0;
@ -4764,7 +4777,6 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
} }
} }
int64_t blocktime = pindex->GetBlockTime();
nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
} else { } else {
WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString()); WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString());
@ -5007,7 +5019,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
} }
auto locked_chain = chain.assumeLocked(); // Temporary. Removed in upcoming lock cleanup auto locked_chain = chain.assumeLocked(); // Temporary. Removed in upcoming lock cleanup
walletInstance->ChainStateFlushed(::ChainActive().GetLocator()); walletInstance->ChainStateFlushed(locked_chain->getLocator());
// Try to create wallet backup right after new wallet was created // Try to create wallet backup right after new wallet was created
std::string strBackupWarning; std::string strBackupWarning;
@ -5113,59 +5125,68 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
// Try to top up keypool. No-op if the wallet is locked. // Try to top up keypool. No-op if the wallet is locked.
walletInstance->TopUpKeyPool(); walletInstance->TopUpKeyPool();
LockAnnotation lock(::cs_main); // Temporary, for FindForkInGlobalIndex below. Removed in upcoming commit.
auto locked_chain = chain.lock(); auto locked_chain = chain.lock();
LOCK(walletInstance->cs_wallet); LOCK(walletInstance->cs_wallet);
CBlockIndex *pindexRescan = ::ChainActive().Genesis(); int rescan_height = 0;
if (!gArgs.GetBoolArg("-rescan", false)) if (!gArgs.GetBoolArg("-rescan", false))
{ {
WalletBatch batch(*walletInstance->database); WalletBatch batch(*walletInstance->database);
CBlockLocator locator; CBlockLocator locator;
if (batch.ReadBestBlock(locator)) if (batch.ReadBestBlock(locator)) {
pindexRescan = FindForkInGlobalIndex(::ChainActive(), 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 //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 //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 // or if he ran -disablewallet for a longer time, then decided to re-enable
if (fPruneMode) if (fPruneMode)
{ {
CBlockIndex *block = ::ChainActive().Tip(); int block_height = *tip_height;
while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA) && block->pprev->nTx > 0 && pindexRescan != block) while (block_height > 0 && locked_chain->haveBlockOnDisk(block_height - 1) && rescan_height != block_height) {
block = block->pprev; --block_height;
}
if (pindexRescan != block) { if (rescan_height != block_height) {
return error(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)")); return error(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"));
} }
} }
uiInterface.InitMessage(_("Rescanning...")); 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 // No need to read and scan block if block was created before
// our wallet birthday (as adjusted for block time variability) // our wallet birthday (as adjusted for block time variability)
// unless a full rescan was requested // unless a full rescan was requested
if (gArgs.GetArg("-rescan", 0) != 2) { if (gArgs.GetArg("-rescan", 0) != 2) {
while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) { if (walletInstance->nTimeFirstKey) {
pindexRescan = ::ChainActive().Next(pindexRescan); if (Optional<int> first_block = locked_chain->findFirstBlockWithTimeAndHeight(walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW, rescan_height)) {
rescan_height = *first_block;
}
} }
} }
nStart = GetTimeMillis(); nStart = GetTimeMillis();
{ {
WalletRescanReserver reserver(walletInstance.get()); WalletRescanReserver reserver(walletInstance.get());
const CBlockIndex *stop_block, *failed_block; if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(locked_chain->getBlockHash(rescan_height), {} /* stop block */, reserver, true /* update */).status)) {
if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, failed_block, stop_block, true))) {
return error(_("Failed to rescan the wallet during initialization")); return error(_("Failed to rescan the wallet during initialization"));
} }
} }
walletInstance->WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - nStart); walletInstance->WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - nStart);
walletInstance->ChainStateFlushed(::ChainActive().GetLocator()); walletInstance->ChainStateFlushed(locked_chain->getLocator());
walletInstance->database->IncrementUpdateCounter(); walletInstance->database->IncrementUpdateCounter();
// Restore wallet transaction metadata after -zapwallettxes=1 // Restore wallet transaction metadata after -zapwallettxes=1
@ -5438,10 +5459,10 @@ CWalletKey::CWalletKey(int64_t nExpires)
nTimeExpires = 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 // Update the tx's hashBlock
hashBlock = pindex->GetBlockHash(); hashBlock = block_hash;
// set the position of the transaction in the block // set the position of the transaction in the block
nIndex = posInBlock; nIndex = posInBlock;
@ -5454,12 +5475,7 @@ int CMerkleTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const
AssertLockHeld(cs_main); AssertLockHeld(cs_main);
// Find the block it claims to be in return locked_chain.getBlockDepth(hashBlock) * (nIndex == -1 ? -1 : 1);
CBlockIndex* pindex = LookupBlockIndex(hashBlock);
if (!pindex || !::ChainActive().Contains(pindex))
return 0;
return ((nIndex == -1) ? (-1) : 1) * (::ChainActive().Height() - pindex->nHeight + 1);
} }
bool CMerkleTx::IsLockedByInstantSend() const bool CMerkleTx::IsLockedByInstantSend() const

View File

@ -97,7 +97,6 @@ static const bool DEFAULT_DISABLE_WALLET = false;
//! if set, all keys will be derived by using BIP39/BIP44 //! if set, all keys will be derived by using BIP39/BIP44
static const bool DEFAULT_USE_HD_WALLET = false; static const bool DEFAULT_USE_HD_WALLET = false;
class CBlockIndex;
class CCoinControl; class CCoinControl;
class CKey; class CKey;
class COutput; class COutput;
@ -283,7 +282,7 @@ public:
READWRITE(obj.tx, obj.hashBlock, vMerkleBranch, obj.nIndex); READWRITE(obj.tx, obj.hashBlock, vMerkleBranch, obj.nIndex);
} }
void SetMerkleBranch(const CBlockIndex* pIndex, int posInBlock); void SetMerkleBranch(const uint256& block_hash, int posInBlock);
/** /**
* Return depth of transaction in blockchain: * Return depth of transaction in blockchain:
@ -711,7 +710,7 @@ private:
* Abandoned state should probably be more carefully tracked via different * Abandoned state should probably be more carefully tracked via different
* posInBlock signals or by checking mempool presence when necessary. * 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. */ /* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
void MarkConflicted(const uint256& hashBlock, const uint256& hashTx); void MarkConflicted(const uint256& hashBlock, const uint256& hashTx);
@ -722,8 +721,8 @@ private:
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions. /* 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. */ * 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, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SyncTransaction(const CTransactionRef& tx, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/* HD derive new child key (on internal or external chain) */ /* HD derive new child key (on internal or external chain) */
void DeriveNewChildKey(WalletBatch &batch, const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal /*= false*/) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void DeriveNewChildKey(WalletBatch &batch, const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal /*= false*/) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@ -770,10 +769,8 @@ private:
* Note that this is *not* how far we've processed, we may need some rescan * 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 * to have seen all transactions in the chain, but is only used to track
* live BlockConnected callbacks. * live BlockConnected callbacks.
*
* Protected by cs_main (see BlockUntilSyncedToCurrentChain)
*/ */
const CBlockIndex* m_last_block_processed = nullptr; uint256 m_last_block_processed;
/** Pulled from wallet DB ("ps_salt") and used when mixing a random number of rounds. /** Pulled from wallet DB ("ps_salt") and used when mixing a random number of rounds.
* This salt is needed to prevent an attacker from learning how many extra times * This salt is needed to prevent an attacker from learning how many extra times
@ -1004,12 +1001,22 @@ public:
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected) override; void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected) override;
int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
enum class ScanResult { struct ScanResult {
SUCCESS, enum { SUCCESS, FAILURE, USER_ABORT } status = SUCCESS;
FAILURE,
USER_ABORT //! 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, MemPoolRemovalReason reason) override; void TransactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) override;
void ReacceptWalletTransactions(); void ReacceptWalletTransactions();
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override EXCLUSIVE_LOCKS_REQUIRED(cs_main); void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override EXCLUSIVE_LOCKS_REQUIRED(cs_main);