[tests] Add -blocknotify functional test
[bitcoinplatinum.git] / test / functional / import-rescan.py
blob6807fa66964dbfac610f74e683be4ef9b994c830
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.test_framework import BitcoinTestFramework
23 from test_framework.util import (assert_raises_rpc_error, connect_nodes, sync_blocks, assert_equal, set_node_times)
25 import collections
26 import enum
27 import itertools
29 Call = enum.Enum("Call", "single multi")
30 Data = enum.Enum("Data", "address pub priv")
31 Rescan = enum.Enum("Rescan", "no yes late_timestamp")
34 class Variant(collections.namedtuple("Variant", "call data rescan prune")):
35 """Helper for importing one key and verifying scanned transactions."""
37 def try_rpc(self, func, *args, **kwargs):
38 if self.expect_disabled:
39 assert_raises_rpc_error(-4, "Rescan is disabled in pruned mode", func, *args, **kwargs)
40 else:
41 return func(*args, **kwargs)
43 def do_import(self, timestamp):
44 """Call one key import RPC."""
46 if self.call == Call.single:
47 if self.data == Data.address:
48 response = self.try_rpc(self.node.importaddress, self.address["address"], self.label,
49 self.rescan == Rescan.yes)
50 elif self.data == Data.pub:
51 response = self.try_rpc(self.node.importpubkey, self.address["pubkey"], self.label,
52 self.rescan == Rescan.yes)
53 elif self.data == Data.priv:
54 response = self.try_rpc(self.node.importprivkey, self.key, self.label, self.rescan == Rescan.yes)
55 assert_equal(response, None)
57 elif self.call == Call.multi:
58 response = self.node.importmulti([{
59 "scriptPubKey": {
60 "address": self.address["address"]
62 "timestamp": timestamp + TIMESTAMP_WINDOW + (1 if self.rescan == Rescan.late_timestamp else 0),
63 "pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [],
64 "keys": [self.key] if self.data == Data.priv else [],
65 "label": self.label,
66 "watchonly": self.data != Data.priv
67 }], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)})
68 assert_equal(response, [{"success": True}])
70 def check(self, txid=None, amount=None, confirmations=None):
71 """Verify that getbalance/listtransactions return expected values."""
73 balance = self.node.getbalance(self.label, 0, True)
74 assert_equal(balance, self.expected_balance)
76 txs = self.node.listtransactions(self.label, 10000, 0, True)
77 assert_equal(len(txs), self.expected_txs)
79 if txid is not None:
80 tx, = [tx for tx in txs if tx["txid"] == txid]
81 assert_equal(tx["account"], self.label)
82 assert_equal(tx["address"], self.address["address"])
83 assert_equal(tx["amount"], amount)
84 assert_equal(tx["category"], "receive")
85 assert_equal(tx["label"], self.label)
86 assert_equal(tx["txid"], txid)
87 assert_equal(tx["confirmations"], confirmations)
88 assert_equal("trusted" not in tx, True)
89 # Verify the transaction is correctly marked watchonly depending on
90 # whether the transaction pays to an imported public key or
91 # imported private key. The test setup ensures that transaction
92 # inputs will not be from watchonly keys (important because
93 # involvesWatchonly will be true if either the transaction output
94 # or inputs are watchonly).
95 if self.data != Data.priv:
96 assert_equal(tx["involvesWatchonly"], True)
97 else:
98 assert_equal("involvesWatchonly" not in tx, True)
101 # List of Variants for each way a key or address could be imported.
102 IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Data, Rescan, (False, True))]
104 # List of nodes to import keys to. Half the nodes will have pruning disabled,
105 # half will have it enabled. Different nodes will be used for imports that are
106 # expected to cause rescans, and imports that are not expected to cause
107 # rescans, in order to prevent rescans during later imports picking up
108 # transactions associated with earlier imports. This makes it easier to keep
109 # track of expected balances and transactions.
110 ImportNode = collections.namedtuple("ImportNode", "prune rescan")
111 IMPORT_NODES = [ImportNode(*fields) for fields in itertools.product((False, True), repeat=2)]
113 # Rescans start at the earliest block up to 2 hours before the key timestamp.
114 TIMESTAMP_WINDOW = 2 * 60 * 60
117 class ImportRescanTest(BitcoinTestFramework):
118 def set_test_params(self):
119 self.num_nodes = 2 + len(IMPORT_NODES)
121 def setup_network(self):
122 extra_args = [[] for _ in range(self.num_nodes)]
123 for i, import_node in enumerate(IMPORT_NODES, 2):
124 if import_node.prune:
125 extra_args[i] += ["-prune=1"]
127 self.add_nodes(self.num_nodes, extra_args)
128 self.start_nodes()
129 for i in range(1, self.num_nodes):
130 connect_nodes(self.nodes[i], 0)
132 def run_test(self):
133 # Create one transaction on node 0 with a unique amount and label for
134 # each possible type of wallet import RPC.
135 for i, variant in enumerate(IMPORT_VARIANTS):
136 variant.label = "label {} {}".format(i, variant)
137 variant.address = self.nodes[1].validateaddress(self.nodes[1].getnewaddress(variant.label))
138 variant.key = self.nodes[1].dumpprivkey(variant.address["address"])
139 variant.initial_amount = 10 - (i + 1) / 4.0
140 variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount)
142 # Generate a block containing the initial transactions, then another
143 # block further in the future (past the rescan window).
144 self.nodes[0].generate(1)
145 assert_equal(self.nodes[0].getrawmempool(), [])
146 timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"]
147 set_node_times(self.nodes, timestamp + TIMESTAMP_WINDOW + 1)
148 self.nodes[0].generate(1)
149 sync_blocks(self.nodes)
151 # For each variation of wallet key import, invoke the import RPC and
152 # check the results from getbalance and listtransactions.
153 for variant in IMPORT_VARIANTS:
154 variant.expect_disabled = variant.rescan == Rescan.yes and variant.prune and variant.call == Call.single
155 expect_rescan = variant.rescan == Rescan.yes and not variant.expect_disabled
156 variant.node = self.nodes[2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))]
157 variant.do_import(timestamp)
158 if expect_rescan:
159 variant.expected_balance = variant.initial_amount
160 variant.expected_txs = 1
161 variant.check(variant.initial_txid, variant.initial_amount, 2)
162 else:
163 variant.expected_balance = 0
164 variant.expected_txs = 0
165 variant.check()
167 # Create new transactions sending to each address.
168 for i, variant in enumerate(IMPORT_VARIANTS):
169 variant.sent_amount = 10 - (2 * i + 1) / 8.0
170 variant.sent_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.sent_amount)
172 # Generate a block containing the new transactions.
173 self.nodes[0].generate(1)
174 assert_equal(self.nodes[0].getrawmempool(), [])
175 sync_blocks(self.nodes)
177 # Check the latest results from getbalance and listtransactions.
178 for variant in IMPORT_VARIANTS:
179 if not variant.expect_disabled:
180 variant.expected_balance += variant.sent_amount
181 variant.expected_txs += 1
182 variant.check(variant.sent_txid, variant.sent_amount, 1)
183 else:
184 variant.check()
186 if __name__ == "__main__":
187 ImportRescanTest().main()