Skip to content

Commit 8be0866

Browse files
committed
[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.
1 parent afae75f commit 8be0866

File tree

1 file changed

+112
-112
lines changed

1 file changed

+112
-112
lines changed

qa/rpc-tests/import-rescan.py

Lines changed: 112 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22
# Copyright (c) 2014-2016 The Bitcoin Core developers
33
# Distributed under the MIT software license, see the accompanying
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""Test rescan behavior of importaddress, importpubkey, importprivkey, and
6+
importmulti RPCs with different types of keys and rescan options.
7+
8+
Test uses three connected nodes.
9+
10+
In the first part of the test, node 0 creates an address for each type of
11+
import RPC call and sends BTC to it. Then nodes 1 and 2 import the addresses,
12+
and the test makes listtransactions and getbalance calls to confirm that the
13+
importing node either did or did not execute rescans picking up the send
14+
transactions.
15+
16+
In the second part of the test, node 0 sends more BTC to each address, and the
17+
test makes more listtransactions and getbalance calls to confirm that the
18+
importing nodes pick up the new transactions regardless of whether rescans
19+
happened previously.
20+
"""
521

622
from test_framework.test_framework import BitcoinTestFramework
723
from test_framework.util import (start_nodes, connect_nodes, sync_blocks, assert_equal)
@@ -10,53 +26,72 @@
1026
import collections
1127
import enum
1228
import itertools
13-
import functools
1429

1530
Call = enum.Enum("Call", "single multi")
1631
Data = enum.Enum("Data", "address pub priv")
17-
ImportNode = collections.namedtuple("ImportNode", "rescan")
18-
19-
20-
def call_import_rpc(call, data, address, scriptPubKey, pubkey, key, label, node, rescan):
21-
"""Helper that calls a wallet import RPC on a bitcoin node."""
22-
watchonly = data != Data.priv
23-
if call == Call.single:
24-
if data == Data.address:
25-
response = node.importaddress(address, label, rescan)
26-
elif data == Data.pub:
27-
response = node.importpubkey(pubkey, label, rescan)
28-
elif data == Data.priv:
29-
response = node.importprivkey(key, label, rescan)
30-
assert_equal(response, None)
31-
elif call == Call.multi:
32-
response = node.importmulti([{
33-
"scriptPubKey": {
34-
"address": address
35-
},
36-
"timestamp": "now",
37-
"pubkeys": [pubkey] if data == Data.pub else [],
38-
"keys": [key] if data == Data.priv else [],
39-
"label": label,
40-
"watchonly": watchonly
41-
}], {"rescan": rescan})
42-
assert_equal(response, [{"success": True}])
43-
return watchonly
44-
45-
46-
# List of RPCs that import a wallet key or address in various ways.
47-
IMPORT_RPCS = [functools.partial(call_import_rpc, call, data) for call, data in itertools.product(Call, Data)]
48-
49-
# List of bitcoind nodes that will import keys.
50-
IMPORT_NODES = [
51-
ImportNode(rescan=True),
52-
ImportNode(rescan=False),
53-
]
32+
Rescan = enum.Enum("Rescan", "no yes")
33+
34+
35+
class Variant(collections.namedtuple("Variant", "call data rescan")):
36+
"""Helper for importing one key and verifying scanned transactions."""
37+
38+
def do_import(self):
39+
"""Call one key import RPC."""
40+
41+
if self.call == Call.single:
42+
if self.data == Data.address:
43+
response = self.node.importaddress(self.address["address"], self.label, self.rescan == Rescan.yes)
44+
elif self.data == Data.pub:
45+
response = self.node.importpubkey(self.address["pubkey"], self.label, self.rescan == Rescan.yes)
46+
elif self.data == Data.priv:
47+
response = self.node.importprivkey(self.key, self.label, self.rescan == Rescan.yes)
48+
assert_equal(response, None)
49+
elif self.call == Call.multi:
50+
response = self.node.importmulti([{
51+
"scriptPubKey": {
52+
"address": self.address["address"]
53+
},
54+
"timestamp": "now",
55+
"pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [],
56+
"keys": [self.key] if self.data == Data.priv else [],
57+
"label": self.label,
58+
"watchonly": self.data != Data.priv
59+
}], {"rescan": self.rescan == Rescan.yes})
60+
assert_equal(response, [{"success": True}])
61+
62+
def check(self, txid=None, amount=None):
63+
"""Verify that getbalance/listtransactions return expected values."""
64+
65+
balance = self.node.getbalance(self.label, 0, True)
66+
assert_equal(balance, self.expected_balance)
67+
68+
txs = self.node.listtransactions(self.label, 10000, 0, True)
69+
assert_equal(len(txs), self.expected_txs)
70+
71+
if txid is not None:
72+
tx, = [tx for tx in txs if tx["txid"] == txid]
73+
assert_equal(tx["account"], self.label)
74+
assert_equal(tx["address"], self.address["address"])
75+
assert_equal(tx["amount"], amount)
76+
assert_equal(tx["category"], "receive")
77+
assert_equal(tx["label"], self.label)
78+
assert_equal(tx["txid"], txid)
79+
assert_equal(tx["confirmations"], 1)
80+
assert_equal("trusted" not in tx, True)
81+
if self.data != Data.priv:
82+
assert_equal(tx["involvesWatchonly"], True)
83+
else:
84+
assert_equal("involvesWatchonly" not in tx, True)
85+
86+
87+
# List of Variants for each way a key or address could be imported.
88+
IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Data, Rescan)]
5489

5590

5691
class ImportRescanTest(BitcoinTestFramework):
5792
def __init__(self):
5893
super().__init__()
59-
self.num_nodes = 1 + len(IMPORT_NODES)
94+
self.num_nodes = 3
6095

6196
def setup_network(self):
6297
extra_args = [["-debug=1"] for _ in range(self.num_nodes)]
@@ -67,89 +102,54 @@ def setup_network(self):
67102
def run_test(self):
68103
# Create one transaction on node 0 with a unique amount and label for
69104
# each possible type of wallet import RPC.
70-
import_rpc_variants = []
71-
for i, import_rpc in enumerate(IMPORT_RPCS):
72-
label = "label{}".format(i)
73-
addr = self.nodes[0].validateaddress(self.nodes[0].getnewaddress(label))
74-
key = self.nodes[0].dumpprivkey(addr["address"])
75-
amount = 24.9375 - i * .0625
76-
txid = self.nodes[0].sendtoaddress(addr["address"], amount)
77-
import_rpc = functools.partial(import_rpc, addr["address"], addr["scriptPubKey"], addr["pubkey"], key,
78-
label)
79-
import_rpc_variants.append((import_rpc, label, amount, txid, addr))
80-
105+
for i, variant in enumerate(IMPORT_VARIANTS):
106+
variant.label = "label {} {}".format(i, variant)
107+
variant.address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress(variant.label))
108+
variant.key = self.nodes[0].dumpprivkey(variant.address["address"])
109+
variant.initial_amount = 25 - (i + 1) / 4.0
110+
variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount)
111+
112+
# Generate a block containing the initial transactions.
81113
self.nodes[0].generate(1)
82114
assert_equal(self.nodes[0].getrawmempool(), [])
83115
sync_blocks(self.nodes)
84116

85-
# For each importing node and variation of wallet import RPC, invoke
86-
# the RPC and check the results from getbalance and listtransactions.
87-
for node, import_node in zip(self.nodes[1:], IMPORT_NODES):
88-
for import_rpc, label, amount, txid, addr in import_rpc_variants:
89-
watchonly = import_rpc(node, import_node.rescan)
90-
91-
balance = node.getbalance(label, 0, True)
92-
if import_node.rescan:
93-
assert_equal(balance, amount)
94-
else:
95-
assert_equal(balance, 0)
96-
97-
txs = node.listtransactions(label, 10000, 0, True)
98-
if import_node.rescan:
99-
assert_equal(len(txs), 1)
100-
assert_equal(txs[0]["account"], label)
101-
assert_equal(txs[0]["address"], addr["address"])
102-
assert_equal(txs[0]["amount"], amount)
103-
assert_equal(txs[0]["category"], "receive")
104-
assert_equal(txs[0]["label"], label)
105-
assert_equal(txs[0]["txid"], txid)
106-
assert_equal(txs[0]["confirmations"], 1)
107-
assert_equal("trusted" not in txs[0], True)
108-
if watchonly:
109-
assert_equal(txs[0]["involvesWatchonly"], True)
110-
else:
111-
assert_equal("involvesWatchonly" not in txs[0], True)
112-
else:
113-
assert_equal(len(txs), 0)
114-
115-
# Create spends for all the imported addresses.
116-
spend_txids = []
117+
# For each variation of wallet key import, invoke the import RPC and
118+
# check the results from getbalance and listtransactions. Import to
119+
# node 1 if rescanning is expected, and to node 2 if rescanning is not
120+
# expected. Node 2 is reserved for imports that do not cause rescans,
121+
# so later import calls don't inadvertently cause the wallet to pick up
122+
# transactions from earlier import calls where a rescan was not
123+
# expected (this would make it complicated to figure out expected
124+
# balances in the second part of the test.)
125+
for variant in IMPORT_VARIANTS:
126+
variant.node = self.nodes[1 if variant.rescan == Rescan.yes else 2]
127+
variant.do_import()
128+
if variant.rescan == Rescan.yes:
129+
variant.expected_balance = variant.initial_amount
130+
variant.expected_txs = 1
131+
variant.check(variant.initial_txid, variant.initial_amount)
132+
else:
133+
variant.expected_balance = 0
134+
variant.expected_txs = 0
135+
variant.check()
136+
137+
# Create new transactions sending to each address.
117138
fee = self.nodes[0].getnetworkinfo()["relayfee"]
118-
for import_rpc, label, amount, txid, addr in import_rpc_variants:
119-
raw_tx = self.nodes[0].getrawtransaction(txid)
120-
decoded_tx = self.nodes[0].decoderawtransaction(raw_tx)
121-
input_vout = next(out["n"] for out in decoded_tx["vout"]
122-
if out["scriptPubKey"]["addresses"] == [addr["address"]])
123-
inputs = [{"txid": txid, "vout": input_vout}]
124-
outputs = {self.nodes[0].getnewaddress(): Decimal(amount) - fee}
125-
raw_spend_tx = self.nodes[0].createrawtransaction(inputs, outputs)
126-
signed_spend_tx = self.nodes[0].signrawtransaction(raw_spend_tx)
127-
spend_txid = self.nodes[0].sendrawtransaction(signed_spend_tx["hex"])
128-
spend_txids.append(spend_txid)
139+
for i, variant in enumerate(IMPORT_VARIANTS):
140+
variant.sent_amount = 25 - (2 * i + 1) / 8.0
141+
variant.sent_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.sent_amount)
129142

143+
# Generate a block containing the new transactions.
130144
self.nodes[0].generate(1)
131145
assert_equal(self.nodes[0].getrawmempool(), [])
132146
sync_blocks(self.nodes)
133147

134-
# Check the results from getbalance and listtransactions after the spends.
135-
for node, import_node in zip(self.nodes[1:], IMPORT_NODES):
136-
txs = node.listtransactions("*", 10000, 0, True)
137-
for (import_rpc, label, amount, txid, addr), spend_txid in zip(import_rpc_variants, spend_txids):
138-
balance = node.getbalance(label, 0, True)
139-
spend_tx = [tx for tx in txs if tx["txid"] == spend_txid]
140-
if import_node.rescan:
141-
assert_equal(balance, amount)
142-
assert_equal(len(spend_tx), 1)
143-
assert_equal(spend_tx[0]["account"], "")
144-
assert_equal(spend_tx[0]["amount"] + spend_tx[0]["fee"], -amount)
145-
assert_equal(spend_tx[0]["category"], "send")
146-
assert_equal("label" not in spend_tx[0], True)
147-
assert_equal(spend_tx[0]["confirmations"], 1)
148-
assert_equal("trusted" not in spend_tx[0], True)
149-
assert_equal("involvesWatchonly" not in txs[0], True)
150-
else:
151-
assert_equal(balance, 0)
152-
assert_equal(spend_tx, [])
148+
# Check the latest results from getbalance and listtransactions.
149+
for variant in IMPORT_VARIANTS:
150+
variant.expected_balance += variant.sent_amount
151+
variant.expected_txs += 1
152+
variant.check(variant.sent_txid, variant.sent_amount)
153153

154154

155155
if __name__ == "__main__":

0 commit comments

Comments
 (0)