Add height to importpubkey for rescan

This adds two helper functions. One function gets a height parameter from the
incoming RPC request. The other performs the scanning. We can use both
functions for reducing code in other RPC calls that can/should take height
parameters and perform rescanning.
This commit is contained in:
chromatic 2022-08-18 21:12:25 -07:00
parent 6686083a14
commit 7a00fb1b18
4 changed files with 97 additions and 22 deletions

View File

@ -20,7 +20,7 @@ happened previously.
from test_framework.authproxy import JSONRPCException
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (start_nodes, connect_nodes, sync_blocks, assert_equal, set_node_times)
from test_framework.util import (start_nodes, connect_nodes, sync_blocks, assert_equal, set_node_times, sync_mempools)
from decimal import Decimal
import collections
@ -127,10 +127,19 @@ class ImportRescanTest(BitcoinTestFramework):
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args)
for i in range(1, self.num_nodes):
connect_nodes(self.nodes[i], 0)
self.sync_recipient_nodes()
def sync_recipient_nodes(self):
syncable = self.nodes[:2]
sync_blocks(syncable)
sync_mempools(syncable)
def run_test(self):
self.test_argument_validation()
self.test_import_types()
self.test_rescan_from_height()
def test_import_types(self):
# Create one transaction on node 0 with a unique amount and label for
# each possible type of wallet import RPC.
@ -202,6 +211,54 @@ class ImportRescanTest(BitcoinTestFramework):
except JSONRPCException as e:
assert("Block height out of range" in e.error["message"])
try:
node.importpubkey("")
except JSONRPCException as e:
assert("Pubkey must be a hex string" in e.error["message"])
try:
node.importpubkey("abcdef")
except JSONRPCException as e:
assert("Pubkey is not a valid public key" in e.error["message"])
try:
address = self.nodes[1].getnewaddress()
pubkey = self.nodes[1].validateaddress(address)["pubkey"]
node.importpubkey(pubkey, "", True, node.getblockcount() + 1)
except JSONRPCException as e:
assert("Block height out of range" in e.error["message"])
def test_rescan_from_height(self):
# this height is before sending anything to the new address
orig_height = self.nodes[0].getblockcount()
address = self.nodes[0].getnewaddress()
pubkey = self.nodes[0].validateaddress(address)["pubkey"]
self.nodes[0].sendtoaddress(address, 100)
# generate two blocks
# the first contains the tx that sends these koinu
# the second is after it
self.nodes[0].generate(2)
new_height = self.nodes[0].getblockcount()
self.sync_recipient_nodes()
# no rescan, no funds seen for this pubkey
self.nodes[1].importpubkey(pubkey, "newpubkey", False)
balance = self.nodes[1].getbalance("newpubkey", 0, True)
assert_equal(balance, Decimal("0"))
# rescan at the block *after* the tx, no funds seen for this pubkey
self.nodes[2].importpubkey(pubkey, "newpubkey", True, new_height)
balance = self.nodes[2].getbalance("newpubkey", 0, True)
assert_equal(balance, Decimal("0"))
# rescan at the block *before* the tx, funds seen for this pubkey
self.nodes[3].importpubkey(pubkey, "newpubkey", True, orig_height)
balance = self.nodes[3].getbalance("newpubkey", 0, True)
assert_equal(balance, Decimal("100"))
def try_rpc(func, *args, **kwargs):
try:
return func(*args, **kwargs), None

View File

@ -107,6 +107,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "importaddress", 2, "rescan" },
{ "importaddress", 3, "p2sh" },
{ "importpubkey", 2, "rescan" },
{ "importpubkey", 3, "height" },
{ "importmulti", 0, "requests" },
{ "importmulti", 1, "options" },
{ "verifychain", 0, "checklevel" },

View File

@ -35,6 +35,8 @@ using namespace std;
void EnsureWalletIsUnlocked();
bool EnsureWalletIsAvailable(bool avoidException);
uint32_t getHeightParamFromRequest(const JSONRPCRequest& request, size_t pos);
void attemptRescanFromHeight(uint32_t nHeight);
std::string static EncodeDumpTime(int64_t nTime) {
return DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", nTime);
@ -151,29 +153,42 @@ UniValue importprivkey(const JSONRPCRequest& request)
if (!pwalletMain->AddKeyPubKey(key, pubkey))
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
if (fRescan) {
CBlockIndex* pblockindex = chainActive.Genesis();
if (request.params.size() > 3) {
int nHeight = request.params[3].get_int();
if (nHeight < 0 || nHeight > chainActive.Height())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
pblockindex = chainActive[nHeight];
} else {
// we have no implicit first height for a key, so we need to scan the whole chain
pwalletMain->UpdateTimeFirstKey(1);
}
pwalletMain->ScanForWalletTransactions(pblockindex, true);
const uint32_t nHeight = getHeightParamFromRequest(request, 3);
attemptRescanFromHeight(nHeight);
}
}
return NullUniValue;
}
uint32_t getHeightParamFromRequest(const JSONRPCRequest& request, const size_t pos)
{
if (request.params.size() <= pos)
return 1;
const int nHeight = request.params[pos].get_int();
if (nHeight < 0 || nHeight > chainActive.Height())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
return nHeight;
}
void attemptRescanFromHeight(const uint32_t nHeight)
{
CBlockIndex* pblockindex = chainActive.Genesis();
// we have no implicit first height for a key, so we need to scan the whole chain
if (nHeight <= 1)
pwalletMain->UpdateTimeFirstKey(1);
else
pblockindex = chainActive[nHeight];
pwalletMain->ScanForWalletTransactions(pblockindex, true);
pwalletMain->ReacceptWalletTransactions();
}
void ImportAddress(const CBitcoinAddress& address, const string& strLabel);
void ImportScript(const CScript& script, const string& strLabel, bool isRedeemScript)
{
@ -382,10 +397,13 @@ UniValue importpubkey(const JSONRPCRequest& request)
"1. \"pubkey\" (string, required) The hex-encoded public key\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
"4. height (numeric, optional, default=1) If rescanning, the block height from which to start\n"
"\nNote: This call can take minutes to complete if rescan is true.\n"
"\nExamples:\n"
"\nImport a public key with rescan\n"
+ HelpExampleCli("importpubkey", "\"mypubkey\"") +
"\nImport a public key with rescan from a specific height\n"
+ HelpExampleCli("importpubkey", "\"mypubkey\" true 123654") +
"\nImport using a label without rescan\n"
+ HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") +
"\nAs a JSON-RPC call\n"
@ -417,10 +435,9 @@ UniValue importpubkey(const JSONRPCRequest& request)
ImportAddress(CBitcoinAddress(pubKey.GetID()), strLabel);
ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false);
if (fRescan)
{
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
pwalletMain->ReacceptWalletTransactions();
if (fRescan) {
const uint32_t nHeight = getHeightParamFromRequest(request, 3);
attemptRescanFromHeight(nHeight);
}
return NullUniValue;

View File

@ -3248,7 +3248,7 @@ static const CRPCCommand commands[] =
{ "wallet", "importwallet", &importwallet, true, {"filename"} },
{ "wallet", "importaddress", &importaddress, true, {"address","label","rescan","p2sh"} },
{ "wallet", "importprunedfunds", &importprunedfunds, true, {"rawtransaction","txoutproof"} },
{ "wallet", "importpubkey", &importpubkey, true, {"pubkey","label","rescan"} },
{ "wallet", "importpubkey", &importpubkey, true, {"pubkey","label","rescan", "height"} },
{ "wallet", "keypoolrefill", &keypoolrefill, true, {"newsize"} },
{ "wallet", "listaccounts", &listaccounts, false, {"minconf","include_watchonly"} },
{ "wallet", "listaddressgroupings", &listaddressgroupings, false, {} },