From 8be0866883ff4efec9182194e900a05742e1b322 Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Tue, 14 Feb 2017 15:00:34 -0500 Subject: [PATCH 1/4] [qa] Simplify import-rescan.py Get rid of partial functions so the test can be more easily extended to add more variants of imports with options that affect rescanning (e.g. different key timestamps). Also change the second half of the test to send /to/ the imported addresses, instead of /from/ the imported addresses. The goal of this part of the test was to confirm that the wallet would pick up new transactions after an import regardless of whether or not a rescan happened during the import. But because the wallet can only do this reliably for incoming transactions and not outgoing transactions (which require the wallet to look up transaction inputs) the test previously was less meaningful than it should have been. --- qa/rpc-tests/import-rescan.py | 212 +++++++++++++++++----------------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/qa/rpc-tests/import-rescan.py b/qa/rpc-tests/import-rescan.py index 8f60e63e2..7734ad5e4 100755 --- a/qa/rpc-tests/import-rescan.py +++ b/qa/rpc-tests/import-rescan.py @@ -2,6 +2,22 @@ # Copyright (c) 2014-2016 The Bitcoin 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. + +Test uses three connected nodes. + +In the first part of the test, node 0 creates an address for each type of +import RPC call and sends BTC to it. Then nodes 1 and 2 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 +test makes more listtransactions and getbalance calls to confirm that the +importing nodes pick up the new transactions regardless of whether rescans +happened previously. +""" from test_framework.test_framework import BitcoinTestFramework from test_framework.util import (start_nodes, connect_nodes, sync_blocks, assert_equal) @@ -10,53 +26,72 @@ from decimal import Decimal import collections import enum import itertools -import functools Call = enum.Enum("Call", "single multi") Data = enum.Enum("Data", "address pub priv") -ImportNode = collections.namedtuple("ImportNode", "rescan") +Rescan = enum.Enum("Rescan", "no yes") -def call_import_rpc(call, data, address, scriptPubKey, pubkey, key, label, node, rescan): - """Helper that calls a wallet import RPC on a bitcoin node.""" - watchonly = data != Data.priv - if call == Call.single: - if data == Data.address: - response = node.importaddress(address, label, rescan) - elif data == Data.pub: - response = node.importpubkey(pubkey, label, rescan) - elif data == Data.priv: - response = node.importprivkey(key, label, rescan) - assert_equal(response, None) - elif call == Call.multi: - response = node.importmulti([{ - "scriptPubKey": { - "address": address - }, - "timestamp": "now", - "pubkeys": [pubkey] if data == Data.pub else [], - "keys": [key] if data == Data.priv else [], - "label": label, - "watchonly": watchonly - }], {"rescan": rescan}) - assert_equal(response, [{"success": True}]) - return watchonly +class Variant(collections.namedtuple("Variant", "call data rescan")): + """Helper for importing one key and verifying scanned transactions.""" + + def do_import(self): + """Call one key import RPC.""" + + if self.call == Call.single: + if self.data == Data.address: + response = self.node.importaddress(self.address["address"], self.label, self.rescan == Rescan.yes) + elif self.data == Data.pub: + response = self.node.importpubkey(self.address["pubkey"], self.label, self.rescan == Rescan.yes) + elif self.data == Data.priv: + response = self.node.importprivkey(self.key, self.label, self.rescan == Rescan.yes) + assert_equal(response, None) + elif self.call == Call.multi: + response = self.node.importmulti([{ + "scriptPubKey": { + "address": self.address["address"] + }, + "timestamp": "now", + "pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [], + "keys": [self.key] if self.data == Data.priv else [], + "label": self.label, + "watchonly": self.data != Data.priv + }], {"rescan": self.rescan == Rescan.yes}) + assert_equal(response, [{"success": True}]) + + def check(self, txid=None, amount=None): + """Verify that getbalance/listtransactions return expected values.""" + + balance = self.node.getbalance(self.label, 0, True) + assert_equal(balance, self.expected_balance) + + txs = self.node.listtransactions(self.label, 10000, 0, True) + assert_equal(len(txs), self.expected_txs) + + if txid is not None: + tx, = [tx for tx in txs if tx["txid"] == txid] + assert_equal(tx["account"], self.label) + assert_equal(tx["address"], self.address["address"]) + assert_equal(tx["amount"], amount) + assert_equal(tx["category"], "receive") + assert_equal(tx["label"], self.label) + assert_equal(tx["txid"], txid) + assert_equal(tx["confirmations"], 1) + assert_equal("trusted" not in tx, True) + if self.data != Data.priv: + assert_equal(tx["involvesWatchonly"], True) + else: + assert_equal("involvesWatchonly" not in tx, True) -# List of RPCs that import a wallet key or address in various ways. -IMPORT_RPCS = [functools.partial(call_import_rpc, call, data) for call, data in itertools.product(Call, Data)] - -# List of bitcoind nodes that will import keys. -IMPORT_NODES = [ - ImportNode(rescan=True), - ImportNode(rescan=False), -] +# List of Variants for each way a key or address could be imported. +IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Data, Rescan)] class ImportRescanTest(BitcoinTestFramework): def __init__(self): super().__init__() - self.num_nodes = 1 + len(IMPORT_NODES) + self.num_nodes = 3 def setup_network(self): extra_args = [["-debug=1"] for _ in range(self.num_nodes)] @@ -67,89 +102,54 @@ class ImportRescanTest(BitcoinTestFramework): def run_test(self): # Create one transaction on node 0 with a unique amount and label for # each possible type of wallet import RPC. - import_rpc_variants = [] - for i, import_rpc in enumerate(IMPORT_RPCS): - label = "label{}".format(i) - addr = self.nodes[0].validateaddress(self.nodes[0].getnewaddress(label)) - key = self.nodes[0].dumpprivkey(addr["address"]) - amount = 24.9375 - i * .0625 - txid = self.nodes[0].sendtoaddress(addr["address"], amount) - import_rpc = functools.partial(import_rpc, addr["address"], addr["scriptPubKey"], addr["pubkey"], key, - label) - import_rpc_variants.append((import_rpc, label, amount, txid, addr)) + for i, variant in enumerate(IMPORT_VARIANTS): + variant.label = "label {} {}".format(i, variant) + variant.address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress(variant.label)) + variant.key = self.nodes[0].dumpprivkey(variant.address["address"]) + variant.initial_amount = 25 - (i + 1) / 4.0 + variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount) + # Generate a block containing the initial transactions. self.nodes[0].generate(1) assert_equal(self.nodes[0].getrawmempool(), []) sync_blocks(self.nodes) - # For each importing node and variation of wallet import RPC, invoke - # the RPC and check the results from getbalance and listtransactions. - for node, import_node in zip(self.nodes[1:], IMPORT_NODES): - for import_rpc, label, amount, txid, addr in import_rpc_variants: - watchonly = import_rpc(node, import_node.rescan) + # For each variation of wallet key import, invoke the import RPC and + # check the results from getbalance and listtransactions. Import to + # node 1 if rescanning is expected, and to node 2 if rescanning is not + # expected. Node 2 is reserved for imports that do not cause rescans, + # so later import calls don't inadvertently cause the wallet to pick up + # transactions from earlier import calls where a rescan was not + # expected (this would make it complicated to figure out expected + # balances in the second part of the test.) + for variant in IMPORT_VARIANTS: + variant.node = self.nodes[1 if variant.rescan == Rescan.yes else 2] + variant.do_import() + if variant.rescan == Rescan.yes: + variant.expected_balance = variant.initial_amount + variant.expected_txs = 1 + variant.check(variant.initial_txid, variant.initial_amount) + else: + variant.expected_balance = 0 + variant.expected_txs = 0 + variant.check() - balance = node.getbalance(label, 0, True) - if import_node.rescan: - assert_equal(balance, amount) - else: - assert_equal(balance, 0) - - txs = node.listtransactions(label, 10000, 0, True) - if import_node.rescan: - assert_equal(len(txs), 1) - assert_equal(txs[0]["account"], label) - assert_equal(txs[0]["address"], addr["address"]) - assert_equal(txs[0]["amount"], amount) - assert_equal(txs[0]["category"], "receive") - assert_equal(txs[0]["label"], label) - assert_equal(txs[0]["txid"], txid) - assert_equal(txs[0]["confirmations"], 1) - assert_equal("trusted" not in txs[0], True) - if watchonly: - assert_equal(txs[0]["involvesWatchonly"], True) - else: - assert_equal("involvesWatchonly" not in txs[0], True) - else: - assert_equal(len(txs), 0) - - # Create spends for all the imported addresses. - spend_txids = [] + # Create new transactions sending to each address. fee = self.nodes[0].getnetworkinfo()["relayfee"] - for import_rpc, label, amount, txid, addr in import_rpc_variants: - raw_tx = self.nodes[0].getrawtransaction(txid) - decoded_tx = self.nodes[0].decoderawtransaction(raw_tx) - input_vout = next(out["n"] for out in decoded_tx["vout"] - if out["scriptPubKey"]["addresses"] == [addr["address"]]) - inputs = [{"txid": txid, "vout": input_vout}] - outputs = {self.nodes[0].getnewaddress(): Decimal(amount) - fee} - raw_spend_tx = self.nodes[0].createrawtransaction(inputs, outputs) - signed_spend_tx = self.nodes[0].signrawtransaction(raw_spend_tx) - spend_txid = self.nodes[0].sendrawtransaction(signed_spend_tx["hex"]) - spend_txids.append(spend_txid) + for i, variant in enumerate(IMPORT_VARIANTS): + variant.sent_amount = 25 - (2 * i + 1) / 8.0 + variant.sent_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.sent_amount) + # Generate a block containing the new transactions. self.nodes[0].generate(1) assert_equal(self.nodes[0].getrawmempool(), []) sync_blocks(self.nodes) - # Check the results from getbalance and listtransactions after the spends. - for node, import_node in zip(self.nodes[1:], IMPORT_NODES): - txs = node.listtransactions("*", 10000, 0, True) - for (import_rpc, label, amount, txid, addr), spend_txid in zip(import_rpc_variants, spend_txids): - balance = node.getbalance(label, 0, True) - spend_tx = [tx for tx in txs if tx["txid"] == spend_txid] - if import_node.rescan: - assert_equal(balance, amount) - assert_equal(len(spend_tx), 1) - assert_equal(spend_tx[0]["account"], "") - assert_equal(spend_tx[0]["amount"] + spend_tx[0]["fee"], -amount) - assert_equal(spend_tx[0]["category"], "send") - assert_equal("label" not in spend_tx[0], True) - assert_equal(spend_tx[0]["confirmations"], 1) - assert_equal("trusted" not in spend_tx[0], True) - assert_equal("involvesWatchonly" not in txs[0], True) - else: - assert_equal(balance, 0) - assert_equal(spend_tx, []) + # Check the latest results from getbalance and listtransactions. + for variant in IMPORT_VARIANTS: + variant.expected_balance += variant.sent_amount + variant.expected_txs += 1 + variant.check(variant.sent_txid, variant.sent_amount) if __name__ == "__main__": From c28583d0629bc6e12ea5d8c660c9b3586be5ad71 Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Tue, 14 Feb 2017 17:39:26 -0500 Subject: [PATCH 2/4] [qa] Extend import-rescan.py to test specific key timestamps --- qa/rpc-tests/import-rescan.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/qa/rpc-tests/import-rescan.py b/qa/rpc-tests/import-rescan.py index 7734ad5e4..7ca606828 100755 --- a/qa/rpc-tests/import-rescan.py +++ b/qa/rpc-tests/import-rescan.py @@ -20,7 +20,7 @@ happened previously. """ from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import (start_nodes, connect_nodes, sync_blocks, assert_equal) +from test_framework.util import (start_nodes, connect_nodes, sync_blocks, assert_equal, set_node_times) from decimal import Decimal import collections @@ -29,13 +29,13 @@ import itertools Call = enum.Enum("Call", "single multi") Data = enum.Enum("Data", "address pub priv") -Rescan = enum.Enum("Rescan", "no yes") +Rescan = enum.Enum("Rescan", "no yes late_timestamp") class Variant(collections.namedtuple("Variant", "call data rescan")): """Helper for importing one key and verifying scanned transactions.""" - def do_import(self): + def do_import(self, timestamp): """Call one key import RPC.""" if self.call == Call.single: @@ -51,15 +51,15 @@ class Variant(collections.namedtuple("Variant", "call data rescan")): "scriptPubKey": { "address": self.address["address"] }, - "timestamp": "now", + "timestamp": timestamp + (1 if self.rescan == Rescan.late_timestamp else 0), "pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [], "keys": [self.key] if self.data == Data.priv else [], "label": self.label, "watchonly": self.data != Data.priv - }], {"rescan": self.rescan == Rescan.yes}) + }], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)}) assert_equal(response, [{"success": True}]) - def check(self, txid=None, amount=None): + def check(self, txid=None, amount=None, confirmations=None): """Verify that getbalance/listtransactions return expected values.""" balance = self.node.getbalance(self.label, 0, True) @@ -76,7 +76,7 @@ class Variant(collections.namedtuple("Variant", "call data rescan")): assert_equal(tx["category"], "receive") assert_equal(tx["label"], self.label) assert_equal(tx["txid"], txid) - assert_equal(tx["confirmations"], 1) + assert_equal(tx["confirmations"], confirmations) assert_equal("trusted" not in tx, True) if self.data != Data.priv: assert_equal(tx["involvesWatchonly"], True) @@ -109,9 +109,13 @@ class ImportRescanTest(BitcoinTestFramework): variant.initial_amount = 25 - (i + 1) / 4.0 variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount) - # Generate a block containing the initial transactions. + # Generate a block containing the initial transactions, then another + # block further in the future (past the rescan window). self.nodes[0].generate(1) assert_equal(self.nodes[0].getrawmempool(), []) + timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"] + set_node_times(self.nodes, timestamp + 1) + self.nodes[0].generate(1) sync_blocks(self.nodes) # For each variation of wallet key import, invoke the import RPC and @@ -124,11 +128,11 @@ class ImportRescanTest(BitcoinTestFramework): # balances in the second part of the test.) for variant in IMPORT_VARIANTS: variant.node = self.nodes[1 if variant.rescan == Rescan.yes else 2] - variant.do_import() + variant.do_import(timestamp) if variant.rescan == Rescan.yes: variant.expected_balance = variant.initial_amount variant.expected_txs = 1 - variant.check(variant.initial_txid, variant.initial_amount) + variant.check(variant.initial_txid, variant.initial_amount, 2) else: variant.expected_balance = 0 variant.expected_txs = 0 @@ -149,7 +153,7 @@ class ImportRescanTest(BitcoinTestFramework): for variant in IMPORT_VARIANTS: variant.expected_balance += variant.sent_amount variant.expected_txs += 1 - variant.check(variant.sent_txid, variant.sent_amount) + variant.check(variant.sent_txid, variant.sent_amount, 1) if __name__ == "__main__": From 38d3e9ee5900dffb02207211ad5da8f0751e4605 Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Wed, 15 Feb 2017 11:28:50 -0500 Subject: [PATCH 3/4] [qa] Extend import-rescan.py to test imports on pruned nodes. --- qa/rpc-tests/import-rescan.py | 64 +++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/qa/rpc-tests/import-rescan.py b/qa/rpc-tests/import-rescan.py index 7ca606828..bd03491f1 100755 --- a/qa/rpc-tests/import-rescan.py +++ b/qa/rpc-tests/import-rescan.py @@ -5,10 +5,8 @@ """Test rescan behavior of importaddress, importpubkey, importprivkey, and importmulti RPCs with different types of keys and rescan options. -Test uses three connected nodes. - In the first part of the test, node 0 creates an address for each type of -import RPC call and sends BTC to it. Then nodes 1 and 2 import the addresses, +import RPC call and sends BTC 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. @@ -19,6 +17,7 @@ importing nodes pick up the new transactions regardless of whether rescans 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 decimal import Decimal @@ -32,7 +31,7 @@ Data = enum.Enum("Data", "address pub priv") Rescan = enum.Enum("Rescan", "no yes late_timestamp") -class Variant(collections.namedtuple("Variant", "call data rescan")): +class Variant(collections.namedtuple("Variant", "call data rescan prune")): """Helper for importing one key and verifying scanned transactions.""" def do_import(self, timestamp): @@ -40,12 +39,16 @@ class Variant(collections.namedtuple("Variant", "call data rescan")): if self.call == Call.single: if self.data == Data.address: - response = self.node.importaddress(self.address["address"], self.label, self.rescan == Rescan.yes) + response, error = try_rpc(self.node.importaddress, self.address["address"], self.label, + self.rescan == Rescan.yes) elif self.data == Data.pub: - response = self.node.importpubkey(self.address["pubkey"], self.label, self.rescan == Rescan.yes) + response, error = try_rpc(self.node.importpubkey, self.address["pubkey"], self.label, + self.rescan == Rescan.yes) elif self.data == Data.priv: - response = self.node.importprivkey(self.key, self.label, self.rescan == Rescan.yes) + response, error = try_rpc(self.node.importprivkey, self.key, self.label, self.rescan == Rescan.yes) assert_equal(response, None) + assert_equal(error, {'message': 'Rescan is disabled in pruned mode', + 'code': -4} if self.expect_disabled else None) elif self.call == Call.multi: response = self.node.importmulti([{ "scriptPubKey": { @@ -85,16 +88,29 @@ class Variant(collections.namedtuple("Variant", "call data rescan")): # List of Variants for each way a key or address could be imported. -IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Data, Rescan)] +IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Data, Rescan, (False, True))] + +# List of nodes to import keys to. Half the nodes will have pruning disabled, +# half will have it enabled. Different nodes will be used for imports that are +# expected to cause rescans, and imports that are not expected to cause +# rescans, in order to prevent rescans during later imports picking up +# transactions associated with earlier imports. This makes it easier to keep +# track of expected balances and transactions. +ImportNode = collections.namedtuple("ImportNode", "prune rescan") +IMPORT_NODES = [ImportNode(*fields) for fields in itertools.product((False, True), repeat=2)] class ImportRescanTest(BitcoinTestFramework): def __init__(self): super().__init__() - self.num_nodes = 3 + self.num_nodes = 1 + len(IMPORT_NODES) def setup_network(self): extra_args = [["-debug=1"] for _ in range(self.num_nodes)] + for i, import_node in enumerate(IMPORT_NODES, 1): + if import_node.prune: + extra_args[i] += ["-prune=1"] + 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) @@ -119,17 +135,13 @@ class ImportRescanTest(BitcoinTestFramework): sync_blocks(self.nodes) # For each variation of wallet key import, invoke the import RPC and - # check the results from getbalance and listtransactions. Import to - # node 1 if rescanning is expected, and to node 2 if rescanning is not - # expected. Node 2 is reserved for imports that do not cause rescans, - # so later import calls don't inadvertently cause the wallet to pick up - # transactions from earlier import calls where a rescan was not - # expected (this would make it complicated to figure out expected - # balances in the second part of the test.) + # check the results from getbalance and listtransactions. for variant in IMPORT_VARIANTS: - variant.node = self.nodes[1 if variant.rescan == Rescan.yes else 2] + variant.expect_disabled = variant.rescan == Rescan.yes and variant.prune and variant.call == Call.single + expect_rescan = variant.rescan == Rescan.yes and not variant.expect_disabled + variant.node = self.nodes[1 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))] variant.do_import(timestamp) - if variant.rescan == Rescan.yes: + if expect_rescan: variant.expected_balance = variant.initial_amount variant.expected_txs = 1 variant.check(variant.initial_txid, variant.initial_amount, 2) @@ -151,9 +163,19 @@ class ImportRescanTest(BitcoinTestFramework): # Check the latest results from getbalance and listtransactions. for variant in IMPORT_VARIANTS: - variant.expected_balance += variant.sent_amount - variant.expected_txs += 1 - variant.check(variant.sent_txid, variant.sent_amount, 1) + if not variant.expect_disabled: + variant.expected_balance += variant.sent_amount + variant.expected_txs += 1 + variant.check(variant.sent_txid, variant.sent_amount, 1) + else: + variant.check() + + +def try_rpc(func, *args, **kwargs): + try: + return func(*args, **kwargs), None + except JSONRPCException as e: + return None, e.error if __name__ == "__main__": From e662af358305b9fa4da772ec37c8356a9fc92ab6 Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Tue, 14 Feb 2017 17:39:26 -0500 Subject: [PATCH 4/4] Use 2 hour grace period for key timestamps in importmulti rescans Gregory Maxwell pointed out the lack of grace period in https://github.com/bitcoin/bitcoin/pull/9490#issue-199407998. The importwallet RPC which uses key timestamps in a similar way already has a 2 hour grace period. --- qa/rpc-tests/import-rescan.py | 7 +++++-- src/wallet/rpcdump.cpp | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/qa/rpc-tests/import-rescan.py b/qa/rpc-tests/import-rescan.py index bd03491f1..54cc6d264 100755 --- a/qa/rpc-tests/import-rescan.py +++ b/qa/rpc-tests/import-rescan.py @@ -54,7 +54,7 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")): "scriptPubKey": { "address": self.address["address"] }, - "timestamp": timestamp + (1 if self.rescan == Rescan.late_timestamp else 0), + "timestamp": timestamp + RESCAN_WINDOW + (1 if self.rescan == Rescan.late_timestamp else 0), "pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [], "keys": [self.key] if self.data == Data.priv else [], "label": self.label, @@ -99,6 +99,9 @@ IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Da ImportNode = collections.namedtuple("ImportNode", "prune rescan") IMPORT_NODES = [ImportNode(*fields) for fields in itertools.product((False, True), repeat=2)] +# Rescans start at the earliest block up to 2 hours before the key timestamp. +RESCAN_WINDOW = 2 * 60 * 60 + class ImportRescanTest(BitcoinTestFramework): def __init__(self): @@ -130,7 +133,7 @@ class ImportRescanTest(BitcoinTestFramework): self.nodes[0].generate(1) assert_equal(self.nodes[0].getrawmempool(), []) timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"] - set_node_times(self.nodes, timestamp + 1) + set_node_times(self.nodes, timestamp + RESCAN_WINDOW + 1) self.nodes[0].generate(1) sync_blocks(self.nodes) diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 908655d41..30f241467 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -988,7 +988,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) " or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n" " key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n" " \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n" - " 0 can be specified to scan the entire blockchain.\n" + " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n" + " creation time of all keys being imported by the importmulti call will be scanned.\n" " \"redeemscript\": \"