Add rescan RPC command

This commit is contained in:
chromatic 2022-06-03 20:10:33 -07:00
parent b38a23cdfe
commit f74e27da05
4 changed files with 207 additions and 0 deletions

View File

@ -169,6 +169,7 @@ testScripts = [
'listsinceblock.py',
'p2p-leaktests.py',
'replace-by-fee.py',
'rescan.py',
'wallet_create_tx.py',
'liststucktransactions.py',
'addnode.py',

127
qa/rpc-tests/rescan.py Normal file
View File

@ -0,0 +1,127 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2016 The Bitcoin Core developers
# Copyright (c) 2022 The Dogecoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *
class RescanTest(BitcoinTestFramework):
def __init__(self):
super().__init__()
self.setup_clean_chain = True
self.num_nodes = 3
# only sync the first two nodes; use the third for prune testing
def sync_all(self):
syncable = self.nodes[:2]
sync_blocks(syncable)
sync_mempools(syncable)
def setup_network(self, split=False):
self.nodes = start_nodes(self.num_nodes - 1, self.options.tmpdir, [['-spendzeroconfchange=0'], None])
self.nodes.append(start_node(2, self.options.tmpdir, ["-prune=1"]))
connect_nodes_bi(self.nodes,0,1)
self.is_network_split=False
self.sync_all()
def run_test(self):
print("Mining blocks...")
self.nodes[0].generate(101)
self.sync_all()
# address
address1 = self.nodes[0].getnewaddress()
# pubkey
address2 = self.nodes[0].getnewaddress()
address2_pubkey = self.nodes[0].validateaddress(address2)['pubkey'] # Using pubkey
# privkey
address3 = self.nodes[0].getnewaddress()
address3_privkey = self.nodes[0].dumpprivkey(address3) # Using privkey
self.sync_all()
# Node 1 sync test
assert_equal(self.nodes[1].getblockcount(), 101)
# Send funds to self
txnid1 = self.nodes[0].sendtoaddress(address1, 10)
rawtxn1 = self.nodes[0].gettransaction(txnid1)['hex']
txnid2 = self.nodes[0].sendtoaddress(address2, 5)
rawtxn2 = self.nodes[0].gettransaction(txnid2)['hex']
txnid3 = self.nodes[0].sendtoaddress(address3, 2.5)
rawtxn3 = self.nodes[0].gettransaction(txnid3)['hex']
self.nodes[0].generate(1)
# Import with affiliated address with no rescan
self.nodes[1].importaddress(address2, "add2", False)
balance2 = self.nodes[1].getbalance("add2", 0, True)
assert_equal(balance2, Decimal('0'))
self.nodes[1].rescan()
balance2 = self.nodes[1].getbalance("add2", 0, True)
assert_equal(balance2, Decimal('5'))
# Import with private key with no rescan
self.nodes[1].importprivkey(address3_privkey, "add3", False)
# add more blocks
self.nodes[1].generate(102)
balance4 = self.nodes[1].getbalance("add3", 0, False)
assert_equal(balance4, Decimal('0'))
self.nodes[1].rescan(200)
balance4 = self.nodes[1].getbalance("add3", 0, False)
assert_equal(balance4, Decimal('0'))
result = self.nodes[1].rescan(2)
balance4 = self.nodes[1].getbalance("add3", 0, True)
assert_equal(balance4, Decimal('2.5'))
assert_equal(result["before"], {
"balance": Decimal('21000000'),
"txcount": 103
})
assert_equal(result["after"], {
"balance": Decimal('21000002.5'),
"txcount": 104
})
assert_equal(result["blocks_scanned"], 202)
assert("time_elapsed" in result)
# 2100000 from mining, 7.5 otherwise
# note that 5 are from a watch-only address
balance4 = self.nodes[1].getbalance("*", 0, True)
assert_equal(balance4, Decimal('21000007.5'))
try:
self.nodes[1].rescan(-100)
raise AssertionError("rescan should throw JSON exception given a negative block height")
except JSONRPCException as e:
assert("Block height out of range" in e.error["message"])
try:
currentheight = self.nodes[1].getblockcount()
self.nodes[1].rescan(currentheight + 1 )
raise AssertionError("rescan should throw JSON exception given a block height too high")
except JSONRPCException as e:
assert("Block height out of range" in e.error["message"])
try:
pruned_node = self.nodes[2]
pruned_node.rescan(1)
raise AssertionError("rescan should throw JSON error when used on a pruned node")
except JSONRPCException as e:
assert("Currently works only on non-pruned nodes" in e.error["message"])
if __name__ == '__main__':
RescanTest().main()

View File

@ -125,6 +125,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getmempooldescendants", 1, "verbose" },
{ "bumpfee", 1, "options" },
{ "setmaxconnections", 0, "maxconnectioncount" },
{ "rescan", 0, "height" },
// Echo with conversion (For testing only)
{ "echojson", 0, "arg0" },
{ "echojson", 1, "arg1" },

View File

@ -807,6 +807,83 @@ UniValue movecmd(const JSONRPCRequest& request)
return true;
}
UniValue rescan(const JSONRPCRequest& request)
{
if (!EnsureWalletIsAvailable(request.fHelp))
return NullUniValue;
const int nParams = request.params.size();
if (request.fHelp || nParams > 1 || fPruneMode)
throw runtime_error(
"rescan ( \"height\" )\n"
"\nRescan the wallet for transactions\n"
"\nWARNING: this operation may take a long time!\n"
"\nCurrently works only on non-pruned nodes\n"
"\nArguments:\n"
"1. \"height\" (number, optional) The block height from which to start rescanning\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
"\nResult:\n"
"{\n"
" \"before\":\n"
" {\n"
" \"balance\" : (numeric) The total amount in " + CURRENCY_UNIT + " received by the address before the rescan\n"
" \"txcount\" : (numeric) The number of transactions received by addresses in the wallet before the rescan\n"
" },\n"
" \"after\":\n"
" {\n"
" \"balance\" : (numeric) The total amount in " + CURRENCY_UNIT + " received by the address after the rescan\n"
" \"txcount\" : (numeric) The number of transactions received by addresses in the wallet after the rescan\n"
" },\n"
" \"blocks_scanned\" : (numeric) The number of blocks scanned during the rescan\n"
" \"time_elapsed\" : (numeric) The number of seconds it took to rescan the blocks (may be zero)\n"
"}\n"
"\nNote: This call can take minutes to complete.\n"
"\nExamples:\n"
"\nRescan from block height 122345\n"
+ HelpExampleCli("rescan", "122345") +
"\nRescan from the first block\n"
+ HelpExampleCli("rescan", "") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("rescan", "122345")
);
CBlockIndex* pblockindex = chainActive.Genesis();
int64_t nHeight = 0;
if (nParams == 1) {
nHeight = request.params[0].get_int();
if (nHeight < 0 || nHeight > chainActive.Height())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
pblockindex = chainActive[nHeight];
}
UniValue beforeObj(UniValue::VOBJ);
beforeObj.pushKV("balance", ValueFromAmount(pwalletMain->GetBalance()));
beforeObj.pushKV("txcount", (int)pwalletMain->mapWallet.size());
int64_t beforeTime = GetTime();
pwalletMain->ScanForWalletTransactions(pblockindex, true);
UniValue afterObj(UniValue::VOBJ);
afterObj.pushKV("balance", ValueFromAmount(pwalletMain->GetBalance()));
afterObj.pushKV("txcount", (int)pwalletMain->mapWallet.size());
UniValue ret(UniValue::VOBJ);
ret.pushKV("before", beforeObj);
ret.pushKV("after", afterObj);
ret.pushKV("blocks_scanned", chainActive.Height() - nHeight);
ret.pushKV("time_elapsed", GetTime() - beforeTime);
return ret;
}
UniValue sendfrom(const JSONRPCRequest& request)
{
@ -3183,6 +3260,7 @@ static const CRPCCommand commands[] =
{ "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
{ "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} },
{ "wallet", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} },
{ "wallet", "rescan", &rescan, false, {"height"} },
{ "wallet", "sendfrom", &sendfrom, false, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },
{ "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom"} },
{ "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount"} },