Changeset View
Changeset View
Standalone View
Standalone View
test/functional/wallet_import_rescan.py
Show All 39 Lines | def do_import(self, timestamp): | ||||
"""Call one key import RPC.""" | """Call one key import RPC.""" | ||||
rescan = self.rescan == Rescan.yes | rescan = self.rescan == Rescan.yes | ||||
assert_equal(self.address["solvable"], True) | assert_equal(self.address["solvable"], True) | ||||
if self.call == Call.single: | if self.call == Call.single: | ||||
if self.data == Data.address: | if self.data == Data.address: | ||||
response = self.node.importaddress( | response = self.node.importaddress( | ||||
address=self.address["address"], label=self.label, rescan=rescan) | address=self.address["address"], label=self.label, rescan=rescan | ||||
) | |||||
elif self.data == Data.pub: | elif self.data == Data.pub: | ||||
response = self.node.importpubkey( | response = self.node.importpubkey( | ||||
pubkey=self.address["pubkey"], label=self.label, rescan=rescan) | pubkey=self.address["pubkey"], label=self.label, rescan=rescan | ||||
) | |||||
elif self.data == Data.priv: | elif self.data == Data.priv: | ||||
response = self.node.importprivkey( | response = self.node.importprivkey( | ||||
privkey=self.key, label=self.label, rescan=rescan) | privkey=self.key, label=self.label, rescan=rescan | ||||
) | |||||
assert_equal(response, None) | assert_equal(response, None) | ||||
elif self.call in (Call.multiaddress, Call.multiscript): | elif self.call in (Call.multiaddress, Call.multiscript): | ||||
request = { | request = { | ||||
"scriptPubKey": { | "scriptPubKey": ( | ||||
"address": self.address["address"] | {"address": self.address["address"]} | ||||
} if self.call == Call.multiaddress else self.address["scriptPubKey"], | if self.call == Call.multiaddress | ||||
"timestamp": timestamp + TIMESTAMP_WINDOW + ( | else self.address["scriptPubKey"] | ||||
1 if self.rescan == Rescan.late_timestamp else 0), | ), | ||||
"timestamp": ( | |||||
timestamp | |||||
+ TIMESTAMP_WINDOW | |||||
+ (1 if self.rescan == Rescan.late_timestamp else 0) | |||||
), | |||||
"pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [], | "pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [], | ||||
"keys": [self.key] if self.data == Data.priv else [], | "keys": [self.key] if self.data == Data.priv else [], | ||||
"label": self.label, | "label": self.label, | ||||
"watchonly": self.data != Data.priv | "watchonly": self.data != Data.priv, | ||||
} | } | ||||
response = self.node.importmulti( | response = self.node.importmulti( | ||||
requests=[request], | requests=[request], | ||||
options={ | options={"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)}, | ||||
"rescan": self.rescan in ( | |||||
Rescan.yes, | |||||
Rescan.late_timestamp)}, | |||||
) | ) | ||||
assert_equal(response, [{"success": True}]) | assert_equal(response, [{"success": True}]) | ||||
def check(self, txid=None, amount=None, confirmation_height=None): | def check(self, txid=None, amount=None, confirmation_height=None): | ||||
"""Verify that listtransactions/listreceivedbyaddress return expected values.""" | """Verify that listtransactions/listreceivedbyaddress return expected values.""" | ||||
txs = self.node.listtransactions( | txs = self.node.listtransactions( | ||||
label=self.label, count=10000, include_watchonly=True) | label=self.label, count=10000, include_watchonly=True | ||||
) | |||||
current_height = self.node.getblockcount() | current_height = self.node.getblockcount() | ||||
assert_equal(len(txs), self.expected_txs) | assert_equal(len(txs), self.expected_txs) | ||||
addresses = self.node.listreceivedbyaddress( | addresses = self.node.listreceivedbyaddress( | ||||
minconf=0, include_watchonly=True, address_filter=self.address['address']) | minconf=0, include_watchonly=True, address_filter=self.address["address"] | ||||
) | |||||
if self.expected_txs: | if self.expected_txs: | ||||
assert_equal(len(addresses[0]["txids"]), self.expected_txs) | assert_equal(len(addresses[0]["txids"]), self.expected_txs) | ||||
if txid is not None: | if txid is not None: | ||||
tx, = [tx for tx in txs if tx["txid"] == txid] | (tx,) = [tx for tx in txs if tx["txid"] == txid] | ||||
assert_equal(tx["label"], self.label) | assert_equal(tx["label"], self.label) | ||||
assert_equal(tx["address"], self.address["address"]) | assert_equal(tx["address"], self.address["address"]) | ||||
assert_equal(tx["amount"], amount) | assert_equal(tx["amount"], amount) | ||||
assert_equal(tx["category"], "receive") | assert_equal(tx["category"], "receive") | ||||
assert_equal(tx["label"], self.label) | assert_equal(tx["label"], self.label) | ||||
assert_equal(tx["txid"], txid) | assert_equal(tx["txid"], txid) | ||||
assert_equal(tx["confirmations"], | assert_equal(tx["confirmations"], 1 + current_height - confirmation_height) | ||||
1 + current_height - confirmation_height) | |||||
assert_equal("trusted" not in tx, True) | assert_equal("trusted" not in tx, True) | ||||
address, = [ad for ad in addresses if txid in ad["txids"]] | (address,) = [ad for ad in addresses if txid in ad["txids"]] | ||||
assert_equal(address["address"], self.address["address"]) | assert_equal(address["address"], self.address["address"]) | ||||
assert_equal(address["amount"], self.expected_balance) | assert_equal(address["amount"], self.expected_balance) | ||||
assert_equal(address["confirmations"], | assert_equal( | ||||
1 + current_height - confirmation_height) | address["confirmations"], 1 + current_height - confirmation_height | ||||
) | |||||
# Verify the transaction is correctly marked watchonly depending on | # Verify the transaction is correctly marked watchonly depending on | ||||
# whether the transaction pays to an imported public key or | # whether the transaction pays to an imported public key or | ||||
# imported private key. The test setup ensures that transaction | # imported private key. The test setup ensures that transaction | ||||
# inputs will not be from watchonly keys (important because | # inputs will not be from watchonly keys (important because | ||||
# involvesWatchonly will be true if either the transaction output | # involvesWatchonly will be true if either the transaction output | ||||
# or inputs are watchonly). | # or inputs are watchonly). | ||||
if self.data != Data.priv: | if self.data != Data.priv: | ||||
assert_equal(address["involvesWatchonly"], True) | assert_equal(address["involvesWatchonly"], True) | ||||
else: | else: | ||||
assert_equal("involvesWatchonly" not in address, True) | assert_equal("involvesWatchonly" not in address, True) | ||||
# List of Variants for each way a key or address could be imported. | # List of Variants for each way a key or address could be imported. | ||||
IMPORT_VARIANTS = [Variant(*variants) | IMPORT_VARIANTS = [ | ||||
for variants in itertools.product(Call, Data, Rescan, (False, True))] | 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, | # 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 | # 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 | # expected to cause rescans, and imports that are not expected to cause | ||||
# rescans, in order to prevent rescans during later imports picking up | # rescans, in order to prevent rescans during later imports picking up | ||||
# transactions associated with earlier imports. This makes it easier to keep | # transactions associated with earlier imports. This makes it easier to keep | ||||
# track of expected balances and transactions. | # track of expected balances and transactions. | ||||
ImportNode = collections.namedtuple("ImportNode", "prune rescan") | ImportNode = collections.namedtuple("ImportNode", "prune rescan") | ||||
IMPORT_NODES = [ImportNode(*fields) | IMPORT_NODES = [ | ||||
for fields in itertools.product((False, True), repeat=2)] | 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. | # Rescans start at the earliest block up to 2 hours before the key timestamp. | ||||
TIMESTAMP_WINDOW = 2 * 60 * 60 | TIMESTAMP_WINDOW = 2 * 60 * 60 | ||||
AMOUNT_DUST = 5.46 | AMOUNT_DUST = 5.46 | ||||
def get_rand_amount(): | def get_rand_amount(): | ||||
Show All 28 Lines | def setup_network(self): | ||||
self.connect_nodes(i, 0) | self.connect_nodes(i, 0) | ||||
def run_test(self): | def run_test(self): | ||||
# Create one transaction on node 0 with a unique amount for | # Create one transaction on node 0 with a unique amount for | ||||
# each possible type of wallet import RPC. | # each possible type of wallet import RPC. | ||||
for i, variant in enumerate(IMPORT_VARIANTS): | for i, variant in enumerate(IMPORT_VARIANTS): | ||||
variant.label = f"label {i} {variant}" | variant.label = f"label {i} {variant}" | ||||
variant.address = self.nodes[1].getaddressinfo( | variant.address = self.nodes[1].getaddressinfo( | ||||
self.nodes[1].getnewaddress(label=variant.label)) | self.nodes[1].getnewaddress(label=variant.label) | ||||
) | |||||
variant.key = self.nodes[1].dumpprivkey(variant.address["address"]) | variant.key = self.nodes[1].dumpprivkey(variant.address["address"]) | ||||
variant.initial_amount = get_rand_amount() | variant.initial_amount = get_rand_amount() | ||||
variant.initial_txid = self.nodes[0].sendtoaddress( | variant.initial_txid = self.nodes[0].sendtoaddress( | ||||
variant.address["address"], variant.initial_amount) | variant.address["address"], variant.initial_amount | ||||
) | |||||
# Generate one block for each send | # Generate one block for each send | ||||
self.generate(self.nodes[0], 1) | self.generate(self.nodes[0], 1) | ||||
variant.confirmation_height = self.nodes[0].getblockcount() | variant.confirmation_height = self.nodes[0].getblockcount() | ||||
variant.timestamp = self.nodes[0].getblockheader( | variant.timestamp = self.nodes[0].getblockheader( | ||||
self.nodes[0].getbestblockhash())["time"] | self.nodes[0].getbestblockhash() | ||||
)["time"] | |||||
# Generate a block further in the future (past the rescan window). | # Generate a block further in the future (past the rescan window). | ||||
assert_equal(self.nodes[0].getrawmempool(), []) | assert_equal(self.nodes[0].getrawmempool(), []) | ||||
set_node_times(self.nodes, self.nodes[0].getblockheader( | set_node_times( | ||||
self.nodes[0].getbestblockhash())["time"] + TIMESTAMP_WINDOW + 1) | self.nodes, | ||||
self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"] | |||||
+ TIMESTAMP_WINDOW | |||||
+ 1, | |||||
) | |||||
self.generate(self.nodes[0], 1) | self.generate(self.nodes[0], 1) | ||||
# For each variation of wallet key import, invoke the import RPC and | # For each variation of wallet key import, invoke the import RPC and | ||||
# check the results from getbalance and listtransactions. | # check the results from getbalance and listtransactions. | ||||
for variant in IMPORT_VARIANTS: | for variant in IMPORT_VARIANTS: | ||||
self.log.info(f'Run import for variant {variant}') | self.log.info(f"Run import for variant {variant}") | ||||
expect_rescan = variant.rescan == Rescan.yes | expect_rescan = variant.rescan == Rescan.yes | ||||
variant.node = self.nodes[2 + IMPORT_NODES.index( | variant.node = self.nodes[ | ||||
ImportNode(variant.prune, expect_rescan))] | 2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan)) | ||||
] | |||||
variant.do_import(variant.timestamp) | variant.do_import(variant.timestamp) | ||||
if expect_rescan: | if expect_rescan: | ||||
variant.expected_balance = variant.initial_amount | variant.expected_balance = variant.initial_amount | ||||
variant.expected_txs = 1 | variant.expected_txs = 1 | ||||
variant.check(variant.initial_txid, variant.initial_amount, | variant.check( | ||||
variant.confirmation_height) | variant.initial_txid, | ||||
variant.initial_amount, | |||||
variant.confirmation_height, | |||||
) | |||||
else: | else: | ||||
variant.expected_balance = 0 | variant.expected_balance = 0 | ||||
variant.expected_txs = 0 | variant.expected_txs = 0 | ||||
variant.check() | variant.check() | ||||
# Create new transactions sending to each address. | # Create new transactions sending to each address. | ||||
for i, variant in enumerate(IMPORT_VARIANTS): | for i, variant in enumerate(IMPORT_VARIANTS): | ||||
variant.sent_amount = get_rand_amount() | variant.sent_amount = get_rand_amount() | ||||
variant.sent_txid = self.nodes[0].sendtoaddress( | variant.sent_txid = self.nodes[0].sendtoaddress( | ||||
variant.address["address"], variant.sent_amount) | variant.address["address"], variant.sent_amount | ||||
) | |||||
# Generate one block for each send | # Generate one block for each send | ||||
self.generate(self.nodes[0], 1) | self.generate(self.nodes[0], 1) | ||||
variant.confirmation_height = self.nodes[0].getblockcount() | variant.confirmation_height = self.nodes[0].getblockcount() | ||||
assert_equal(self.nodes[0].getrawmempool(), []) | assert_equal(self.nodes[0].getrawmempool(), []) | ||||
self.sync_all() | self.sync_all() | ||||
# Check the latest results from getbalance and listtransactions. | # Check the latest results from getbalance and listtransactions. | ||||
for variant in IMPORT_VARIANTS: | for variant in IMPORT_VARIANTS: | ||||
self.log.info(f'Run check for variant {variant}') | self.log.info(f"Run check for variant {variant}") | ||||
variant.expected_balance += variant.sent_amount | variant.expected_balance += variant.sent_amount | ||||
variant.expected_txs += 1 | variant.expected_txs += 1 | ||||
variant.check(variant.sent_txid, variant.sent_amount, | variant.check( | ||||
variant.confirmation_height) | variant.sent_txid, variant.sent_amount, variant.confirmation_height | ||||
) | |||||
if __name__ == "__main__": | if __name__ == "__main__": | ||||
ImportRescanTest().main() | ImportRescanTest().main() |