Remove accidental stray semicolon
[bitcoinplatinum.git] / test / functional / import-rescan.py
blobcb5b65c682559818ec34bca6d460baacb3c00a4f
1 #!/usr/bin/env python3
2 # Copyright (c) 2014-2016 The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """Test wallet import RPCs.
7 Test rescan behavior of importaddress, importpubkey, importprivkey, and
8 importmulti RPCs with different types of keys and rescan options.
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 other nodes 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.
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 """
22 from test_framework.authproxy import JSONRPCException
23 from test_framework.test_framework import BitcoinTestFramework
24 from test_framework.util import (connect_nodes, sync_blocks, assert_equal, set_node_times)
26 import collections
27 import enum
28 import itertools
30 Call = enum.Enum("Call", "single multi")
31 Data = enum.Enum("Data", "address pub priv")
32 Rescan = enum.Enum("Rescan", "no yes late_timestamp")
35 class Variant(collections.namedtuple("Variant", "call data rescan prune")):
36 """Helper for importing one key and verifying scanned transactions."""
38 def do_import(self, timestamp):
39 """Call one key import RPC."""
41 if self.call == Call.single:
42 if self.data == Data.address:
43 response, error = try_rpc(self.node.importaddress, self.address["address"], self.label,
44 self.rescan == Rescan.yes)
45 elif self.data == Data.pub:
46 response, error = try_rpc(self.node.importpubkey, self.address["pubkey"], self.label,
47 self.rescan == Rescan.yes)
48 elif self.data == Data.priv:
49 response, error = try_rpc(self.node.importprivkey, self.key, self.label, self.rescan == Rescan.yes)
50 assert_equal(response, None)
51 assert_equal(error, {'message': 'Rescan is disabled in pruned mode',
52 'code': -4} if self.expect_disabled else None)
53 elif self.call == Call.multi:
54 response = self.node.importmulti([{
55 "scriptPubKey": {
56 "address": self.address["address"]
58 "timestamp": timestamp + TIMESTAMP_WINDOW + (1 if self.rescan == Rescan.late_timestamp else 0),
59 "pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [],
60 "keys": [self.key] if self.data == Data.priv else [],
61 "label": self.label,
62 "watchonly": self.data != Data.priv
63 }], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)})
64 assert_equal(response, [{"success": True}])
66 def check(self, txid=None, amount=None, confirmations=None):
67 """Verify that getbalance/listtransactions return expected values."""
69 balance = self.node.getbalance(self.label, 0, True)
70 assert_equal(balance, self.expected_balance)
72 txs = self.node.listtransactions(self.label, 10000, 0, True)
73 assert_equal(len(txs), self.expected_txs)
75 if txid is not None:
76 tx, = [tx for tx in txs if tx["txid"] == txid]
77 assert_equal(tx["account"], self.label)
78 assert_equal(tx["address"], self.address["address"])
79 assert_equal(tx["amount"], amount)
80 assert_equal(tx["category"], "receive")
81 assert_equal(tx["label"], self.label)
82 assert_equal(tx["txid"], txid)
83 assert_equal(tx["confirmations"], confirmations)
84 assert_equal("trusted" not in tx, True)
85 # Verify the transaction is correctly marked watchonly depending on
86 # whether the transaction pays to an imported public key or
87 # imported private key. The test setup ensures that transaction
88 # inputs will not be from watchonly keys (important because
89 # involvesWatchonly will be true if either the transaction output
90 # or inputs are watchonly).
91 if self.data != Data.priv:
92 assert_equal(tx["involvesWatchonly"], True)
93 else:
94 assert_equal("involvesWatchonly" not in tx, True)
97 # List of Variants for each way a key or address could be imported.
98 IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Data, Rescan, (False, True))]
100 # List of nodes to import keys to. Half the nodes will have pruning disabled,
101 # half will have it enabled. Different nodes will be used for imports that are
102 # expected to cause rescans, and imports that are not expected to cause
103 # rescans, in order to prevent rescans during later imports picking up
104 # transactions associated with earlier imports. This makes it easier to keep
105 # track of expected balances and transactions.
106 ImportNode = collections.namedtuple("ImportNode", "prune rescan")
107 IMPORT_NODES = [ImportNode(*fields) for fields in itertools.product((False, True), repeat=2)]
109 # Rescans start at the earliest block up to 2 hours before the key timestamp.
110 TIMESTAMP_WINDOW = 2 * 60 * 60
113 class ImportRescanTest(BitcoinTestFramework):
114 def set_test_params(self):
115 self.num_nodes = 2 + len(IMPORT_NODES)
117 def setup_network(self):
118 extra_args = [[] for _ in range(self.num_nodes)]
119 for i, import_node in enumerate(IMPORT_NODES, 2):
120 if import_node.prune:
121 extra_args[i] += ["-prune=1"]
123 self.add_nodes(self.num_nodes, extra_args)
124 self.start_nodes()
125 for i in range(1, self.num_nodes):
126 connect_nodes(self.nodes[i], 0)
128 def run_test(self):
129 # Create one transaction on node 0 with a unique amount and label for
130 # each possible type of wallet import RPC.
131 for i, variant in enumerate(IMPORT_VARIANTS):
132 variant.label = "label {} {}".format(i, variant)
133 variant.address = self.nodes[1].validateaddress(self.nodes[1].getnewaddress(variant.label))
134 variant.key = self.nodes[1].dumpprivkey(variant.address["address"])
135 variant.initial_amount = 10 - (i + 1) / 4.0
136 variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount)
138 # Generate a block containing the initial transactions, then another
139 # block further in the future (past the rescan window).
140 self.nodes[0].generate(1)
141 assert_equal(self.nodes[0].getrawmempool(), [])
142 timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"]
143 set_node_times(self.nodes, timestamp + TIMESTAMP_WINDOW + 1)
144 self.nodes[0].generate(1)
145 sync_blocks(self.nodes)
147 # For each variation of wallet key import, invoke the import RPC and
148 # check the results from getbalance and listtransactions.
149 for variant in IMPORT_VARIANTS:
150 variant.expect_disabled = variant.rescan == Rescan.yes and variant.prune and variant.call == Call.single
151 expect_rescan = variant.rescan == Rescan.yes and not variant.expect_disabled
152 variant.node = self.nodes[2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))]
153 variant.do_import(timestamp)
154 if expect_rescan:
155 variant.expected_balance = variant.initial_amount
156 variant.expected_txs = 1
157 variant.check(variant.initial_txid, variant.initial_amount, 2)
158 else:
159 variant.expected_balance = 0
160 variant.expected_txs = 0
161 variant.check()
163 # Create new transactions sending to each address.
164 for i, variant in enumerate(IMPORT_VARIANTS):
165 variant.sent_amount = 10 - (2 * i + 1) / 8.0
166 variant.sent_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.sent_amount)
168 # Generate a block containing the new transactions.
169 self.nodes[0].generate(1)
170 assert_equal(self.nodes[0].getrawmempool(), [])
171 sync_blocks(self.nodes)
173 # Check the latest results from getbalance and listtransactions.
174 for variant in IMPORT_VARIANTS:
175 if not variant.expect_disabled:
176 variant.expected_balance += variant.sent_amount
177 variant.expected_txs += 1
178 variant.check(variant.sent_txid, variant.sent_amount, 1)
179 else:
180 variant.check()
183 def try_rpc(func, *args, **kwargs):
184 try:
185 return func(*args, **kwargs), None
186 except JSONRPCException as e:
187 return None, e.error
190 if __name__ == "__main__":
191 ImportRescanTest().main()