Skip to content

Commit 2a8e35a

Browse files
committed
Fix importwallet edge case rescan bug
Start importwallet rescans at the first block with timestamp greater or equal to the wallet birthday instead of the last block with timestamp less or equal. This fixes an edge case bug where importwallet could fail to start the rescan early enough if there are blocks with decreasing timestamps or multiple blocks with the same timestamp.
1 parent 41987aa commit 2a8e35a

File tree

2 files changed

+65
-6
lines changed

2 files changed

+65
-6
lines changed

src/wallet/rpcdump.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -536,14 +536,11 @@ UniValue importwallet(const JSONRPCRequest& request)
536536
}
537537
file.close();
538538
pwallet->ShowProgress("", 100); // hide progress dialog in GUI
539-
540-
CBlockIndex *pindex = chainActive.Tip();
541-
while (pindex && pindex->pprev && pindex->GetBlockTime() > nTimeBegin - TIMESTAMP_WINDOW)
542-
pindex = pindex->pprev;
543-
544539
pwallet->UpdateTimeFirstKey(nTimeBegin);
545540

546-
LogPrintf("Rescanning last %i blocks\n", chainActive.Height() - pindex->nHeight + 1);
541+
CBlockIndex *pindex = chainActive.FindEarliestAtLeast(nTimeBegin - TIMESTAMP_WINDOW);
542+
543+
LogPrintf("Rescanning last %i blocks\n", pindex ? chainActive.Height() - pindex->nHeight + 1 : 0);
547544
pwallet->ScanForWalletTransactions(pindex);
548545
pwallet->MarkDirty();
549546

src/wallet/test/wallet_tests.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include <univalue.h>
2020

2121
extern UniValue importmulti(const JSONRPCRequest& request);
22+
extern UniValue dumpwallet(const JSONRPCRequest& request);
23+
extern UniValue importwallet(const JSONRPCRequest& request);
2224

2325
// how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles
2426
#define RUN_TESTS 100
@@ -437,6 +439,66 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
437439
}
438440
}
439441

442+
// Verify importwallet RPC starts rescan at earliest block with timestamp
443+
// greater or equal than key birthday. Previously there was a bug where
444+
// importwallet RPC would start the scan at the latest block with timestamp less
445+
// than or equal to key birthday.
446+
BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
447+
{
448+
CWallet *pwalletMainBackup = ::pwalletMain;
449+
LOCK(cs_main);
450+
451+
// Create two blocks with same timestamp to verify that importwallet rescan
452+
// will pick up both blocks, not just the first.
453+
const int64_t BLOCK_TIME = chainActive.Tip()->GetBlockTimeMax() + 5;
454+
SetMockTime(BLOCK_TIME);
455+
coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
456+
coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
457+
458+
// Set key birthday to block time increased by the timestamp window, so
459+
// rescan will start at the block time.
460+
const int64_t KEY_TIME = BLOCK_TIME + TIMESTAMP_WINDOW;
461+
SetMockTime(KEY_TIME);
462+
coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
463+
464+
// Import key into wallet and call dumpwallet to create backup file.
465+
{
466+
CWallet wallet;
467+
LOCK(wallet.cs_wallet);
468+
wallet.mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
469+
wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
470+
471+
JSONRPCRequest request;
472+
request.params.setArray();
473+
request.params.push_back("wallet.backup");
474+
::pwalletMain = &wallet;
475+
::dumpwallet(request);
476+
}
477+
478+
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
479+
// were scanned, and no prior blocks were scanned.
480+
{
481+
CWallet wallet;
482+
483+
JSONRPCRequest request;
484+
request.params.setArray();
485+
request.params.push_back("wallet.backup");
486+
::pwalletMain = &wallet;
487+
::importwallet(request);
488+
489+
BOOST_CHECK_EQUAL(wallet.mapWallet.size(), 3);
490+
BOOST_CHECK_EQUAL(coinbaseTxns.size(), 103);
491+
for (size_t i = 0; i < coinbaseTxns.size(); ++i) {
492+
bool found = wallet.GetWalletTx(coinbaseTxns[i].GetHash());
493+
bool expected = i >= 100;
494+
BOOST_CHECK_EQUAL(found, expected);
495+
}
496+
}
497+
498+
SetMockTime(0);
499+
::pwalletMain = pwalletMainBackup;
500+
}
501+
440502
// Check that GetImmatureCredit() returns a newly calculated value instead of
441503
// the cached value after a MarkDirty() call.
442504
//

0 commit comments

Comments
 (0)