Convert CWallet::ScanForWalletTransactions and SyncTransaction to the new Chain apis
Only change in behavior is "Rescan started from block <height>" message replaced by "Rescan started from block <hash>" message in ScanForWalletTransactions. Co-authored-by: Ben Woosley <ben.woosley@gmail.com>
This commit is contained in:
parent
2ffb07929e
commit
db21f02648
7 changed files with 209 additions and 125 deletions
|
@ -60,6 +60,42 @@ class LockImpl : public Chain::Lock
|
|||
assert(block != nullptr);
|
||||
return block->GetMedianTimePast();
|
||||
}
|
||||
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);
|
||||
|
@ -116,6 +152,11 @@ public:
|
|||
}
|
||||
return true;
|
||||
}
|
||||
double guessVerificationProgress(const uint256& block_hash) override
|
||||
{
|
||||
LOCK(cs_main);
|
||||
return GuessVerificationProgress(Params().TxData(), LookupBlockIndex(block_hash));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -58,6 +58,26 @@ public:
|
|||
//! will abort.
|
||||
virtual int64_t getBlockMedianTimePast(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
|
||||
|
@ -85,6 +105,10 @@ public:
|
|||
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
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include <qt/test/wallettests.h>
|
||||
#include <qt/test/util.h>
|
||||
|
||||
#include <init.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/node.h>
|
||||
#include <base58.h>
|
||||
|
@ -146,13 +147,10 @@ void TestGUI()
|
|||
auto locked_chain = wallet->chain().lock();
|
||||
WalletRescanReserver reserver(wallet.get());
|
||||
reserver.reserve();
|
||||
const CBlockIndex* const null_block = nullptr;
|
||||
const CBlockIndex *stop_block, *failed_block;
|
||||
QCOMPARE(
|
||||
wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, failed_block, stop_block, true /* fUpdate */),
|
||||
CWallet::ScanResult::SUCCESS);
|
||||
QCOMPARE(stop_block, chainActive.Tip());
|
||||
QCOMPARE(failed_block, null_block);
|
||||
CWallet::ScanResult result = wallet->ScanForWalletTransactions(locked_chain->getBlockHash(0), {} /* stop_block */, reserver, true /* fUpdate */);
|
||||
QCOMPARE(result.status, CWallet::ScanResult::SUCCESS);
|
||||
QCOMPARE(result.stop_block, chainActive.Tip()->GetBlockHash());
|
||||
QVERIFY(result.failed_block.IsNull());
|
||||
}
|
||||
wallet->SetBroadcastTransactions(true);
|
||||
|
||||
|
|
|
@ -3387,50 +3387,48 @@ UniValue rescanblockchain(const JSONRPCRequest& request)
|
|||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||
}
|
||||
|
||||
CBlockIndex *pindexStart = nullptr;
|
||||
CBlockIndex *pindexStop = nullptr;
|
||||
CBlockIndex *pChainTip = nullptr;
|
||||
int start_height = 0;
|
||||
uint256 start_block, stop_block;
|
||||
{
|
||||
auto locked_chain = pwallet->chain().lock();
|
||||
pindexStart = chainActive.Genesis();
|
||||
pChainTip = chainActive.Tip();
|
||||
Optional<int> tip_height = locked_chain->getHeight();
|
||||
|
||||
if (!request.params[0].isNull()) {
|
||||
pindexStart = chainActive[request.params[0].get_int()];
|
||||
if (!pindexStart) {
|
||||
start_height = request.params[0].get_int();
|
||||
if (start_height < 0 || !tip_height || start_height > *tip_height) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
|
||||
}
|
||||
}
|
||||
|
||||
Optional<int> stop_height;
|
||||
if (!request.params[1].isNull()) {
|
||||
pindexStop = chainActive[request.params[1].get_int()];
|
||||
if (!pindexStop) {
|
||||
stop_height = request.params[1].get_int();
|
||||
if (*stop_height < 0 || !tip_height || *stop_height > *tip_height) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
|
||||
}
|
||||
else if (pindexStop->nHeight < pindexStart->nHeight) {
|
||||
else if (*stop_height < start_height) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater than start_height");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can't rescan beyond non-pruned blocks, stop and throw an error
|
||||
if (fPruneMode) {
|
||||
auto locked_chain = pwallet->chain().lock();
|
||||
CBlockIndex *block = pindexStop ? pindexStop : pChainTip;
|
||||
while (block && block->nHeight >= pindexStart->nHeight) {
|
||||
if (!(block->nStatus & BLOCK_HAVE_DATA)) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
|
||||
// We can't rescan beyond non-pruned blocks, stop and throw an error
|
||||
if (locked_chain->findPruned(start_height, stop_height)) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
|
||||
}
|
||||
|
||||
if (tip_height) {
|
||||
start_block = locked_chain->getBlockHash(start_height);
|
||||
if (stop_height) {
|
||||
stop_block = locked_chain->getBlockHash(*stop_height);
|
||||
}
|
||||
block = block->pprev;
|
||||
}
|
||||
}
|
||||
|
||||
const CBlockIndex *failed_block, *stopBlock;
|
||||
CWallet::ScanResult result =
|
||||
pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, failed_block, stopBlock, true);
|
||||
switch (result) {
|
||||
pwallet->ScanForWalletTransactions(start_block, stop_block, reserver, true /* fUpdate */);
|
||||
switch (result.status) {
|
||||
case CWallet::ScanResult::SUCCESS:
|
||||
break; // stopBlock set by ScanForWalletTransactions
|
||||
break;
|
||||
case CWallet::ScanResult::FAILURE:
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
|
||||
case CWallet::ScanResult::USER_ABORT:
|
||||
|
@ -3438,8 +3436,8 @@ UniValue rescanblockchain(const JSONRPCRequest& request)
|
|||
// no default case, so the compiler can warn about missing cases
|
||||
}
|
||||
UniValue response(UniValue::VOBJ);
|
||||
response.pushKV("start_height", pindexStart->nHeight);
|
||||
response.pushKV("stop_height", stopBlock->nHeight);
|
||||
response.pushKV("start_height", start_height);
|
||||
response.pushKV("stop_height", result.stop_height ? *result.stop_height : UniValue());
|
||||
return response;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,6 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
|
|||
auto chain = interfaces::MakeChain();
|
||||
|
||||
// Cap last block file size, and mine new block in a new block file.
|
||||
const CBlockIndex* const null_block = nullptr;
|
||||
CBlockIndex* oldTip = chainActive.Tip();
|
||||
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
|
||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
|
@ -53,10 +52,11 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
|
|||
AddKey(wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(&wallet);
|
||||
reserver.reserve();
|
||||
const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1;
|
||||
BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(nullptr, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS);
|
||||
BOOST_CHECK_EQUAL(failed_block, null_block);
|
||||
BOOST_CHECK_EQUAL(stop_block, null_block);
|
||||
CWallet::ScanResult result = wallet.ScanForWalletTransactions({} /* start_block */, {} /* stop_block */, reserver, false /* update */);
|
||||
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
|
||||
BOOST_CHECK(result.failed_block.IsNull());
|
||||
BOOST_CHECK(result.stop_block.IsNull());
|
||||
BOOST_CHECK(!result.stop_height);
|
||||
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0);
|
||||
}
|
||||
|
||||
|
@ -67,10 +67,11 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
|
|||
AddKey(wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(&wallet);
|
||||
reserver.reserve();
|
||||
const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1;
|
||||
BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS);
|
||||
BOOST_CHECK_EQUAL(failed_block, null_block);
|
||||
BOOST_CHECK_EQUAL(stop_block, newTip);
|
||||
CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */);
|
||||
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
|
||||
BOOST_CHECK(result.failed_block.IsNull());
|
||||
BOOST_CHECK_EQUAL(result.stop_block, newTip->GetBlockHash());
|
||||
BOOST_CHECK_EQUAL(*result.stop_height, newTip->nHeight);
|
||||
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN);
|
||||
}
|
||||
|
||||
|
@ -85,10 +86,11 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
|
|||
AddKey(wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(&wallet);
|
||||
reserver.reserve();
|
||||
const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1;
|
||||
BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE);
|
||||
BOOST_CHECK_EQUAL(failed_block, oldTip);
|
||||
BOOST_CHECK_EQUAL(stop_block, newTip);
|
||||
CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */);
|
||||
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
|
||||
BOOST_CHECK_EQUAL(result.failed_block, oldTip->GetBlockHash());
|
||||
BOOST_CHECK_EQUAL(result.stop_block, newTip->GetBlockHash());
|
||||
BOOST_CHECK_EQUAL(*result.stop_height, newTip->nHeight);
|
||||
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN);
|
||||
}
|
||||
|
||||
|
@ -102,10 +104,11 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
|
|||
AddKey(wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(&wallet);
|
||||
reserver.reserve();
|
||||
const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1;
|
||||
BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE);
|
||||
BOOST_CHECK_EQUAL(failed_block, newTip);
|
||||
BOOST_CHECK_EQUAL(stop_block, null_block);
|
||||
CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */);
|
||||
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
|
||||
BOOST_CHECK_EQUAL(result.failed_block, newTip->GetBlockHash());
|
||||
BOOST_CHECK(result.stop_block.IsNull());
|
||||
BOOST_CHECK(!result.stop_height);
|
||||
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0);
|
||||
}
|
||||
}
|
||||
|
@ -340,11 +343,11 @@ public:
|
|||
AddKey(*wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(wallet.get());
|
||||
reserver.reserve();
|
||||
const CBlockIndex* const null_block = nullptr;
|
||||
const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1;
|
||||
BOOST_CHECK_EQUAL(wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS);
|
||||
BOOST_CHECK_EQUAL(stop_block, chainActive.Tip());
|
||||
BOOST_CHECK_EQUAL(failed_block, null_block);
|
||||
CWallet::ScanResult result = wallet->ScanForWalletTransactions(chainActive.Genesis()->GetBlockHash(), {} /* stop_block */, reserver, false /* update */);
|
||||
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
|
||||
BOOST_CHECK_EQUAL(result.stop_block, chainActive.Tip()->GetBlockHash());
|
||||
BOOST_CHECK_EQUAL(*result.stop_height, chainActive.Height());
|
||||
BOOST_CHECK(result.failed_block.IsNull());
|
||||
}
|
||||
|
||||
~ListCoinsTestingSetup()
|
||||
|
|
|
@ -1117,8 +1117,8 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
|
|||
}
|
||||
}
|
||||
|
||||
void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pindex, int posInBlock, bool update_tx) {
|
||||
if (!AddToWalletIfInvolvingMe(ptx, pindex->GetBlockHash(), 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
|
||||
|
@ -1130,7 +1130,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pin
|
|||
void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) {
|
||||
auto locked_chain = chain().lock();
|
||||
LOCK(cs_wallet);
|
||||
SyncTransaction(ptx);
|
||||
SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
|
||||
|
||||
auto it = mapWallet.find(ptx->GetHash());
|
||||
if (it != mapWallet.end()) {
|
||||
|
@ -1158,11 +1158,11 @@ void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const
|
|||
// the notification that the conflicted transaction was evicted.
|
||||
|
||||
for (const CTransactionRef& ptx : vtxConflicted) {
|
||||
SyncTransaction(ptx);
|
||||
SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
|
||||
TransactionRemovedFromMempool(ptx);
|
||||
}
|
||||
for (size_t i = 0; i < pblock->vtx.size(); i++) {
|
||||
SyncTransaction(pblock->vtx[i], pindex, i);
|
||||
SyncTransaction(pblock->vtx[i], pindex->GetBlockHash(), i);
|
||||
TransactionRemovedFromMempool(pblock->vtx[i]);
|
||||
}
|
||||
|
||||
|
@ -1174,7 +1174,7 @@ void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) {
|
|||
LOCK(cs_wallet);
|
||||
|
||||
for (const CTransactionRef& ptx : pblock->vtx) {
|
||||
SyncTransaction(ptx);
|
||||
SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1587,132 +1587,143 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
|
|||
// Find starting block. May be null if nCreateTime is greater than the
|
||||
// highest blockchain timestamp, in which case there is nothing that needs
|
||||
// to be scanned.
|
||||
CBlockIndex* startBlock = nullptr;
|
||||
uint256 start_block;
|
||||
{
|
||||
auto locked_chain = chain().lock();
|
||||
startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW);
|
||||
WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
|
||||
const Optional<int> start_height = locked_chain->findFirstBlockWithTime(startTime - TIMESTAMP_WINDOW, &start_block);
|
||||
const Optional<int> tip_height = locked_chain->getHeight();
|
||||
WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, tip_height && start_height ? *tip_height - *start_height + 1 : 0);
|
||||
}
|
||||
|
||||
if (startBlock) {
|
||||
const CBlockIndex *failedBlock, *stop_block;
|
||||
if (!start_block.IsNull()) {
|
||||
// TODO: this should take into account failure by ScanResult::USER_ABORT
|
||||
if (ScanResult::FAILURE == ScanForWalletTransactions(startBlock, nullptr, reserver, failedBlock, stop_block, update)) {
|
||||
return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1;
|
||||
ScanResult result = ScanForWalletTransactions(start_block, {} /* stop_block */, reserver, update);
|
||||
if (result.status == ScanResult::FAILURE) {
|
||||
int64_t time_max;
|
||||
if (!chain().findBlock(result.failed_block, nullptr /* block */, nullptr /* time */, &time_max)) {
|
||||
throw std::logic_error("ScanForWalletTransactions returned invalid block hash");
|
||||
}
|
||||
return time_max + TIMESTAMP_WINDOW + 1;
|
||||
}
|
||||
}
|
||||
return startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the block chain (starting in pindexStart) for transactions
|
||||
* Scan the block chain (starting in start_block) for transactions
|
||||
* from or to us. If fUpdate is true, found transactions that already
|
||||
* exist in the wallet will be updated.
|
||||
*
|
||||
* @param[in] pindexStop if not a nullptr, the scan will stop at this block-index
|
||||
* @param[out] failed_block if FAILURE is returned, the most recent block
|
||||
* that could not be scanned, otherwise nullptr
|
||||
* @param[out] stop_block the most recent block that could be scanned,
|
||||
* otherwise nullptr if no block could be scanned
|
||||
* @param[in] start_block if not null, the scan will start at this block instead
|
||||
* of the genesis block
|
||||
* @param[in] stop_block if not null, the scan will stop at this block instead
|
||||
* of the chain tip
|
||||
*
|
||||
* @return ScanResult indicating success or failure of the scan. SUCCESS if
|
||||
* scan was successful. FAILURE if a complete rescan was not possible (due to
|
||||
* pruning or corruption). USER_ABORT if the rescan was aborted before it
|
||||
* could complete.
|
||||
*
|
||||
* @pre Caller needs to make sure pindexStop (and the optional pindexStart) are on
|
||||
* @pre Caller needs to make sure start_block (and the optional stop_block) are on
|
||||
* the main chain after to the addition of any new keys you want to detect
|
||||
* transactions for.
|
||||
*/
|
||||
CWallet::ScanResult CWallet::ScanForWalletTransactions(const CBlockIndex* const pindexStart, const CBlockIndex* const pindexStop, const WalletRescanReserver& reserver, const CBlockIndex*& failed_block, const CBlockIndex*& stop_block, bool fUpdate)
|
||||
CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, const uint256& stop_block, const WalletRescanReserver& reserver, bool fUpdate)
|
||||
{
|
||||
int64_t nNow = GetTime();
|
||||
const CChainParams& chainParams = Params();
|
||||
|
||||
assert(reserver.isReserved());
|
||||
if (pindexStop) {
|
||||
assert(pindexStop->nHeight >= pindexStart->nHeight);
|
||||
}
|
||||
|
||||
const CBlockIndex* pindex = pindexStart;
|
||||
failed_block = nullptr;
|
||||
stop_block = nullptr;
|
||||
uint256 block_hash = start_block;
|
||||
ScanResult result;
|
||||
|
||||
if (pindex) WalletLogPrintf("Rescan started from block %d...\n", pindex->nHeight);
|
||||
WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString());
|
||||
|
||||
{
|
||||
fAbortRescan = false;
|
||||
ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
|
||||
CBlockIndex* tip = nullptr;
|
||||
uint256 tip_hash;
|
||||
Optional<int> block_height;
|
||||
double progress_begin;
|
||||
double progress_end;
|
||||
{
|
||||
auto locked_chain = chain().lock();
|
||||
progress_begin = GuessVerificationProgress(chainParams.TxData(), pindex);
|
||||
if (pindexStop == nullptr) {
|
||||
tip = chainActive.Tip();
|
||||
progress_end = GuessVerificationProgress(chainParams.TxData(), tip);
|
||||
} else {
|
||||
progress_end = GuessVerificationProgress(chainParams.TxData(), pindexStop);
|
||||
if (Optional<int> tip_height = locked_chain->getHeight()) {
|
||||
tip_hash = locked_chain->getBlockHash(*tip_height);
|
||||
}
|
||||
block_height = locked_chain->getBlockHeight(block_hash);
|
||||
progress_begin = chain().guessVerificationProgress(block_hash);
|
||||
progress_end = chain().guessVerificationProgress(stop_block.IsNull() ? tip_hash : stop_block);
|
||||
}
|
||||
double progress_current = progress_begin;
|
||||
while (pindex && !fAbortRescan && !ShutdownRequested()) {
|
||||
if (pindex->nHeight % 100 == 0 && progress_end - progress_begin > 0.0) {
|
||||
while (block_height && !fAbortRescan && !ShutdownRequested()) {
|
||||
if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) {
|
||||
ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)((progress_current - progress_begin) / (progress_end - progress_begin) * 100))));
|
||||
}
|
||||
if (GetTime() >= nNow + 60) {
|
||||
nNow = GetTime();
|
||||
WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, progress_current);
|
||||
WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", *block_height, progress_current);
|
||||
}
|
||||
|
||||
CBlock block;
|
||||
if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
|
||||
if (chain().findBlock(block_hash, &block) && !block.IsNull()) {
|
||||
auto locked_chain = chain().lock();
|
||||
LOCK(cs_wallet);
|
||||
if (pindex && !chainActive.Contains(pindex)) {
|
||||
if (!locked_chain->getBlockHeight(block_hash)) {
|
||||
// Abort scan if current block is no longer active, to prevent
|
||||
// marking transactions as coming from the wrong block.
|
||||
failed_block = pindex;
|
||||
// TODO: This should return success instead of failure, see
|
||||
// https://github.com/bitcoin/bitcoin/pull/14711#issuecomment-458342518
|
||||
result.failed_block = block_hash;
|
||||
result.status = ScanResult::FAILURE;
|
||||
break;
|
||||
}
|
||||
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
|
||||
SyncTransaction(block.vtx[posInBlock], pindex, posInBlock, fUpdate);
|
||||
SyncTransaction(block.vtx[posInBlock], block_hash, posInBlock, fUpdate);
|
||||
}
|
||||
// scan succeeded, record block as most recent successfully scanned
|
||||
stop_block = pindex;
|
||||
result.stop_block = block_hash;
|
||||
result.stop_height = *block_height;
|
||||
} else {
|
||||
// could not scan block, keep scanning but record this block as the most recent failure
|
||||
failed_block = pindex;
|
||||
result.failed_block = block_hash;
|
||||
result.status = ScanResult::FAILURE;
|
||||
}
|
||||
if (pindex == pindexStop) {
|
||||
if (block_hash == stop_block) {
|
||||
break;
|
||||
}
|
||||
{
|
||||
auto locked_chain = chain().lock();
|
||||
pindex = chainActive.Next(pindex);
|
||||
progress_current = GuessVerificationProgress(chainParams.TxData(), pindex);
|
||||
if (pindexStop == nullptr && tip != chainActive.Tip()) {
|
||||
tip = chainActive.Tip();
|
||||
Optional<int> tip_height = locked_chain->getHeight();
|
||||
if (!tip_height || *tip_height <= block_height || !locked_chain->getBlockHeight(block_hash)) {
|
||||
// break successfully when rescan has reached the tip, or
|
||||
// previous block is no longer on the chain due to a reorg
|
||||
break;
|
||||
}
|
||||
|
||||
// increment block and verification progress
|
||||
block_hash = locked_chain->getBlockHash(++*block_height);
|
||||
progress_current = chain().guessVerificationProgress(block_hash);
|
||||
|
||||
// handle updated tip hash
|
||||
const uint256 prev_tip_hash = tip_hash;
|
||||
tip_hash = locked_chain->getBlockHash(*tip_height);
|
||||
if (stop_block.IsNull() && prev_tip_hash != tip_hash) {
|
||||
// in case the tip has changed, update progress max
|
||||
progress_end = GuessVerificationProgress(chainParams.TxData(), tip);
|
||||
progress_end = chain().guessVerificationProgress(tip_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 100); // hide progress dialog in GUI
|
||||
if (pindex && fAbortRescan) {
|
||||
WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, progress_current);
|
||||
return ScanResult::USER_ABORT;
|
||||
} else if (pindex && ShutdownRequested()) {
|
||||
WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", pindex->nHeight, progress_current);
|
||||
return ScanResult::USER_ABORT;
|
||||
if (block_height && fAbortRescan) {
|
||||
WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", block_height.value_or(0), progress_current);
|
||||
result.status = ScanResult::USER_ABORT;
|
||||
} else if (block_height && ShutdownRequested()) {
|
||||
WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", block_height.value_or(0), progress_current);
|
||||
result.status = ScanResult::USER_ABORT;
|
||||
}
|
||||
}
|
||||
if (failed_block) {
|
||||
return ScanResult::FAILURE;
|
||||
} else {
|
||||
return ScanResult::SUCCESS;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void CWallet::ReacceptWalletTransactions()
|
||||
|
@ -4194,8 +4205,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
|
|||
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(pindexRescan->GetBlockHash(), {} /* stop block */, reserver, true /* update */).status)) {
|
||||
InitError(_("Failed to rescan the wallet during initialization"));
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -678,8 +678,8 @@ private:
|
|||
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
/* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions.
|
||||
* Should be called with pindexBlock and posInBlock if this is for a transaction that is included in a block. */
|
||||
void SyncTransaction(const CTransactionRef& tx, const CBlockIndex *pindex = nullptr, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
* Should be called with non-zero block_hash and posInBlock if this is for a transaction that is included in a block. */
|
||||
void SyncTransaction(const CTransactionRef& tx, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
/* the HD chain data model (external chain counters) */
|
||||
CHDChain hdChain;
|
||||
|
@ -909,12 +909,22 @@ public:
|
|||
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override;
|
||||
int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
|
||||
|
||||
enum class ScanResult {
|
||||
SUCCESS,
|
||||
FAILURE,
|
||||
USER_ABORT
|
||||
struct ScanResult {
|
||||
enum { SUCCESS, FAILURE, USER_ABORT } status = SUCCESS;
|
||||
|
||||
//! Hash and height of most recent block that was successfully scanned.
|
||||
//! Unset if no blocks were scanned due to read errors or the chain
|
||||
//! being empty.
|
||||
uint256 stop_block;
|
||||
Optional<int> stop_height;
|
||||
|
||||
//! Height of the most recent block that could not be scanned due to
|
||||
//! read errors or pruning. Will be set if status is FAILURE, unset if
|
||||
//! status is SUCCESS, and may or may not be set if status is
|
||||
//! USER_ABORT.
|
||||
uint256 failed_block;
|
||||
};
|
||||
ScanResult ScanForWalletTransactions(const CBlockIndex* const pindexStart, const CBlockIndex* const pindexStop, const WalletRescanReserver& reserver, const CBlockIndex*& failed_block, const CBlockIndex*& stop_block, bool fUpdate = false);
|
||||
ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate);
|
||||
void TransactionRemovedFromMempool(const CTransactionRef &ptx) override;
|
||||
void ReacceptWalletTransactions();
|
||||
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
|
Loading…
Reference in a new issue