Skip to content

Commit 93ebbc8

Browse files
jonasschnelliPastaPastaPasta
authored andcommitted
Merge #7061: [Wallet] Add RPC call "rescanblockchain <startheight> <stopheight>"
7a91ceb [QA] Add RPC based rescan test (Jonas Schnelli) c77170f [Wallet] add rescanblockchain <start_height> <stop_height> RPC command (Jonas Schnelli) Pull request description: A RPC rescan command is much more flexible for the following reasons: * You can define the start and end-height * It can be called during runtime * It can work in multiwallet environment Tree-SHA512: df67177bad6ad1d08e5a621f095564524fa3eb87204c2048ef7265e77013e4b1b29f991708f807002329a507a254f35e79a4ed28a2d18d4b3da7a75d57ce0ea5
1 parent 1e2ab76 commit 93ebbc8

File tree

7 files changed

+117
-11
lines changed

7 files changed

+117
-11
lines changed

src/qt/test/wallettests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ void TestSendCoins()
9494
wallet.SetAddressBook(test.coinbaseKey.GetPubKey().GetID(), "", "receive");
9595
wallet.AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey());
9696
}
97-
wallet.ScanForWalletTransactions(chainActive.Genesis(), true);
97+
wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, true);
9898
wallet.SetBroadcastTransactions(true);
9999

100100
// Create widgets for sending coins and listing transactions.

src/rpc/client.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
180180
{ "echojson", 7, "arg7" },
181181
{ "echojson", 8, "arg8" },
182182
{ "echojson", 9, "arg9" },
183+
{ "rescanblockchain", 0, "start_height"},
184+
{ "rescanblockchain", 1, "stop_height"},
183185
{ "stop", 0, "wait" },
184186
};
185187

src/wallet/rpcwallet.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3150,6 +3150,81 @@ UniValue generate(const JSONRPCRequest& request)
31503150
}
31513151
#endif //ENABLE_MINING
31523152

3153+
UniValue rescanblockchain(const JSONRPCRequest& request)
3154+
{
3155+
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
3156+
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
3157+
return NullUniValue;
3158+
}
3159+
3160+
if (request.fHelp || request.params.size() > 2) {
3161+
throw std::runtime_error(
3162+
"rescanblockchain (\"start_height\") (\"stop_height\")\n"
3163+
"\nRescan the local blockchain for wallet related transactions.\n"
3164+
"\nArguments:\n"
3165+
"1. \"start_height\" (numeric, optional) block height where the rescan should start\n"
3166+
"2. \"stop_height\" (numeric, optional) the last block height that should be scanned\n"
3167+
"\nResult:\n"
3168+
"{\n"
3169+
" \"start_height\" (numeric) The block height where the rescan has started. If omitted, rescan started from the genesis block.\n"
3170+
" \"stop_height\" (numeric) The height of the last rescanned block. If omitted, rescan stopped at the chain tip.\n"
3171+
"}\n"
3172+
"\nExamples:\n"
3173+
+ HelpExampleCli("rescanblockchain", "100000 120000")
3174+
+ HelpExampleRpc("rescanblockchain", "100000 120000")
3175+
);
3176+
}
3177+
3178+
LOCK2(cs_main, pwallet->cs_wallet);
3179+
3180+
CBlockIndex *pindexStart = chainActive.Genesis();
3181+
CBlockIndex *pindexStop = nullptr;
3182+
if (!request.params[0].isNull()) {
3183+
pindexStart = chainActive[request.params[0].get_int()];
3184+
if (!pindexStart) {
3185+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
3186+
}
3187+
}
3188+
3189+
if (!request.params[1].isNull()) {
3190+
pindexStop = chainActive[request.params[1].get_int()];
3191+
if (!pindexStop) {
3192+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
3193+
}
3194+
else if (pindexStop->nHeight < pindexStart->nHeight) {
3195+
throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height");
3196+
}
3197+
}
3198+
3199+
// We can't rescan beyond non-pruned blocks, stop and throw an error
3200+
if (fPruneMode) {
3201+
CBlockIndex *block = pindexStop ? pindexStop : chainActive.Tip();
3202+
while (block && block->nHeight >= pindexStart->nHeight) {
3203+
if (!(block->nStatus & BLOCK_HAVE_DATA)) {
3204+
throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
3205+
}
3206+
block = block->pprev;
3207+
}
3208+
}
3209+
3210+
CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, true);
3211+
if (!stopBlock) {
3212+
if (pwallet->IsAbortingRescan()) {
3213+
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
3214+
}
3215+
// if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex
3216+
stopBlock = pindexStop ? pindexStop : chainActive.Tip();
3217+
}
3218+
else {
3219+
throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
3220+
}
3221+
3222+
UniValue response(UniValue::VOBJ);
3223+
response.pushKV("start_height", pindexStart->nHeight);
3224+
response.pushKV("stop_height", stopBlock->nHeight);
3225+
return response;
3226+
}
3227+
31533228
extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp
31543229
extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
31553230
extern UniValue importprivkey(const JSONRPCRequest& request);
@@ -3160,6 +3235,7 @@ extern UniValue importwallet(const JSONRPCRequest& request);
31603235
extern UniValue importprunedfunds(const JSONRPCRequest& request);
31613236
extern UniValue removeprunedfunds(const JSONRPCRequest& request);
31623237
extern UniValue importmulti(const JSONRPCRequest& request);
3238+
extern UniValue rescanblockchain(const JSONRPCRequest& request);
31633239

31643240
extern UniValue dumphdinfo(const JSONRPCRequest& request);
31653241
extern UniValue importelectrumwallet(const JSONRPCRequest& request);
@@ -3218,6 +3294,7 @@ static const CRPCCommand commands[] =
32183294
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
32193295
{ "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout","mixingonly"} },
32203296
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
3297+
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
32213298

32223299
#if ENABLE_MINER
32233300
{ "generating", "generate", &generate, {"nblocks","maxtries"} },

src/wallet/test/wallet_tests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
386386
{
387387
CWallet wallet;
388388
AddKey(wallet, coinbaseKey);
389-
BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip));
389+
BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr));
390390
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 1000 * COIN);
391391
}
392392

@@ -399,7 +399,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
399399
{
400400
CWallet wallet;
401401
AddKey(wallet, coinbaseKey);
402-
BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip));
402+
BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr));
403403
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 500 * COIN);
404404
}
405405

@@ -604,7 +604,7 @@ class ListCoinsTestingSetup : public TestChain100Setup
604604
bool firstRun;
605605
wallet->LoadWallet(firstRun);
606606
AddKey(*wallet, coinbaseKey);
607-
wallet->ScanForWalletTransactions(chainActive.Genesis());
607+
wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr);
608608
}
609609

610610
~ListCoinsTestingSetup()

src/wallet/wallet.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1928,7 +1928,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, bool update)
19281928
LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
19291929

19301930
if (startBlock) {
1931-
const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, update);
1931+
const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, update);
19321932
if (failedBlock) {
19331933
return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1;
19341934
}
@@ -1944,12 +1944,19 @@ int64_t CWallet::RescanFromTime(int64_t startTime, bool update)
19441944
* Returns null if scan was successful. Otherwise, if a complete rescan was not
19451945
* possible (due to pruning or corruption), returns pointer to the most recent
19461946
* block that could not be scanned.
1947+
*
1948+
* If pindexStop is not a nullptr, the scan will stop at the block-index
1949+
* defined by pindexStop
19471950
*/
1948-
CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
1951+
CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate)
19491952
{
19501953
int64_t nNow = GetTime();
19511954
const CChainParams& chainParams = Params();
19521955

1956+
if (pindexStop) {
1957+
assert(pindexStop->nHeight >= pindexStart->nHeight);
1958+
}
1959+
19531960
CBlockIndex* pindex = pindexStart;
19541961
CBlockIndex* ret = nullptr;
19551962
{
@@ -1977,6 +1984,9 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool f
19771984
} else {
19781985
ret = pindex;
19791986
}
1987+
if (pindex == pindexStop) {
1988+
break;
1989+
}
19801990
pindex = chainActive.Next(pindex);
19811991
}
19821992
if (pindex && fAbortRescan) {
@@ -5093,7 +5103,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
50935103
}
50945104

50955105
nStart = GetTimeMillis();
5096-
walletInstance->ScanForWalletTransactions(pindexRescan, true);
5106+
walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, true);
50975107
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);
50985108
walletInstance->SetBestChain(chainActive.GetLocator());
50995109
walletInstance->dbw->IncrementUpdateCounter();

src/wallet/wallet.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1025,7 +1025,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
10251025
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected) override;
10261026
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
10271027
int64_t RescanFromTime(int64_t startTime, bool update);
1028-
CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
1028+
CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate = false);
10291029
void ReacceptWalletTransactions();
10301030
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override;
10311031
// ResendWalletTransactionsBefore may only be called if fBroadcastTransactions!

test/functional/wallet-hd.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import sys
88
import shutil
9+
import os
910

1011
from test_framework.test_framework import BitcoinTestFramework
1112
from test_framework.util import *
@@ -73,11 +74,11 @@ def run_test (self):
7374
self.stop_node(1)
7475
# we need to delete the complete regtest directory
7576
# otherwise node1 would auto-recover all funds in flag the keypool keys as used
76-
shutil.rmtree(tmpdir + "/node1/regtest/blocks")
77-
shutil.rmtree(tmpdir + "/node1/regtest/chainstate")
77+
shutil.rmtree(os.path.join(tmpdir, "node1/regtest/blocks"))
78+
shutil.rmtree(os.path.join(tmpdir, "node1/regtest/chainstate"))
7879
shutil.rmtree(tmpdir + "/node1/regtest/evodb")
7980
shutil.rmtree(tmpdir + "/node1/regtest/llmq")
80-
shutil.copyfile(tmpdir + "/hd.bak", tmpdir + "/node1/regtest/wallet.dat")
81+
shutil.copyfile(os.path.join(tmpdir, "hd.bak"), os.path.join(tmpdir, "node1/regtest/wallet.dat"))
8182
self.start_node(1)
8283

8384
# Assert that derivation is deterministic
@@ -96,6 +97,22 @@ def run_test (self):
9697
self.start_node(1, extra_args=self.extra_args[1] + ['-rescan'])
9798
assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1)
9899

100+
# Try a RPC based rescan
101+
self.stop_node(1)
102+
shutil.rmtree(os.path.join(tmpdir, "node1/regtest/blocks"))
103+
shutil.rmtree(os.path.join(tmpdir, "node1/regtest/chainstate"))
104+
shutil.copyfile(os.path.join(tmpdir, "hd.bak"), os.path.join(tmpdir, "node1/regtest/wallet.dat"))
105+
self.start_node(1, extra_args=self.extra_args[1])
106+
connect_nodes_bi(self.nodes, 0, 1)
107+
self.sync_all()
108+
out = self.nodes[1].rescanblockchain(0, 1)
109+
assert_equal(out['start_height'], 0)
110+
assert_equal(out['stop_height'], 1)
111+
out = self.nodes[1].rescanblockchain()
112+
assert_equal(out['start_height'], 0)
113+
assert_equal(out['stop_height'], self.nodes[1].getblockcount())
114+
assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1)
115+
99116
# send a tx and make sure its using the internal chain for the changeoutput
100117
txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1)
101118
outs = self.nodes[1].decoderawtransaction(self.nodes[1].gettransaction(txid)['hex'])['vout']

0 commit comments

Comments
 (0)