mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 12:02:48 +01:00
Merge pull request #4537 from PastaPastaPasta/backport-14711
Backport 14711, 15342, partial 15855
This commit is contained in:
commit
1ff4e7599c
@ -229,6 +229,7 @@ BITCOIN_CORE_H = \
|
||||
node/coinstats.h \
|
||||
node/transaction.h \
|
||||
noui.h \
|
||||
optional.h \
|
||||
policy/feerate.h \
|
||||
policy/fees.h \
|
||||
policy/policy.h \
|
||||
|
@ -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,132 @@ namespace {
|
||||
|
||||
class LockImpl : public Chain::Lock
|
||||
{
|
||||
Optional<int> getHeight() override
|
||||
{
|
||||
LockAnnotation lock(::cs_main);
|
||||
int height = ::ChainActive().Height();
|
||||
if (height >= 0) {
|
||||
return height;
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
Optional<int> getBlockHeight(const uint256& hash) override
|
||||
{
|
||||
LockAnnotation lock(::cs_main);
|
||||
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
|
||||
{
|
||||
LockAnnotation lock(::cs_main);
|
||||
CBlockIndex* block = ::ChainActive()[height];
|
||||
assert(block != nullptr);
|
||||
return block->GetBlockHash();
|
||||
}
|
||||
int64_t getBlockTime(int height) override
|
||||
{
|
||||
LockAnnotation lock(::cs_main);
|
||||
CBlockIndex* block = ::ChainActive()[height];
|
||||
assert(block != nullptr);
|
||||
return block->GetBlockTime();
|
||||
}
|
||||
int64_t getBlockMedianTimePast(int height) override
|
||||
{
|
||||
LockAnnotation lock(::cs_main);
|
||||
CBlockIndex* block = ::ChainActive()[height];
|
||||
assert(block != nullptr);
|
||||
return block->GetMedianTimePast();
|
||||
}
|
||||
bool haveBlockOnDisk(int height) override
|
||||
{
|
||||
LockAnnotation lock(::cs_main);
|
||||
CBlockIndex* block = ::ChainActive()[height];
|
||||
return block && ((block->nStatus & BLOCK_HAVE_DATA) != 0) && block->nTx > 0;
|
||||
}
|
||||
Optional<int> findFirstBlockWithTime(int64_t time, uint256* hash) override
|
||||
{
|
||||
LockAnnotation lock(::cs_main);
|
||||
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
|
||||
{
|
||||
LockAnnotation lock(::cs_main);
|
||||
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
|
||||
{
|
||||
LockAnnotation lock(::cs_main);
|
||||
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
|
||||
{
|
||||
LockAnnotation lock(::cs_main);
|
||||
if (::ChainActive().Tip()->GetBlockHash() == hash) return true;
|
||||
CBlockIndex* block = LookupBlockIndex(hash);
|
||||
return block && block->GetAncestor(::ChainActive().Height()) == ::ChainActive().Tip();
|
||||
}
|
||||
CBlockLocator getTipLocator() override
|
||||
{
|
||||
LockAnnotation lock(::cs_main);
|
||||
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 +165,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
|
||||
|
@ -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 getTipLocator() = 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
|
||||
|
@ -382,8 +382,12 @@ public:
|
||||
if (mi == m_wallet->mapWallet.end()) {
|
||||
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);
|
||||
block_time = ::ChainActive().Tip()->GetBlockTime();
|
||||
return true;
|
||||
}
|
||||
WalletTx getWalletTxDetails(const uint256& txid,
|
||||
@ -396,7 +400,7 @@ public:
|
||||
LOCK(m_wallet->cs_wallet);
|
||||
auto mi = m_wallet->mapWallet.find(txid);
|
||||
if (mi != m_wallet->mapWallet.end()) {
|
||||
num_blocks = ::ChainActive().Height();
|
||||
num_blocks = locked_chain->getHeight().value_or(-1);
|
||||
in_mempool = mi->second.InMempool();
|
||||
order_form = mi->second.vOrderForm;
|
||||
tx_status = MakeWalletTxStatus(*locked_chain, mi->second);
|
||||
@ -431,7 +435,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
balances = getBalances();
|
||||
num_blocks = ::ChainActive().Height();
|
||||
num_blocks = locked_chain->getHeight().value_or(-1);
|
||||
return true;
|
||||
}
|
||||
CAmount getBalance() override
|
||||
|
17
src/optional.h
Normal file
17
src/optional.h
Normal 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
|
@ -2,6 +2,7 @@
|
||||
#include <qt/test/util.h>
|
||||
|
||||
#include <coinjoin/client.h>
|
||||
#include <init.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/node.h>
|
||||
#include <base58.h>
|
||||
@ -121,13 +122,10 @@ void TestGUI()
|
||||
|
||||
WalletRescanReserver reserver(wallet.get());
|
||||
reserver.reserve();
|
||||
const CBlockIndex* const null_block = nullptr;
|
||||
const CBlockIndex *stop_block, *failed_block;
|
||||
QCOMPARE(
|
||||
wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block, true /* fUpdate */),
|
||||
CWallet::ScanResult::SUCCESS);
|
||||
QCOMPARE(stop_block, ::ChainActive().Tip());
|
||||
QCOMPARE(failed_block, null_block);
|
||||
CWallet::ScanResult result = wallet->ScanForWalletTransactions(locked_chain->getBlockHash(0), {} /* stop_block */, reserver, true /* fUpdate */);
|
||||
QCOMPARE(result.status, CWallet::ScanResult::SUCCESS);
|
||||
QCOMPARE(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash());
|
||||
QVERIFY(result.last_failed_block.IsNull());
|
||||
}
|
||||
wallet->SetBroadcastTransactions(true);
|
||||
|
||||
|
@ -354,8 +354,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
|
||||
|
||||
auto locked_chain = pwallet->chain().lock();
|
||||
LockAnnotation lock(::cs_main);
|
||||
const CBlockIndex* pindex = LookupBlockIndex(merkleBlock.header.GetHash());
|
||||
if (!pindex || !::ChainActive().Contains(pindex)) {
|
||||
if (locked_chain->getBlockHeight(merkleBlock.header.GetHash()) == nullopt) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
|
||||
}
|
||||
|
||||
@ -544,7 +543,8 @@ UniValue importwallet(const JSONRPCRequest& request)
|
||||
if (!file.is_open()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
|
||||
}
|
||||
nTimeBegin = ::ChainActive().Tip()->GetBlockTime();
|
||||
Optional<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);
|
||||
@ -688,7 +688,8 @@ UniValue importelectrumwallet(const JSONRPCRequest& request)
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
|
||||
}
|
||||
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
auto locked_chain = pwallet->chain().lock();
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
EnsureWalletIsUnlocked(pwallet);
|
||||
|
||||
@ -777,24 +778,25 @@ UniValue importelectrumwallet(const JSONRPCRequest& request)
|
||||
file.close();
|
||||
pwallet->ShowProgress("", 100); // hide progress dialog in GUI
|
||||
|
||||
const uint32_t tip_height = locked_chain->getHeight().value_or(-1);
|
||||
|
||||
// Whether to perform rescan after import
|
||||
int nStartHeight = 0;
|
||||
if (!request.params[1].isNull())
|
||||
nStartHeight = request.params[1].get_int();
|
||||
if (::ChainActive().Height() < nStartHeight)
|
||||
nStartHeight = ::ChainActive().Height();
|
||||
if (tip_height < nStartHeight)
|
||||
nStartHeight = tip_height;
|
||||
|
||||
// Assume that electrum wallet was created at that block
|
||||
int nTimeBegin = ::ChainActive()[nStartHeight]->GetBlockTime();
|
||||
int nTimeBegin = locked_chain->getBlockTime(nStartHeight);
|
||||
pwallet->UpdateTimeFirstKey(nTimeBegin);
|
||||
|
||||
pwallet->WalletLogPrintf("Rescanning %i blocks\n", ::ChainActive().Height() - nStartHeight + 1);
|
||||
pwallet->WalletLogPrintf("Rescanning %i blocks\n", tip_height - nStartHeight + 1);
|
||||
WalletRescanReserver reserver(pwallet);
|
||||
if (!reserver.reserve()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||
}
|
||||
const CBlockIndex *stop_block, *failed_block;
|
||||
pwallet->ScanForWalletTransactions(::ChainActive()[nStartHeight], nullptr, reserver, failed_block, stop_block, true);
|
||||
pwallet->ScanForWalletTransactions(locked_chain->getBlockHash(nStartHeight), {}, reserver, true);
|
||||
|
||||
if (!fGood)
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet");
|
||||
@ -968,15 +970,16 @@ UniValue dumpwallet(const JSONRPCRequest& request)
|
||||
// produce output
|
||||
file << strprintf("# Wallet dump created by Dash Core %s\n", CLIENT_BUILD);
|
||||
file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime()));
|
||||
file << strprintf("# * Best block at time of backup was %i (%s),\n", ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash().ToString());
|
||||
file << strprintf("# mined on %s\n", FormatISO8601DateTime(::ChainActive().Tip()->GetBlockTime()));
|
||||
const Optional<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";
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("dashcoreversion", CLIENT_BUILD);
|
||||
obj.pushKV("lastblockheight", ::ChainActive().Height());
|
||||
obj.pushKV("lastblockhash", ::ChainActive().Tip()->GetBlockHash().ToString());
|
||||
obj.pushKV("lastblocktime", FormatISO8601DateTime(::ChainActive().Tip()->GetBlockTime()));
|
||||
obj.pushKV("lastblockheight", tip_height.value_or(-1));
|
||||
obj.pushKV("lastblockhash", tip_height ? locked_chain->getBlockHash(*tip_height).ToString() : NullUniValue);
|
||||
obj.pushKV("lastblocktime", tip_height ? FormatISO8601DateTime(locked_chain->getBlockTime(*tip_height)) : NullUniValue);
|
||||
|
||||
// add the base58check encoded extended master if the wallet uses HD
|
||||
CHDChain hdChainCurrent;
|
||||
@ -1552,15 +1555,16 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
|
||||
EnsureWalletIsUnlocked(pwallet);
|
||||
|
||||
// Verify all timestamps are present before importing any keys.
|
||||
now = ::ChainActive().Tip() ? ::ChainActive().Tip()->GetMedianTimePast() : 0;
|
||||
const Optional<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;
|
||||
}
|
||||
|
@ -119,7 +119,10 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo
|
||||
{
|
||||
entry.pushKV("blockhash", wtx.hashBlock.GetHex());
|
||||
entry.pushKV("blockindex", wtx.nIndex);
|
||||
entry.pushKV("blocktime", LookupBlockIndex(wtx.hashBlock)->GetBlockTime());
|
||||
int64_t block_time;
|
||||
bool found_block = chain.findBlock(wtx.hashBlock, nullptr /* block */, &block_time);
|
||||
assert(found_block);
|
||||
entry.pushKV("blocktime", block_time);
|
||||
} else {
|
||||
entry.pushKV("trusted", wtx.IsTrusted(locked_chain));
|
||||
}
|
||||
@ -1634,26 +1637,19 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
|
||||
LockAnnotation lock(::cs_main);
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
const CBlockIndex* pindex = nullptr; // Block index of the specified block or the common ancestor, if the block provided was in a deactivated chain.
|
||||
const CBlockIndex* paltindex = nullptr; // Block index of the specified block, even if it's in a deactivated chain.
|
||||
Optional<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;
|
||||
|
||||
if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
|
||||
uint256 blockId;
|
||||
|
||||
if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
|
||||
blockId.SetHex(request.params[0].get_str());
|
||||
paltindex = pindex = LookupBlockIndex(blockId);
|
||||
height = locked_chain->findFork(blockId, &altheight);
|
||||
|
||||
if (!pindex) {
|
||||
if (!height) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
||||
}
|
||||
if (::ChainActive()[pindex->nHeight] != pindex) {
|
||||
// the block being asked for is a part of a deactivated chain;
|
||||
// we don't want to depend on its perceived height in the block
|
||||
// chain, we want to instead use the last common ancestor
|
||||
pindex = ::ChainActive().FindFork(pindex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!request.params[1].isNull()) {
|
||||
@ -1670,7 +1666,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
|
||||
|
||||
bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
|
||||
|
||||
int depth = pindex ? (1 + ::ChainActive().Height() - pindex->nHeight) : -1;
|
||||
const Optional<int> tip_height = locked_chain->getHeight();
|
||||
int depth = tip_height && height ? (1 + *tip_height - *height) : -1;
|
||||
|
||||
UniValue transactions(UniValue::VARR);
|
||||
|
||||
@ -1685,9 +1682,9 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
|
||||
// when a reorg'd block is requested, we also list any relevant transactions
|
||||
// in the blocks of the chain that was detached
|
||||
UniValue removed(UniValue::VARR);
|
||||
while (include_removed && paltindex && paltindex != pindex) {
|
||||
while (include_removed && altheight && *altheight > *height) {
|
||||
CBlock block;
|
||||
if (!ReadBlockFromDisk(block, paltindex, Params().GetConsensus())) {
|
||||
if (!pwallet->chain().findBlock(blockId, &block) || block.IsNull()) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
|
||||
}
|
||||
for (const CTransactionRef& tx : block.vtx) {
|
||||
@ -1698,11 +1695,12 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
|
||||
ListTransactions(*locked_chain, pwallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */);
|
||||
}
|
||||
}
|
||||
paltindex = paltindex->pprev;
|
||||
blockId = block.hashPrevBlock;
|
||||
--*altheight;
|
||||
}
|
||||
|
||||
CBlockIndex *pblockLast = ::ChainActive()[::ChainActive().Height() + 1 - target_confirms];
|
||||
uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : uint256();
|
||||
int last_height = tip_height ? *tip_height + 1 - target_confirms : -1;
|
||||
uint256 lastblock = last_height >= 0 ? locked_chain->getBlockHash(last_height) : uint256();
|
||||
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
ret.pushKV("transactions", transactions);
|
||||
@ -2701,7 +2699,8 @@ static UniValue upgradetohd(const JSONRPCRequest& request)
|
||||
+ HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\"")
|
||||
+ HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\" \"walletpassphrase\""));
|
||||
|
||||
LOCK2(cs_main, pwallet->cs_wallet);
|
||||
auto locked_chain = pwallet->chain().lock();
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
// Do not do anything to HD wallets
|
||||
if (pwallet->IsHDEnabled()) {
|
||||
@ -2772,8 +2771,7 @@ static UniValue upgradetohd(const JSONRPCRequest& request)
|
||||
if (!reserver.reserve()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||
}
|
||||
const CBlockIndex *stop_block, *failed_block;
|
||||
pwallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block, true);
|
||||
pwallet->ScanForWalletTransactions(locked_chain->getBlockHash(0), {}, reserver, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -3565,8 +3563,8 @@ static UniValue rescanblockchain(const JSONRPCRequest& request)
|
||||
.ToString() +
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"start_height\" (numeric) The block height where the rescan has started.\n"
|
||||
" \"stop_height\" (numeric) The height of the last rescanned block.\n"
|
||||
" \"start_height\" (numeric) The block height where the rescan started (the requested height or 0)\n"
|
||||
" \"stop_height\" (numeric) The height of the last rescanned block. May be null in rare cases if there was a reorg and the call didn't scan any blocks because they were already scanned in the background.\n"
|
||||
"}\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("rescanblockchain", "100000 120000")
|
||||
@ -3579,50 +3577,53 @@ static UniValue rescanblockchain(const JSONRPCRequest& request)
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||
}
|
||||
|
||||
CBlockIndex *pindexStart = nullptr;
|
||||
CBlockIndex *pindexStop = nullptr;
|
||||
CBlockIndex *pChainTip = nullptr;
|
||||
int start_height = 0;
|
||||
uint256 start_block, stop_block;
|
||||
{
|
||||
auto locked_chain = pwallet->chain().lock();
|
||||
pindexStart = ::ChainActive().Genesis();
|
||||
pChainTip = ::ChainActive().Tip();
|
||||
Optional<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)) {
|
||||
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.");
|
||||
}
|
||||
block = block->pprev;
|
||||
|
||||
if (tip_height) {
|
||||
start_block = locked_chain->getBlockHash(start_height);
|
||||
// If called with a stop_height, set the stop_height here to
|
||||
// trigger a rescan to that height.
|
||||
// If called without a stop height, leave stop_height as null here
|
||||
// so rescan continues to the tip (even if the tip advances during
|
||||
// rescan).
|
||||
if (stop_height) {
|
||||
stop_block = locked_chain->getBlockHash(*stop_height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CBlockIndex *failed_block, *stopBlock;
|
||||
CWallet::ScanResult result =
|
||||
pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, failed_block, stopBlock, true);
|
||||
switch (result) {
|
||||
pwallet->ScanForWalletTransactions(start_block, stop_block, reserver, true /* fUpdate */);
|
||||
switch (result.status) {
|
||||
case CWallet::ScanResult::SUCCESS:
|
||||
break; // stopBlock set by ScanForWalletTransactions
|
||||
break;
|
||||
case CWallet::ScanResult::FAILURE:
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
|
||||
case CWallet::ScanResult::USER_ABORT:
|
||||
@ -3630,8 +3631,8 @@ static UniValue rescanblockchain(const JSONRPCRequest& request)
|
||||
// no default case, so the compiler can warn about missing cases
|
||||
}
|
||||
UniValue response(UniValue::VOBJ);
|
||||
response.pushKV("start_height", pindexStart->nHeight);
|
||||
response.pushKV("stop_height", stopBlock->nHeight);
|
||||
response.pushKV("start_height", start_height);
|
||||
response.pushKV("stop_height", result.last_scanned_height ? *result.last_scanned_height : UniValue());
|
||||
return response;
|
||||
}
|
||||
|
||||
|
@ -47,8 +47,9 @@ public:
|
||||
}
|
||||
WalletRescanReserver reserver(wallet.get());
|
||||
reserver.reserve();
|
||||
const CBlockIndex *stop_block, *failed_block;
|
||||
wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block);
|
||||
|
||||
CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), {} /* stop_block */, reserver, true /* fUpdate */);
|
||||
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
|
||||
}
|
||||
|
||||
~CTransactionBuilderTestSetup()
|
||||
@ -70,8 +71,9 @@ public:
|
||||
blocktx = CMutableTransaction(*it->second.tx);
|
||||
}
|
||||
CreateAndProcessBlock({blocktx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
LOCK2(cs_main, wallet->cs_wallet);
|
||||
it->second.SetMerkleBranch(::ChainActive().Tip(), 1);
|
||||
auto locked_chain = wallet->chain().lock();
|
||||
LOCK(wallet->cs_wallet);
|
||||
it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1);
|
||||
return it->second;
|
||||
}
|
||||
CompactTallyItem GetTallyItem(const std::vector<CAmount>& vecAmounts)
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <consensus/validation.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <key_io.h>
|
||||
#include <policy/policy.h>
|
||||
#include <rpc/server.h>
|
||||
#include <test/setup_common.h>
|
||||
#include <validation.h>
|
||||
@ -39,7 +40,6 @@ static void AddKey(CWallet& wallet, const CKey& key)
|
||||
BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
|
||||
{
|
||||
// Cap last block file size, and mine new block in a new block file.
|
||||
const CBlockIndex* const null_block = nullptr;
|
||||
CBlockIndex* oldTip = ::ChainActive().Tip();
|
||||
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
|
||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
@ -47,18 +47,19 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
|
||||
|
||||
auto chain = interfaces::MakeChain();
|
||||
auto locked_chain = chain->lock();
|
||||
LockAnnotation lock(::cs_main); // for PruneOneBlockFile
|
||||
LockAnnotation lock(::cs_main);
|
||||
|
||||
// Verify ScanForWalletTransactions accomodates a null start block.
|
||||
// Verify ScanForWalletTransactions accommodates a null start block.
|
||||
{
|
||||
CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy());
|
||||
AddKey(wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(&wallet);
|
||||
reserver.reserve();
|
||||
const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1;
|
||||
BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(nullptr, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS);
|
||||
BOOST_CHECK_EQUAL(failed_block, null_block);
|
||||
BOOST_CHECK_EQUAL(stop_block, null_block);
|
||||
CWallet::ScanResult result = wallet.ScanForWalletTransactions({} /* start_block */, {} /* stop_block */, reserver, false /* update */);
|
||||
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
|
||||
BOOST_CHECK(result.last_failed_block.IsNull());
|
||||
BOOST_CHECK(result.last_scanned_block.IsNull());
|
||||
BOOST_CHECK(!result.last_scanned_height);
|
||||
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0);
|
||||
}
|
||||
|
||||
@ -69,10 +70,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.last_failed_block.IsNull());
|
||||
BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
|
||||
BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
|
||||
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 1000 * COIN);
|
||||
}
|
||||
|
||||
@ -87,10 +89,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.last_failed_block, oldTip->GetBlockHash());
|
||||
BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
|
||||
BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
|
||||
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 500 * COIN);
|
||||
}
|
||||
|
||||
@ -104,26 +107,26 @@ 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.last_failed_block, newTip->GetBlockHash());
|
||||
BOOST_CHECK(result.last_scanned_block.IsNull());
|
||||
BOOST_CHECK(!result.last_scanned_height);
|
||||
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
|
||||
{
|
||||
auto chain = interfaces::MakeChain();
|
||||
|
||||
// Cap last block file size, and mine new block in a new block file.
|
||||
CBlockIndex* oldTip = ::ChainActive().Tip();
|
||||
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
|
||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
CBlockIndex* newTip = ::ChainActive().Tip();
|
||||
|
||||
LockAnnotation lock(::cs_main);
|
||||
auto chain = interfaces::MakeChain();
|
||||
auto locked_chain = chain->lock();
|
||||
LockAnnotation lock(::cs_main);
|
||||
|
||||
// Prune the older block file.
|
||||
PruneOneBlockFile(oldTip->GetBlockPos().nFile);
|
||||
@ -284,7 +287,7 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
|
||||
|
||||
CWalletTx wtx(&wallet, MakeTransactionRef(tx));
|
||||
if (block) {
|
||||
wtx.SetMerkleBranch(block, 0);
|
||||
wtx.SetMerkleBranch(block->GetBlockHash(), 0);
|
||||
}
|
||||
{
|
||||
LOCK(cs_main);
|
||||
@ -348,11 +351,11 @@ public:
|
||||
AddKey(*wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(wallet.get());
|
||||
reserver.reserve();
|
||||
const CBlockIndex* const null_block = nullptr;
|
||||
const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1;
|
||||
BOOST_CHECK_EQUAL(wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS);
|
||||
BOOST_CHECK_EQUAL(stop_block, ::ChainActive().Tip());
|
||||
BOOST_CHECK_EQUAL(failed_block, null_block);
|
||||
CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), {} /* stop_block */, reserver, false /* update */);
|
||||
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
|
||||
BOOST_CHECK_EQUAL(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash());
|
||||
BOOST_CHECK_EQUAL(*result.last_scanned_height, ::ChainActive().Height());
|
||||
BOOST_CHECK(result.last_failed_block.IsNull());
|
||||
}
|
||||
|
||||
~ListCoinsTestingSetup()
|
||||
@ -382,7 +385,7 @@ public:
|
||||
LOCK(wallet->cs_wallet);
|
||||
auto it = wallet->mapWallet.find(tx->GetHash());
|
||||
BOOST_CHECK(it != wallet->mapWallet.end());
|
||||
it->second.SetMerkleBranch(::ChainActive().Tip(), 1);
|
||||
it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1);
|
||||
return it->second;
|
||||
}
|
||||
|
||||
@ -485,8 +488,8 @@ public:
|
||||
AddKey(*wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(wallet.get());
|
||||
reserver.reserve();
|
||||
const CBlockIndex *stop_block, *failed_block;
|
||||
wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block);
|
||||
CWallet::ScanResult result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash() /* start_block */, {} /* stop_block */, reserver, false /* update */);
|
||||
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
|
||||
}
|
||||
|
||||
std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain();
|
||||
@ -588,7 +591,7 @@ public:
|
||||
LOCK2(cs_main, wallet->cs_wallet);
|
||||
auto it = wallet->mapWallet.find(tx->GetHash());
|
||||
BOOST_CHECK(it != wallet->mapWallet.end());
|
||||
it->second.SetMerkleBranch(::ChainActive().Tip(), 1);
|
||||
it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1);
|
||||
|
||||
std::vector<COutPoint> vecOutpoints;
|
||||
size_t n;
|
||||
@ -932,8 +935,8 @@ BOOST_FIXTURE_TEST_CASE(select_coins_grouped_by_addresses, ListCoinsTestingSetup
|
||||
// Reveal the mined tx, it should conflict with the one we have in the wallet already.
|
||||
WalletRescanReserver reserver(wallet.get());
|
||||
reserver.reserve();
|
||||
const CBlockIndex *stop_block, *failed_block;
|
||||
BOOST_CHECK_EQUAL(wallet->ScanForWalletTransactions(::ChainActive().Genesis(), nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS);
|
||||
auto result = wallet->ScanForWalletTransactions(::ChainActive().Genesis()->GetBlockHash(), {}, reserver, false);
|
||||
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
const auto& conflicts = wallet->GetConflicts(tx2->GetHash());
|
||||
|
@ -1185,20 +1185,20 @@ void CWallet::LoadToWallet(const CWalletTx& wtxIn)
|
||||
}
|
||||
}
|
||||
|
||||
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate)
|
||||
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool fUpdate)
|
||||
{
|
||||
const CTransaction& tx = *ptx;
|
||||
{
|
||||
AssertLockHeld(cs_main); // because of AddToWallet
|
||||
AssertLockHeld(cs_wallet);
|
||||
|
||||
if (pIndex != nullptr) {
|
||||
if (!block_hash.IsNull()) {
|
||||
for (const CTxIn& txin : tx.vin) {
|
||||
std::pair<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++;
|
||||
}
|
||||
@ -1231,14 +1231,19 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI
|
||||
WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__);
|
||||
}
|
||||
}
|
||||
if (pIndex != nullptr && mapKeyMetadata[keyid].nCreateTime > pIndex->nTime) {
|
||||
if (!block_hash.IsNull()) {
|
||||
int64_t block_time;
|
||||
bool found_block = chain().findBlock(block_hash, nullptr /* block */, &block_time);
|
||||
assert(found_block);
|
||||
if (mapKeyMetadata[keyid].nCreateTime > block_time) {
|
||||
WalletLogPrintf("%s: Found a key which appears to be used earlier than we expected, updating metadata\n", __func__);
|
||||
CPubKey vchPubKey;
|
||||
bool res = GetPubKey(keyid, vchPubKey);
|
||||
assert(res); // this should never fail
|
||||
mapKeyMetadata[keyid].nCreateTime = pIndex->nTime;
|
||||
mapKeyMetadata[keyid].nCreateTime = block_time;
|
||||
batch.WriteKeyMeta(vchPubKey, mapKeyMetadata[keyid]);
|
||||
UpdateTimeFirstKey(pIndex->nTime);
|
||||
UpdateTimeFirstKey(block_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1246,8 +1251,8 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI
|
||||
CWalletTx wtx(this, ptx);
|
||||
|
||||
// Get merkle branch if transaction was found in a block
|
||||
if (pIndex != nullptr)
|
||||
wtx.SetMerkleBranch(pIndex, posInBlock);
|
||||
if (!block_hash.IsNull())
|
||||
wtx.SetMerkleBranch(block_hash, posInBlock);
|
||||
|
||||
return AddToWallet(wtx, false);
|
||||
}
|
||||
@ -1338,11 +1343,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
|
||||
LockAnnotation lock(::cs_main);
|
||||
LOCK(cs_wallet); // check WalletBatch::LoadWallet()
|
||||
|
||||
int conflictconfirms = 0;
|
||||
CBlockIndex* pindex = LookupBlockIndex(hashBlock);
|
||||
if (pindex && ::ChainActive().Contains(pindex)) {
|
||||
conflictconfirms = -(::ChainActive().Height() - pindex->nHeight + 1);
|
||||
}
|
||||
int conflictconfirms = -locked_chain->getBlockDepth(hashBlock);
|
||||
// If number of conflict confirms cannot be determined, this means
|
||||
// that the block is still unknown or not yet part of the main chain,
|
||||
// for example when loading the wallet during a reindex. Do nothing in that
|
||||
@ -1391,8 +1392,8 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
|
||||
fAnonymizableTallyCachedNonDenom = false;
|
||||
}
|
||||
|
||||
void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pindex, int posInBlock, bool update_tx) {
|
||||
if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, update_tx))
|
||||
void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool update_tx) {
|
||||
if (!AddToWalletIfInvolvingMe(ptx, block_hash, posInBlock, update_tx))
|
||||
return; // Not one of ours
|
||||
|
||||
// If a transaction changes 'conflicted' state, that changes the balance
|
||||
@ -1407,7 +1408,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pin
|
||||
void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx, int64_t nAcceptTime) {
|
||||
auto locked_chain = chain().lock();
|
||||
LOCK(cs_wallet);
|
||||
SyncTransaction(ptx);
|
||||
SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
|
||||
|
||||
auto it = mapWallet.find(ptx->GetHash());
|
||||
if (it != mapWallet.end()) {
|
||||
@ -1437,17 +1438,17 @@ void CWallet::BlockConnected(const std::shared_ptr<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 */);
|
||||
// UNKNOWN because it's a manual removal, not using mempool logic
|
||||
TransactionRemovedFromMempool(ptx, MemPoolRemovalReason::UNKNOWN);
|
||||
}
|
||||
for (size_t i = 0; i < pblock->vtx.size(); i++) {
|
||||
SyncTransaction(pblock->vtx[i], pindex, i);
|
||||
SyncTransaction(pblock->vtx[i], pindex->GetBlockHash(), i);
|
||||
// UNKNOWN because it's a manual removal, not using mempool logic
|
||||
TransactionRemovedFromMempool(pblock->vtx[i], MemPoolRemovalReason::UNKNOWN);
|
||||
}
|
||||
|
||||
m_last_block_processed = pindex;
|
||||
m_last_block_processed = pindex->GetBlockHash();
|
||||
|
||||
// The GUI expects a NotifyTransactionChanged when a coinbase tx
|
||||
// which is in our wallet moves from in-the-best-block to
|
||||
@ -1487,7 +1488,7 @@ void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, con
|
||||
|
||||
for (const CTransactionRef& ptx : pblock->vtx) {
|
||||
// NOTE: do NOT pass pindex here
|
||||
SyncTransaction(ptx);
|
||||
SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
|
||||
}
|
||||
|
||||
// reset cache to make sure no longer mature coins are excluded
|
||||
@ -1508,9 +1509,8 @@ void CWallet::BlockUntilSyncedToCurrentChain() {
|
||||
// protected by cs_wallet instead of cs_main, but as long as we need
|
||||
// cs_main here anyway, it's easier to just call it cs_main-protected.
|
||||
auto locked_chain = chain().lock();
|
||||
const CBlockIndex* initialChainTip = ::ChainActive().Tip();
|
||||
|
||||
if (m_last_block_processed && m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) {
|
||||
if (!m_last_block_processed.IsNull() && locked_chain->isPotentialTip(m_last_block_processed)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -2132,133 +2132,146 @@ 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.last_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 Scan starting block. If block is not on the active
|
||||
* chain, the scan will return SUCCESS immediately.
|
||||
* @param[in] stop_block Scan ending block. If block is not on the active
|
||||
* chain, the scan will continue until it reaches 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.
|
||||
* @return ScanResult returning scan information and indicating success or
|
||||
* failure. Return status will be set to 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()) {
|
||||
while (block_height && !fAbortRescan && !ShutdownRequested()) {
|
||||
m_scanning_progress = (progress_current - progress_begin) / (progress_end - progress_begin);
|
||||
if (pindex->nHeight % 100 == 0 && progress_end - progress_begin > 0.0) {
|
||||
if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) {
|
||||
ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100))));
|
||||
}
|
||||
if (GetTime() >= nNow + 60) {
|
||||
nNow = GetTime();
|
||||
WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, progress_current);
|
||||
WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", *block_height, progress_current);
|
||||
}
|
||||
|
||||
CBlock block;
|
||||
if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
|
||||
if (chain().findBlock(block_hash, &block) && !block.IsNull()) {
|
||||
auto locked_chain = chain().lock();
|
||||
LOCK(cs_wallet);
|
||||
if (pindex && !::ChainActive().Contains(pindex)) {
|
||||
if (!locked_chain->getBlockHeight(block_hash)) {
|
||||
// Abort scan if current block is no longer active, to prevent
|
||||
// marking transactions as coming from the wrong block.
|
||||
failed_block = pindex;
|
||||
// TODO: This should return success instead of failure, see
|
||||
// https://github.com/bitcoin/bitcoin/pull/14711#issuecomment-458342518
|
||||
result.last_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.last_scanned_block = block_hash;
|
||||
result.last_scanned_height = *block_height;
|
||||
} else {
|
||||
// could not scan block, keep scanning but record this block as the most recent failure
|
||||
failed_block = pindex;
|
||||
result.last_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, 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, progress_current);
|
||||
result.status = ScanResult::USER_ABORT;
|
||||
}
|
||||
}
|
||||
if (failed_block) {
|
||||
return ScanResult::FAILURE;
|
||||
} else {
|
||||
return ScanResult::SUCCESS;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void CWallet::ReacceptWalletTransactions()
|
||||
@ -3474,6 +3487,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,
|
||||
int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign, int nExtraPayloadSize)
|
||||
{
|
||||
uint32_t const height = locked_chain.getHeight().value_or(-1);
|
||||
|
||||
CAmount nValue = 0;
|
||||
int nChangePosRequest = nChangePosInOut;
|
||||
unsigned int nSubtractFeeFromAmount = 0;
|
||||
@ -3518,7 +3533,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
|
||||
// now we ensure code won't be written that makes assumptions about
|
||||
// nLockTime that preclude a fix later.
|
||||
|
||||
txNew.nLockTime = ::ChainActive().Height();
|
||||
txNew.nLockTime = height;
|
||||
|
||||
// Secondly occasionally randomly pick a nLockTime even further back, so
|
||||
// that transactions that are delayed after signing for whatever reason,
|
||||
@ -3527,7 +3542,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
|
||||
if (GetRandInt(10) == 0)
|
||||
txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100));
|
||||
|
||||
assert(txNew.nLockTime <= (unsigned int)::ChainActive().Height());
|
||||
assert(txNew.nLockTime <= height);
|
||||
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
|
||||
FeeCalculation feeCalc;
|
||||
CFeeRate discard_rate = coin_control.m_discard_feerate ? *coin_control.m_discard_feerate : GetDiscardRate(*this, ::feeEstimator);
|
||||
@ -4673,11 +4688,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
|
||||
@ -4689,18 +4705,16 @@ 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
|
||||
CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey);
|
||||
for (const CKeyID &keyid : vAffected) {
|
||||
// ... 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;
|
||||
}
|
||||
vAffected.clear();
|
||||
}
|
||||
@ -4709,7 +4723,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
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4738,7 +4752,8 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
|
||||
AssertLockHeld(::cs_main);
|
||||
unsigned int nTimeSmart = wtx.nTimeReceived;
|
||||
if (!wtx.hashUnset()) {
|
||||
if (const CBlockIndex* pindex = LookupBlockIndex(wtx.hashBlock)) {
|
||||
int64_t blocktime;
|
||||
if (chain().findBlock(wtx.hashBlock, nullptr /* block */, &blocktime)) {
|
||||
int64_t latestNow = wtx.nTimeReceived;
|
||||
int64_t latestEntry = 0;
|
||||
|
||||
@ -4764,7 +4779,6 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
|
||||
}
|
||||
}
|
||||
|
||||
int64_t blocktime = pindex->GetBlockTime();
|
||||
nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
|
||||
} else {
|
||||
WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString());
|
||||
@ -5007,7 +5021,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->getTipLocator());
|
||||
|
||||
// Try to create wallet backup right after new wallet was created
|
||||
std::string strBackupWarning;
|
||||
@ -5020,7 +5034,6 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
|
||||
return error(strBackupError);
|
||||
}
|
||||
}
|
||||
|
||||
} 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));
|
||||
@ -5113,59 +5126,68 @@ 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) {
|
||||
return error(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"));
|
||||
}
|
||||
}
|
||||
|
||||
uiInterface.InitMessage(_("Rescanning..."));
|
||||
walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", ::ChainActive().Height() - pindexRescan->nHeight, pindexRescan->nHeight);
|
||||
walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", *tip_height - rescan_height, rescan_height);
|
||||
|
||||
// No need to read and scan block if block was created before
|
||||
// our wallet birthday (as adjusted for block time variability)
|
||||
// unless a full rescan was requested
|
||||
if (gArgs.GetArg("-rescan", 0) != 2) {
|
||||
while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) {
|
||||
pindexRescan = ::ChainActive().Next(pindexRescan);
|
||||
if (walletInstance->nTimeFirstKey) {
|
||||
if (Optional<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)) {
|
||||
return error(_("Failed to rescan the wallet during initialization"));
|
||||
}
|
||||
}
|
||||
walletInstance->WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - nStart);
|
||||
walletInstance->ChainStateFlushed(::ChainActive().GetLocator());
|
||||
walletInstance->ChainStateFlushed(locked_chain->getTipLocator());
|
||||
walletInstance->database->IncrementUpdateCounter();
|
||||
|
||||
// Restore wallet transaction metadata after -zapwallettxes=1
|
||||
@ -5438,10 +5460,10 @@ CWalletKey::CWalletKey(int64_t nExpires)
|
||||
nTimeExpires = nExpires;
|
||||
}
|
||||
|
||||
void CMerkleTx::SetMerkleBranch(const CBlockIndex* pindex, int posInBlock)
|
||||
void CMerkleTx::SetMerkleBranch(const uint256& block_hash, int posInBlock)
|
||||
{
|
||||
// Update the tx's hashBlock
|
||||
hashBlock = pindex->GetBlockHash();
|
||||
hashBlock = block_hash;
|
||||
|
||||
// set the position of the transaction in the block
|
||||
nIndex = posInBlock;
|
||||
@ -5454,12 +5476,7 @@ int CMerkleTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const
|
||||
|
||||
AssertLockHeld(cs_main);
|
||||
|
||||
// Find the block it claims to be in
|
||||
CBlockIndex* pindex = LookupBlockIndex(hashBlock);
|
||||
if (!pindex || !::ChainActive().Contains(pindex))
|
||||
return 0;
|
||||
|
||||
return ((nIndex == -1) ? (-1) : 1) * (::ChainActive().Height() - pindex->nHeight + 1);
|
||||
return locked_chain.getBlockDepth(hashBlock) * (nIndex == -1 ? -1 : 1);
|
||||
}
|
||||
|
||||
bool CMerkleTx::IsLockedByInstantSend() const
|
||||
|
@ -97,7 +97,6 @@ static const bool DEFAULT_DISABLE_WALLET = false;
|
||||
//! if set, all keys will be derived by using BIP39/BIP44
|
||||
static const bool DEFAULT_USE_HD_WALLET = false;
|
||||
|
||||
class CBlockIndex;
|
||||
class CCoinControl;
|
||||
class CKey;
|
||||
class COutput;
|
||||
@ -283,7 +282,7 @@ public:
|
||||
READWRITE(obj.tx, obj.hashBlock, vMerkleBranch, obj.nIndex);
|
||||
}
|
||||
|
||||
void SetMerkleBranch(const CBlockIndex* pIndex, int posInBlock);
|
||||
void SetMerkleBranch(const uint256& block_hash, int posInBlock);
|
||||
|
||||
/**
|
||||
* Return depth of transaction in blockchain:
|
||||
@ -711,7 +710,7 @@ private:
|
||||
* Abandoned state should probably be more carefully tracked via different
|
||||
* posInBlock signals or by checking mempool presence when necessary.
|
||||
*/
|
||||
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
/* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
|
||||
void MarkConflicted(const uint256& hashBlock, const uint256& hashTx);
|
||||
@ -722,8 +721,8 @@ private:
|
||||
void SyncMetaData(std::pair<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);
|
||||
|
||||
/* HD derive new child key (on internal or external chain) */
|
||||
void DeriveNewChildKey(WalletBatch &batch, const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal /*= false*/) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
@ -770,10 +769,8 @@ private:
|
||||
* Note that this is *not* how far we've processed, we may need some rescan
|
||||
* to have seen all transactions in the chain, but is only used to track
|
||||
* live BlockConnected callbacks.
|
||||
*
|
||||
* Protected by cs_main (see BlockUntilSyncedToCurrentChain)
|
||||
*/
|
||||
const CBlockIndex* m_last_block_processed = nullptr;
|
||||
uint256 m_last_block_processed;
|
||||
|
||||
/** Pulled from wallet DB ("ps_salt") and used when mixing a random number of rounds.
|
||||
* This salt is needed to prevent an attacker from learning how many extra times
|
||||
@ -1004,12 +1001,22 @@ public:
|
||||
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected) override;
|
||||
int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
|
||||
|
||||
enum class ScanResult {
|
||||
SUCCESS,
|
||||
FAILURE,
|
||||
USER_ABORT
|
||||
struct ScanResult {
|
||||
enum { SUCCESS, FAILURE, USER_ABORT } status = SUCCESS;
|
||||
|
||||
//! Hash and height of most recent block that was successfully scanned.
|
||||
//! Unset if no blocks were scanned due to read errors or the chain
|
||||
//! being empty.
|
||||
uint256 last_scanned_block;
|
||||
Optional<int> last_scanned_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 last_failed_block;
|
||||
};
|
||||
ScanResult ScanForWalletTransactions(const CBlockIndex* const pindexStart, const CBlockIndex* const pindexStop, const WalletRescanReserver& reserver, const CBlockIndex*& failed_block, const CBlockIndex*& stop_block, bool fUpdate = false);
|
||||
ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate);
|
||||
void TransactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) override;
|
||||
void ReacceptWalletTransactions();
|
||||
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
Loading…
Reference in New Issue
Block a user