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.

listsinceblock.py 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2017 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 the listsincelast RPC."""
  6. from test_framework.test_framework import BitcoinTestFramework
  7. from test_framework.util import assert_equal, assert_array_result, assert_raises_rpc_error
  8. class ListSinceBlockTest (BitcoinTestFramework):
  9. def set_test_params(self):
  10. self.num_nodes = 4
  11. self.setup_clean_chain = True
  12. def run_test(self):
  13. self.nodes[2].generate(101)
  14. self.sync_all()
  15. self.test_no_blockhash()
  16. self.test_invalid_blockhash()
  17. self.test_reorg()
  18. self.test_double_spend()
  19. self.test_double_send()
  20. def test_no_blockhash(self):
  21. txid = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1)
  22. blockhash, = self.nodes[2].generate(1)
  23. self.sync_all()
  24. txs = self.nodes[0].listtransactions()
  25. assert_array_result(txs, {"txid": txid}, {
  26. "category": "receive",
  27. "amount": 1,
  28. "blockhash": blockhash,
  29. "confirmations": 1,
  30. })
  31. assert_equal(
  32. self.nodes[0].listsinceblock(),
  33. {"lastblock": blockhash,
  34. "removed": [],
  35. "transactions": txs})
  36. assert_equal(
  37. self.nodes[0].listsinceblock(""),
  38. {"lastblock": blockhash,
  39. "removed": [],
  40. "transactions": txs})
  41. def test_invalid_blockhash(self):
  42. assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock,
  43. "42759cde25462784395a337460bde75f58e73d3f08bd31fdc3507cbac856a2c4")
  44. assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock,
  45. "0000000000000000000000000000000000000000000000000000000000000000")
  46. assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock,
  47. "invalid-hex")
  48. def test_reorg(self):
  49. '''
  50. `listsinceblock` did not behave correctly when handed a block that was
  51. no longer in the main chain:
  52. ab0
  53. / \
  54. aa1 [tx0] bb1
  55. | |
  56. aa2 bb2
  57. | |
  58. aa3 bb3
  59. |
  60. bb4
  61. Consider a client that has only seen block `aa3` above. It asks the node
  62. to `listsinceblock aa3`. But at some point prior the main chain switched
  63. to the bb chain.
  64. Previously: listsinceblock would find height=4 for block aa3 and compare
  65. this to height=5 for the tip of the chain (bb4). It would then return
  66. results restricted to bb3-bb4.
  67. Now: listsinceblock finds the fork at ab0 and returns results in the
  68. range bb1-bb4.
  69. This test only checks that [tx0] is present.
  70. '''
  71. # Split network into two
  72. self.split_network()
  73. # send to nodes[0] from nodes[2]
  74. senttx = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1)
  75. # generate on both sides
  76. lastblockhash = self.nodes[1].generate(6)[5]
  77. self.nodes[2].generate(7)
  78. self.log.info('lastblockhash=%s' % (lastblockhash))
  79. self.sync_all([self.nodes[:2], self.nodes[2:]])
  80. self.join_network()
  81. # listsinceblock(lastblockhash) should now include tx, as seen from nodes[0]
  82. lsbres = self.nodes[0].listsinceblock(lastblockhash)
  83. found = False
  84. for tx in lsbres['transactions']:
  85. if tx['txid'] == senttx:
  86. found = True
  87. break
  88. assert found
  89. def test_double_spend(self):
  90. '''
  91. This tests the case where the same UTXO is spent twice on two separate
  92. blocks as part of a reorg.
  93. ab0
  94. / \
  95. aa1 [tx1] bb1 [tx2]
  96. | |
  97. aa2 bb2
  98. | |
  99. aa3 bb3
  100. |
  101. bb4
  102. Problematic case:
  103. 1. User 1 receives BTC in tx1 from utxo1 in block aa1.
  104. 2. User 2 receives BTC in tx2 from utxo1 (same) in block bb1
  105. 3. User 1 sees 2 confirmations at block aa3.
  106. 4. Reorg into bb chain.
  107. 5. User 1 asks `listsinceblock aa3` and does not see that tx1 is now
  108. invalidated.
  109. Currently the solution to this is to detect that a reorg'd block is
  110. asked for in listsinceblock, and to iterate back over existing blocks up
  111. until the fork point, and to include all transactions that relate to the
  112. node wallet.
  113. '''
  114. self.sync_all()
  115. # Split network into two
  116. self.split_network()
  117. # share utxo between nodes[1] and nodes[2]
  118. utxos = self.nodes[2].listunspent()
  119. utxo = utxos[0]
  120. privkey = self.nodes[2].dumpprivkey(utxo['address'])
  121. self.nodes[1].importprivkey(privkey)
  122. # send from nodes[1] using utxo to nodes[0]
  123. change = '%.8f' % (float(utxo['amount']) - 1.0003)
  124. recipientDict = {
  125. self.nodes[0].getnewaddress(): 1,
  126. self.nodes[1].getnewaddress(): change,
  127. }
  128. utxoDicts = [{
  129. 'txid': utxo['txid'],
  130. 'vout': utxo['vout'],
  131. }]
  132. txid1 = self.nodes[1].sendrawtransaction(
  133. self.nodes[1].signrawtransaction(
  134. self.nodes[1].createrawtransaction(utxoDicts, recipientDict))['hex'])
  135. # send from nodes[2] using utxo to nodes[3]
  136. recipientDict2 = {
  137. self.nodes[3].getnewaddress(): 1,
  138. self.nodes[2].getnewaddress(): change,
  139. }
  140. self.nodes[2].sendrawtransaction(
  141. self.nodes[2].signrawtransaction(
  142. self.nodes[2].createrawtransaction(utxoDicts, recipientDict2))['hex'])
  143. # generate on both sides
  144. lastblockhash = self.nodes[1].generate(3)[2]
  145. self.nodes[2].generate(4)
  146. self.join_network()
  147. self.sync_all()
  148. # gettransaction should work for txid1
  149. assert self.nodes[0].gettransaction(txid1)['txid'] == txid1, "gettransaction failed to find txid1"
  150. # listsinceblock(lastblockhash) should now include txid1, as seen from nodes[0]
  151. lsbres = self.nodes[0].listsinceblock(lastblockhash)
  152. assert any(tx['txid'] == txid1 for tx in lsbres['removed'])
  153. # but it should not include 'removed' if include_removed=false
  154. lsbres2 = self.nodes[0].listsinceblock(blockhash=lastblockhash, include_removed=False)
  155. assert 'removed' not in lsbres2
  156. def test_double_send(self):
  157. '''
  158. This tests the case where the same transaction is submitted twice on two
  159. separate blocks as part of a reorg. The former will vanish and the
  160. latter will appear as the true transaction (with confirmations dropping
  161. as a result).
  162. ab0
  163. / \
  164. aa1 [tx1] bb1
  165. | |
  166. aa2 bb2
  167. | |
  168. aa3 bb3 [tx1]
  169. |
  170. bb4
  171. Asserted:
  172. 1. tx1 is listed in listsinceblock.
  173. 2. It is included in 'removed' as it was removed, even though it is now
  174. present in a different block.
  175. 3. It is listed with a confirmations count of 2 (bb3, bb4), not
  176. 3 (aa1, aa2, aa3).
  177. '''
  178. self.sync_all()
  179. # Split network into two
  180. self.split_network()
  181. # create and sign a transaction
  182. utxos = self.nodes[2].listunspent()
  183. utxo = utxos[0]
  184. change = '%.8f' % (float(utxo['amount']) - 1.0003)
  185. recipientDict = {
  186. self.nodes[0].getnewaddress(): 1,
  187. self.nodes[2].getnewaddress(): change,
  188. }
  189. utxoDicts = [{
  190. 'txid': utxo['txid'],
  191. 'vout': utxo['vout'],
  192. }]
  193. signedtxres = self.nodes[2].signrawtransaction(
  194. self.nodes[2].createrawtransaction(utxoDicts, recipientDict))
  195. assert signedtxres['complete']
  196. signedtx = signedtxres['hex']
  197. # send from nodes[1]; this will end up in aa1
  198. txid1 = self.nodes[1].sendrawtransaction(signedtx)
  199. # generate bb1-bb2 on right side
  200. self.nodes[2].generate(2)
  201. # send from nodes[2]; this will end up in bb3
  202. txid2 = self.nodes[2].sendrawtransaction(signedtx)
  203. assert_equal(txid1, txid2)
  204. # generate on both sides
  205. lastblockhash = self.nodes[1].generate(3)[2]
  206. self.nodes[2].generate(2)
  207. self.join_network()
  208. self.sync_all()
  209. # gettransaction should work for txid1
  210. self.nodes[0].gettransaction(txid1)
  211. # listsinceblock(lastblockhash) should now include txid1 in transactions
  212. # as well as in removed
  213. lsbres = self.nodes[0].listsinceblock(lastblockhash)
  214. assert any(tx['txid'] == txid1 for tx in lsbres['transactions'])
  215. assert any(tx['txid'] == txid1 for tx in lsbres['removed'])
  216. # find transaction and ensure confirmations is valid
  217. for tx in lsbres['transactions']:
  218. if tx['txid'] == txid1:
  219. assert_equal(tx['confirmations'], 2)
  220. # the same check for the removed array; confirmations should STILL be 2
  221. for tx in lsbres['removed']:
  222. if tx['txid'] == txid1:
  223. assert_equal(tx['confirmations'], 2)
  224. if __name__ == '__main__':
  225. ListSinceBlockTest().main()