mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 04:22:55 +01:00
Merge #7061: [Wallet] Add RPC call "rescanblockchain <startheight> <stopheight>"
7a91ceb5e
[QA] Add RPC based rescan test (Jonas Schnelli)c77170fbd
[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
This commit is contained in:
commit
8c2de827e9
@ -164,7 +164,7 @@ void TestGUI()
|
|||||||
wallet.SetAddressBook(test.coinbaseKey.GetPubKey().GetID(), "", "receive");
|
wallet.SetAddressBook(test.coinbaseKey.GetPubKey().GetID(), "", "receive");
|
||||||
wallet.AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey());
|
wallet.AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey());
|
||||||
}
|
}
|
||||||
wallet.ScanForWalletTransactions(chainActive.Genesis(), true);
|
wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, true);
|
||||||
wallet.SetBroadcastTransactions(true);
|
wallet.SetBroadcastTransactions(true);
|
||||||
|
|
||||||
// Create widgets for sending coins and listing transactions.
|
// Create widgets for sending coins and listing transactions.
|
||||||
|
@ -141,6 +141,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||||||
{ "echojson", 7, "arg7" },
|
{ "echojson", 7, "arg7" },
|
||||||
{ "echojson", 8, "arg8" },
|
{ "echojson", 8, "arg8" },
|
||||||
{ "echojson", 9, "arg9" },
|
{ "echojson", 9, "arg9" },
|
||||||
|
{ "rescanblockchain", 0, "start_height"},
|
||||||
|
{ "rescanblockchain", 1, "stop_height"},
|
||||||
};
|
};
|
||||||
|
|
||||||
class CRPCConvertTable
|
class CRPCConvertTable
|
||||||
|
@ -3212,6 +3212,81 @@ UniValue generate(const JSONRPCRequest& request)
|
|||||||
return generateBlocks(coinbase_script, num_generate, max_tries, true);
|
return generateBlocks(coinbase_script, num_generate, max_tries, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UniValue rescanblockchain(const JSONRPCRequest& request)
|
||||||
|
{
|
||||||
|
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
|
||||||
|
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
|
||||||
|
return NullUniValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.fHelp || request.params.size() > 2) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"rescanblockchain (\"start_height\") (\"stop_height\")\n"
|
||||||
|
"\nRescan the local blockchain for wallet related transactions.\n"
|
||||||
|
"\nArguments:\n"
|
||||||
|
"1. \"start_height\" (numeric, optional) block height where the rescan should start\n"
|
||||||
|
"2. \"stop_height\" (numeric, optional) the last block height that should be scanned\n"
|
||||||
|
"\nResult:\n"
|
||||||
|
"{\n"
|
||||||
|
" \"start_height\" (numeric) The block height where the rescan has started. If omitted, rescan started from the genesis block.\n"
|
||||||
|
" \"stop_height\" (numeric) The height of the last rescanned block. If omitted, rescan stopped at the chain tip.\n"
|
||||||
|
"}\n"
|
||||||
|
"\nExamples:\n"
|
||||||
|
+ HelpExampleCli("rescanblockchain", "100000 120000")
|
||||||
|
+ HelpExampleRpc("rescanblockchain", "100000 120000")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCK2(cs_main, pwallet->cs_wallet);
|
||||||
|
|
||||||
|
CBlockIndex *pindexStart = chainActive.Genesis();
|
||||||
|
CBlockIndex *pindexStop = nullptr;
|
||||||
|
if (!request.params[0].isNull()) {
|
||||||
|
pindexStart = chainActive[request.params[0].get_int()];
|
||||||
|
if (!pindexStart) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request.params[1].isNull()) {
|
||||||
|
pindexStop = chainActive[request.params[1].get_int()];
|
||||||
|
if (!pindexStop) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
|
||||||
|
}
|
||||||
|
else if (pindexStop->nHeight < pindexStart->nHeight) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't rescan beyond non-pruned blocks, stop and throw an error
|
||||||
|
if (fPruneMode) {
|
||||||
|
CBlockIndex *block = pindexStop ? pindexStop : chainActive.Tip();
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
block = block->pprev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, true);
|
||||||
|
if (!stopBlock) {
|
||||||
|
if (pwallet->IsAbortingRescan()) {
|
||||||
|
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
|
||||||
|
}
|
||||||
|
// if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex
|
||||||
|
stopBlock = pindexStop ? pindexStop : chainActive.Tip();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue response(UniValue::VOBJ);
|
||||||
|
response.pushKV("start_height", pindexStart->nHeight);
|
||||||
|
response.pushKV("stop_height", stopBlock->nHeight);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp
|
extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp
|
||||||
extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
|
extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
|
||||||
extern UniValue importprivkey(const JSONRPCRequest& request);
|
extern UniValue importprivkey(const JSONRPCRequest& request);
|
||||||
@ -3222,6 +3297,7 @@ extern UniValue importwallet(const JSONRPCRequest& request);
|
|||||||
extern UniValue importprunedfunds(const JSONRPCRequest& request);
|
extern UniValue importprunedfunds(const JSONRPCRequest& request);
|
||||||
extern UniValue removeprunedfunds(const JSONRPCRequest& request);
|
extern UniValue removeprunedfunds(const JSONRPCRequest& request);
|
||||||
extern UniValue importmulti(const JSONRPCRequest& request);
|
extern UniValue importmulti(const JSONRPCRequest& request);
|
||||||
|
extern UniValue rescanblockchain(const JSONRPCRequest& request);
|
||||||
|
|
||||||
static const CRPCCommand commands[] =
|
static const CRPCCommand commands[] =
|
||||||
{ // category name actor (function) argNames
|
{ // category name actor (function) argNames
|
||||||
@ -3276,6 +3352,7 @@ static const CRPCCommand commands[] =
|
|||||||
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
|
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
|
||||||
{ "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} },
|
{ "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} },
|
||||||
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
|
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
|
||||||
|
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
|
||||||
|
|
||||||
{ "generating", "generate", &generate, {"nblocks","maxtries"} },
|
{ "generating", "generate", &generate, {"nblocks","maxtries"} },
|
||||||
};
|
};
|
||||||
|
@ -386,7 +386,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
|||||||
{
|
{
|
||||||
CWallet wallet;
|
CWallet wallet;
|
||||||
AddKey(wallet, coinbaseKey);
|
AddKey(wallet, coinbaseKey);
|
||||||
BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip));
|
BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr));
|
||||||
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN);
|
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +399,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
|||||||
{
|
{
|
||||||
CWallet wallet;
|
CWallet wallet;
|
||||||
AddKey(wallet, coinbaseKey);
|
AddKey(wallet, coinbaseKey);
|
||||||
BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip));
|
BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr));
|
||||||
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN);
|
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -604,7 +604,7 @@ public:
|
|||||||
bool firstRun;
|
bool firstRun;
|
||||||
wallet->LoadWallet(firstRun);
|
wallet->LoadWallet(firstRun);
|
||||||
AddKey(*wallet, coinbaseKey);
|
AddKey(*wallet, coinbaseKey);
|
||||||
wallet->ScanForWalletTransactions(chainActive.Genesis());
|
wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
~ListCoinsTestingSetup()
|
~ListCoinsTestingSetup()
|
||||||
|
@ -1568,7 +1568,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, bool update)
|
|||||||
LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
|
LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
|
||||||
|
|
||||||
if (startBlock) {
|
if (startBlock) {
|
||||||
const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, update);
|
const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, update);
|
||||||
if (failedBlock) {
|
if (failedBlock) {
|
||||||
return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1;
|
return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1;
|
||||||
}
|
}
|
||||||
@ -1584,12 +1584,19 @@ int64_t CWallet::RescanFromTime(int64_t startTime, bool update)
|
|||||||
* Returns null if scan was successful. Otherwise, if a complete rescan was not
|
* Returns null if scan was successful. Otherwise, if a complete rescan was not
|
||||||
* possible (due to pruning or corruption), returns pointer to the most recent
|
* possible (due to pruning or corruption), returns pointer to the most recent
|
||||||
* block that could not be scanned.
|
* block that could not be scanned.
|
||||||
|
*
|
||||||
|
* If pindexStop is not a nullptr, the scan will stop at the block-index
|
||||||
|
* defined by pindexStop
|
||||||
*/
|
*/
|
||||||
CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
|
CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate)
|
||||||
{
|
{
|
||||||
int64_t nNow = GetTime();
|
int64_t nNow = GetTime();
|
||||||
const CChainParams& chainParams = Params();
|
const CChainParams& chainParams = Params();
|
||||||
|
|
||||||
|
if (pindexStop) {
|
||||||
|
assert(pindexStop->nHeight >= pindexStart->nHeight);
|
||||||
|
}
|
||||||
|
|
||||||
CBlockIndex* pindex = pindexStart;
|
CBlockIndex* pindex = pindexStart;
|
||||||
CBlockIndex* ret = nullptr;
|
CBlockIndex* ret = nullptr;
|
||||||
{
|
{
|
||||||
@ -1617,6 +1624,9 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool f
|
|||||||
} else {
|
} else {
|
||||||
ret = pindex;
|
ret = pindex;
|
||||||
}
|
}
|
||||||
|
if (pindex == pindexStop) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
pindex = chainActive.Next(pindex);
|
pindex = chainActive.Next(pindex);
|
||||||
}
|
}
|
||||||
if (pindex && fAbortRescan) {
|
if (pindex && fAbortRescan) {
|
||||||
@ -3930,7 +3940,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
|
|||||||
}
|
}
|
||||||
|
|
||||||
nStart = GetTimeMillis();
|
nStart = GetTimeMillis();
|
||||||
walletInstance->ScanForWalletTransactions(pindexRescan, true);
|
walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, true);
|
||||||
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);
|
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);
|
||||||
walletInstance->SetBestChain(chainActive.GetLocator());
|
walletInstance->SetBestChain(chainActive.GetLocator());
|
||||||
walletInstance->dbw->IncrementUpdateCounter();
|
walletInstance->dbw->IncrementUpdateCounter();
|
||||||
|
@ -919,7 +919,7 @@ public:
|
|||||||
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override;
|
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override;
|
||||||
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
|
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
|
||||||
int64_t RescanFromTime(int64_t startTime, bool update);
|
int64_t RescanFromTime(int64_t startTime, bool update);
|
||||||
CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
|
CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate = false);
|
||||||
void ReacceptWalletTransactions();
|
void ReacceptWalletTransactions();
|
||||||
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override;
|
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override;
|
||||||
// ResendWalletTransactionsBefore may only be called if fBroadcastTransactions!
|
// ResendWalletTransactionsBefore may only be called if fBroadcastTransactions!
|
||||||
|
@ -10,6 +10,7 @@ from test_framework.util import (
|
|||||||
connect_nodes_bi,
|
connect_nodes_bi,
|
||||||
)
|
)
|
||||||
import shutil
|
import shutil
|
||||||
|
import os
|
||||||
|
|
||||||
class WalletHDTest(BitcoinTestFramework):
|
class WalletHDTest(BitcoinTestFramework):
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
@ -70,9 +71,9 @@ class WalletHDTest(BitcoinTestFramework):
|
|||||||
self.stop_node(1)
|
self.stop_node(1)
|
||||||
# we need to delete the complete regtest directory
|
# we need to delete the complete regtest directory
|
||||||
# otherwise node1 would auto-recover all funds in flag the keypool keys as used
|
# otherwise node1 would auto-recover all funds in flag the keypool keys as used
|
||||||
shutil.rmtree(tmpdir + "/node1/regtest/blocks")
|
shutil.rmtree(os.path.join(tmpdir, "node1/regtest/blocks"))
|
||||||
shutil.rmtree(tmpdir + "/node1/regtest/chainstate")
|
shutil.rmtree(os.path.join(tmpdir, "node1/regtest/chainstate"))
|
||||||
shutil.copyfile(tmpdir + "/hd.bak", tmpdir + "/node1/regtest/wallet.dat")
|
shutil.copyfile(os.path.join(tmpdir, "hd.bak"), os.path.join(tmpdir, "node1/regtest/wallet.dat"))
|
||||||
self.start_node(1)
|
self.start_node(1)
|
||||||
|
|
||||||
# Assert that derivation is deterministic
|
# Assert that derivation is deterministic
|
||||||
@ -91,6 +92,22 @@ class WalletHDTest(BitcoinTestFramework):
|
|||||||
self.start_node(1, extra_args=self.extra_args[1] + ['-rescan'])
|
self.start_node(1, extra_args=self.extra_args[1] + ['-rescan'])
|
||||||
assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1)
|
assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1)
|
||||||
|
|
||||||
|
# Try a RPC based rescan
|
||||||
|
self.stop_node(1)
|
||||||
|
shutil.rmtree(os.path.join(tmpdir, "node1/regtest/blocks"))
|
||||||
|
shutil.rmtree(os.path.join(tmpdir, "node1/regtest/chainstate"))
|
||||||
|
shutil.copyfile(os.path.join(tmpdir, "hd.bak"), os.path.join(tmpdir, "node1/regtest/wallet.dat"))
|
||||||
|
self.start_node(1, extra_args=self.extra_args[1])
|
||||||
|
connect_nodes_bi(self.nodes, 0, 1)
|
||||||
|
self.sync_all()
|
||||||
|
out = self.nodes[1].rescanblockchain(0, 1)
|
||||||
|
assert_equal(out['start_height'], 0)
|
||||||
|
assert_equal(out['stop_height'], 1)
|
||||||
|
out = self.nodes[1].rescanblockchain()
|
||||||
|
assert_equal(out['start_height'], 0)
|
||||||
|
assert_equal(out['stop_height'], self.nodes[1].getblockcount())
|
||||||
|
assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1)
|
||||||
|
|
||||||
# send a tx and make sure its using the internal chain for the changeoutput
|
# send a tx and make sure its using the internal chain for the changeoutput
|
||||||
txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1)
|
txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1)
|
||||||
outs = self.nodes[1].decoderawtransaction(self.nodes[1].gettransaction(txid)['hex'])['vout']
|
outs = self.nodes[1].decoderawtransaction(self.nodes[1].gettransaction(txid)['hex'])['vout']
|
||||||
|
Loading…
Reference in New Issue
Block a user