Add optional height to importprivkey RPC

This allows users to avoid rescanning the entire chain when importing a
new private key, if they provide the height of the block from which to
start. Note that any transactions to or from the corresponding wallet
will only be indexed if they occur at or after the given height.

The height argument is `height`, consistent with the `height` argument to
`rescan`.
This commit is contained in:
chromatic 2022-05-04 21:46:36 -07:00
parent f447c0c0de
commit 05d20afccd
4 changed files with 46 additions and 9 deletions

View File

@ -1,17 +1,18 @@
#!/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.
"""Test rescan behavior of importaddress, importpubkey, importprivkey, and
importmulti RPCs with different types of keys and rescan options.
In the first part of the test, node 1 creates an address for each type of
import RPC call and node 0 sends BTC to it. Then other nodes import the
import RPC call and node 0 sends Doge to it. Then other nodes import the
addresses, and the test makes listtransactions and getbalance calls to confirm
that the importing node either did or did not execute rescans picking up the
send transactions.
In the second part of the test, node 0 sends more BTC to each address, and the
In the second part of the test, node 0 sends more Doge to each address, and the
test makes more listtransactions and getbalance calls to confirm that the
importing nodes pick up the new transactions regardless of whether rescans
happened previously.
@ -114,6 +115,9 @@ class ImportRescanTest(BitcoinTestFramework):
super().__init__()
self.num_nodes = 2 + len(IMPORT_NODES)
def first_node(self):
return self.nodes[0]
def setup_network(self):
extra_args = [["-debug=1"] for _ in range(self.num_nodes)]
for i, import_node in enumerate(IMPORT_NODES, 2):
@ -125,8 +129,11 @@ class ImportRescanTest(BitcoinTestFramework):
connect_nodes(self.nodes[i], 0)
def run_test(self):
self.test_argument_validation()
# Create one transaction on node 0 with a unique amount and label for
# each possible type of wallet import RPC.
for i, variant in enumerate(IMPORT_VARIANTS):
variant.label = "label {} {}".format(i, variant)
variant.address = self.nodes[1].validateaddress(self.nodes[1].getnewaddress(variant.label))
@ -179,6 +186,21 @@ class ImportRescanTest(BitcoinTestFramework):
else:
variant.check()
def test_argument_validation(self):
node = self.first_node()
try:
node.importprivkey("")
except JSONRPCException as e:
assert("Invalid private key encoding" in e.error["message"])
address = node.validateaddress(node.getnewaddress("some label"))
privkey = node.dumpprivkey(address["address"])
try:
node.importprivkey(privkey, "", True, str(node.getblockcount() + 1))
except JSONRPCException as e:
assert("Block height out of range" in e.error["message"])
def try_rpc(func, *args, **kwargs):
try:
@ -186,6 +208,5 @@ def try_rpc(func, *args, **kwargs):
except JSONRPCException as e:
return None, e.error
if __name__ == "__main__":
ImportRescanTest().main()

View File

@ -103,6 +103,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "lockunspent", 0, "unlock" },
{ "lockunspent", 1, "transactions" },
{ "importprivkey", 2, "rescan" },
{ "importprivkey", 3, "height" },
{ "importaddress", 2, "rescan" },
{ "importaddress", 3, "p2sh" },
{ "importpubkey", 2, "rescan" },

View File

@ -83,8 +83,8 @@ UniValue importprivkey(const JSONRPCRequest& request)
{
if (!EnsureWalletIsAvailable(request.fHelp))
return NullUniValue;
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
throw runtime_error(
"importprivkey \"dogecoinprivkey\" ( \"label\" ) ( rescan )\n"
"\nAdds a private key (as returned by dumpprivkey) to your wallet.\n"
@ -92,6 +92,7 @@ UniValue importprivkey(const JSONRPCRequest& request)
"1. \"dogecoinprivkey\" (string, required) The private key (see dumpprivkey)\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"
"\nDump a private key\n"
@ -102,6 +103,8 @@ UniValue importprivkey(const JSONRPCRequest& request)
+ HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") +
"\nImport using default blank label and without rescan\n"
+ HelpExampleCli("importprivkey", "\"mykey\" \"\" false") +
"\nImport using default blank label, with rescan, from a specific block height\n"
+ HelpExampleCli("importprivkey", "\"mykey\" \"\" true 3760036") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")
);
@ -148,11 +151,23 @@ UniValue importprivkey(const JSONRPCRequest& request)
if (!pwalletMain->AddKeyPubKey(key, pubkey))
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
// whenever a key is imported, we need to scan the whole chain
pwalletMain->UpdateTimeFirstKey(1);
if (fRescan) {
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
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);
}
}

View File

@ -3244,7 +3244,7 @@ static const CRPCCommand commands[] =
{ "wallet", "getunconfirmedbalance", &getunconfirmedbalance, false, {} },
{ "wallet", "getwalletinfo", &getwalletinfo, false, {} },
{ "wallet", "importmulti", &importmulti, true, {"requests","options"} },
{ "wallet", "importprivkey", &importprivkey, true, {"privkey","label","rescan"} },
{ "wallet", "importprivkey", &importprivkey, true, {"privkey","label","rescan", "height"} },
{ "wallet", "importwallet", &importwallet, true, {"filename"} },
{ "wallet", "importaddress", &importaddress, true, {"address","label","rescan","p2sh"} },
{ "wallet", "importprunedfunds", &importprunedfunds, true, {"rawtransaction","txoutproof"} },