You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

import-rescan.py 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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.
  6. Test rescan behavior of importaddress, importpubkey, importprivkey, and
  7. importmulti RPCs with different types of keys and rescan options.
  8. In the first part of the test, node 0 creates an address for each type of
  9. import RPC call and sends BTC to it. Then other nodes import the addresses,
  10. and the test makes listtransactions and getbalance calls to confirm that the
  11. importing node either did or did not execute rescans picking up the send
  12. transactions.
  13. In the second part of the test, node 0 sends more BTC to each address, and the
  14. test makes more listtransactions and getbalance calls to confirm that the
  15. importing nodes pick up the new transactions regardless of whether rescans
  16. happened previously.
  17. """
  18. from test_framework.test_framework import BitcoinTestFramework
  19. from test_framework.util import (assert_raises_rpc_error, connect_nodes, sync_blocks, assert_equal, set_node_times)
  20. import collections
  21. import enum
  22. import itertools
  23. Call = enum.Enum("Call", "single multi")
  24. Data = enum.Enum("Data", "address pub priv")
  25. Rescan = enum.Enum("Rescan", "no yes late_timestamp")
  26. class Variant(collections.namedtuple("Variant", "call data rescan prune")):
  27. """Helper for importing one key and verifying scanned transactions."""
  28. def try_rpc(self, func, *args, **kwargs):
  29. if self.expect_disabled:
  30. assert_raises_rpc_error(-4, "Rescan is disabled in pruned mode", func, *args, **kwargs)
  31. else:
  32. return func(*args, **kwargs)
  33. def do_import(self, timestamp):
  34. """Call one key import RPC."""
  35. if self.call == Call.single:
  36. if self.data == Data.address:
  37. response = self.try_rpc(self.node.importaddress, self.address["address"], self.label,
  38. self.rescan == Rescan.yes)
  39. elif self.data == Data.pub:
  40. response = self.try_rpc(self.node.importpubkey, self.address["pubkey"], self.label,
  41. self.rescan == Rescan.yes)
  42. elif self.data == Data.priv:
  43. response = self.try_rpc(self.node.importprivkey, self.key, self.label, self.rescan == Rescan.yes)
  44. assert_equal(response, None)
  45. elif self.call == Call.multi:
  46. response = self.node.importmulti([{
  47. "scriptPubKey": {
  48. "address": self.address["address"]
  49. },
  50. "timestamp": timestamp + TIMESTAMP_WINDOW + (1 if self.rescan == Rescan.late_timestamp else 0),
  51. "pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [],
  52. "keys": [self.key] if self.data == Data.priv else [],
  53. "label": self.label,
  54. "watchonly": self.data != Data.priv
  55. }], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)})
  56. assert_equal(response, [{"success": True}])
  57. def check(self, txid=None, amount=None, confirmations=None):
  58. """Verify that getbalance/listtransactions return expected values."""
  59. balance = self.node.getbalance(self.label, 0, True)
  60. assert_equal(balance, self.expected_balance)
  61. txs = self.node.listtransactions(self.label, 10000, 0, True)
  62. assert_equal(len(txs), self.expected_txs)
  63. if txid is not None:
  64. tx, = [tx for tx in txs if tx["txid"] == txid]
  65. assert_equal(tx["account"], self.label)
  66. assert_equal(tx["address"], self.address["address"])
  67. assert_equal(tx["amount"], amount)
  68. assert_equal(tx["category"], "receive")
  69. assert_equal(tx["label"], self.label)
  70. assert_equal(tx["txid"], txid)
  71. assert_equal(tx["confirmations"], confirmations)
  72. assert_equal("trusted" not in tx, True)
  73. # Verify the transaction is correctly marked watchonly depending on
  74. # whether the transaction pays to an imported public key or
  75. # imported private key. The test setup ensures that transaction
  76. # inputs will not be from watchonly keys (important because
  77. # involvesWatchonly will be true if either the transaction output
  78. # or inputs are watchonly).
  79. if self.data != Data.priv:
  80. assert_equal(tx["involvesWatchonly"], True)
  81. else:
  82. assert_equal("involvesWatchonly" not in tx, True)
  83. # List of Variants for each way a key or address could be imported.
  84. IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Data, Rescan, (False, True))]
  85. # List of nodes to import keys to. Half the nodes will have pruning disabled,
  86. # half will have it enabled. Different nodes will be used for imports that are
  87. # expected to cause rescans, and imports that are not expected to cause
  88. # rescans, in order to prevent rescans during later imports picking up
  89. # transactions associated with earlier imports. This makes it easier to keep
  90. # track of expected balances and transactions.
  91. ImportNode = collections.namedtuple("ImportNode", "prune rescan")
  92. IMPORT_NODES = [ImportNode(*fields) for fields in itertools.product((False, True), repeat=2)]
  93. # Rescans start at the earliest block up to 2 hours before the key timestamp.
  94. TIMESTAMP_WINDOW = 2 * 60 * 60
  95. class ImportRescanTest(BitcoinTestFramework):
  96. def set_test_params(self):
  97. self.num_nodes = 2 + len(IMPORT_NODES)
  98. def setup_network(self):
  99. extra_args = [[] for _ in range(self.num_nodes)]
  100. for i, import_node in enumerate(IMPORT_NODES, 2):
  101. if import_node.prune:
  102. extra_args[i] += ["-prune=1"]
  103. self.add_nodes(self.num_nodes, extra_args)
  104. self.start_nodes()
  105. for i in range(1, self.num_nodes):
  106. connect_nodes(self.nodes[i], 0)
  107. def run_test(self):
  108. # Create one transaction on node 0 with a unique amount and label for
  109. # each possible type of wallet import RPC.
  110. for i, variant in enumerate(IMPORT_VARIANTS):
  111. variant.label = "label {} {}".format(i, variant)
  112. variant.address = self.nodes[1].validateaddress(self.nodes[1].getnewaddress(variant.label))
  113. variant.key = self.nodes[1].dumpprivkey(variant.address["address"])
  114. variant.initial_amount = 10 - (i + 1) / 4.0
  115. variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount)
  116. # Generate a block containing the initial transactions, then another
  117. # block further in the future (past the rescan window).
  118. self.nodes[0].generate(1)
  119. assert_equal(self.nodes[0].getrawmempool(), [])
  120. timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"]
  121. set_node_times(self.nodes, timestamp + TIMESTAMP_WINDOW + 1)
  122. self.nodes[0].generate(1)
  123. sync_blocks(self.nodes)
  124. # For each variation of wallet key import, invoke the import RPC and
  125. # check the results from getbalance and listtransactions.
  126. for variant in IMPORT_VARIANTS:
  127. variant.expect_disabled = variant.rescan == Rescan.yes and variant.prune and variant.call == Call.single
  128. expect_rescan = variant.rescan == Rescan.yes and not variant.expect_disabled
  129. variant.node = self.nodes[2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))]
  130. variant.do_import(timestamp)
  131. if expect_rescan:
  132. variant.expected_balance = variant.initial_amount
  133. variant.expected_txs = 1
  134. variant.check(variant.initial_txid, variant.initial_amount, 2)
  135. else:
  136. variant.expected_balance = 0
  137. variant.expected_txs = 0
  138. variant.check()
  139. # Create new transactions sending to each address.
  140. fee = self.nodes[0].getnetworkinfo()["relayfee"]
  141. for i, variant in enumerate(IMPORT_VARIANTS):
  142. variant.sent_amount = 10 - (2 * i + 1) / 8.0
  143. variant.sent_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.sent_amount)
  144. # Generate a block containing the new transactions.
  145. self.nodes[0].generate(1)
  146. assert_equal(self.nodes[0].getrawmempool(), [])
  147. sync_blocks(self.nodes)
  148. # Check the latest results from getbalance and listtransactions.
  149. for variant in IMPORT_VARIANTS:
  150. if not variant.expect_disabled:
  151. variant.expected_balance += variant.sent_amount
  152. variant.expected_txs += 1
  153. variant.check(variant.sent_txid, variant.sent_amount, 1)
  154. else:
  155. variant.check()
  156. if __name__ == "__main__":
  157. ImportRescanTest().main()