/ test / functional / rpc_blockchain.py
rpc_blockchain.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2014-present 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 RPCs related to blockchainstate.
  6  
  7  Test the following RPCs:
  8      - getblockchaininfo
  9      - getdeploymentinfo
 10      - getchaintxstats
 11      - gettxoutsetinfo
 12      - gettxout
 13      - getblockheader
 14      - getdifficulty
 15      - getnetworkhashps
 16      - waitforblockheight
 17      - getblock
 18      - getblockhash
 19      - getbestblockhash
 20      - verifychain
 21  
 22  Tests correspond to code in rpc/blockchain.cpp.
 23  """
 24  
 25  from decimal import Decimal
 26  import http.client
 27  import os
 28  import subprocess
 29  import textwrap
 30  
 31  from test_framework.blocktools import (
 32      MAX_FUTURE_BLOCK_TIME,
 33      TIME_GENESIS_BLOCK,
 34      REGTEST_N_BITS,
 35      REGTEST_TARGET,
 36      create_block,
 37      create_coinbase,
 38      create_tx_with_script,
 39      nbits_str,
 40      target_str,
 41  )
 42  from test_framework.messages import (
 43      CBlockHeader,
 44      COIN,
 45      from_hex,
 46      msg_block,
 47  )
 48  from test_framework.p2p import P2PInterface
 49  from test_framework.script import hash256, OP_TRUE
 50  from test_framework.test_framework import BitcoinTestFramework
 51  from test_framework.util import (
 52      assert_not_equal,
 53      assert_equal,
 54      assert_greater_than,
 55      assert_greater_than_or_equal,
 56      assert_raises,
 57      assert_raises_rpc_error,
 58      assert_is_hex_string,
 59      assert_is_hash_string,
 60  )
 61  from test_framework.wallet import MiniWallet
 62  
 63  
 64  HEIGHT = 200  # blocks mined
 65  TIME_RANGE_STEP = 600  # ten-minute steps
 66  TIME_RANGE_MTP = TIME_GENESIS_BLOCK + (HEIGHT - 6) * TIME_RANGE_STEP
 67  TIME_RANGE_TIP = TIME_GENESIS_BLOCK + (HEIGHT - 1) * TIME_RANGE_STEP
 68  TIME_RANGE_END = TIME_GENESIS_BLOCK + HEIGHT * TIME_RANGE_STEP
 69  DIFFICULTY_ADJUSTMENT_INTERVAL = 144
 70  
 71  
 72  class BlockchainTest(BitcoinTestFramework):
 73      def set_test_params(self):
 74          self.setup_clean_chain = True
 75          self.num_nodes = 1
 76          self.supports_cli = False
 77  
 78      def run_test(self):
 79          self.wallet = MiniWallet(self.nodes[0])
 80          self._test_prune_disk_space()
 81          self.mine_chain()
 82          self._test_max_future_block_time()
 83          self.restart_node(
 84              0,
 85              extra_args=[
 86                  "-stopatheight=207",
 87                  "-checkblocks=-1",  # Check all blocks
 88                  "-prune=1",  # Set pruning after rescan is complete
 89              ],
 90          )
 91  
 92          self._test_getblockchaininfo()
 93          self._test_getchaintxstats()
 94          self._test_gettxoutsetinfo()
 95          self._test_gettxout()
 96          self._test_getblockheader()
 97          self._test_getdifficulty()
 98          self._test_getnetworkhashps()
 99          self._test_stopatheight()
100          self._test_waitforblock() # also tests waitfornewblock
101          self._test_waitforblockheight()
102          self._test_getblock()
103          self._test_getdeploymentinfo()
104          self._test_verificationprogress()
105          self._test_y2106()
106          assert self.nodes[0].verifychain(4, 0)
107  
108      def mine_chain(self):
109          self.log.info(f"Generate {HEIGHT} blocks after the genesis block in ten-minute steps")
110          for t in range(TIME_GENESIS_BLOCK, TIME_RANGE_END, TIME_RANGE_STEP):
111              self.nodes[0].setmocktime(t)
112              self.generate(self.wallet, 1)
113          assert_equal(self.nodes[0].getblockchaininfo()['blocks'], HEIGHT)
114  
115      def _test_prune_disk_space(self):
116          self.log.info("Test that a manually pruned node does not run into "
117                        "integer overflow on first start up")
118          self.restart_node(0, extra_args=["-prune=1"])
119          self.log.info("Avoid warning when assumed chain size is enough")
120          self.restart_node(0, extra_args=["-prune=123456789"])
121  
122      def _test_max_future_block_time(self):
123          self.stop_node(0)
124          self.log.info("A block tip of more than MAX_FUTURE_BLOCK_TIME in the future raises an error")
125          self.nodes[0].assert_start_raises_init_error(
126              extra_args=[f"-mocktime={TIME_RANGE_TIP - MAX_FUTURE_BLOCK_TIME - 1}"],
127              expected_msg="The block database contains a block which appears to be from the future."
128              " This may be due to your computer's date and time being set incorrectly."
129              f" Only rebuild the block database if you are sure that your computer's date and time are correct.{os.linesep}"
130              "Please restart with -reindex or -reindex-chainstate to recover.",
131          )
132          self.log.info("A block tip of MAX_FUTURE_BLOCK_TIME in the future is fine")
133          self.start_node(0, extra_args=[f"-mocktime={TIME_RANGE_TIP - MAX_FUTURE_BLOCK_TIME}"])
134  
135      def _test_getblockchaininfo(self):
136          self.log.info("Test getblockchaininfo")
137  
138          keys = [
139              'bestblockhash',
140              'bits',
141              'blocks',
142              'chain',
143              'chainwork',
144              'difficulty',
145              'headers',
146              'initialblockdownload',
147              'mediantime',
148              'pruned',
149              'size_on_disk',
150              'target',
151              'time',
152              'verificationprogress',
153              'warnings',
154          ]
155          res = self.nodes[0].getblockchaininfo()
156  
157          assert_equal(res['time'], TIME_RANGE_END - TIME_RANGE_STEP)
158          assert_equal(res['mediantime'], TIME_RANGE_MTP)
159  
160          # result should have these additional pruning keys if manual pruning is enabled
161          assert_equal(sorted(res.keys()), sorted(['pruneheight', 'automatic_pruning'] + keys))
162  
163          # size_on_disk should be > 0
164          assert_greater_than(res['size_on_disk'], 0)
165  
166          # pruneheight should be greater or equal to 0
167          assert_greater_than_or_equal(res['pruneheight'], 0)
168  
169          # check other pruning fields given that prune=1
170          assert res['pruned']
171          assert not res['automatic_pruning']
172  
173          self.restart_node(0, ['-stopatheight=207'])
174          res = self.nodes[0].getblockchaininfo()
175          # should have exact keys
176          assert_equal(sorted(res.keys()), keys)
177  
178          self.stop_node(0)
179          self.nodes[0].assert_start_raises_init_error(
180              extra_args=['-testactivationheight=name@2'],
181              expected_msg='Error: Invalid name (name@2) for -testactivationheight=name@height.',
182          )
183          self.nodes[0].assert_start_raises_init_error(
184              extra_args=['-testactivationheight=bip34@-2'],
185              expected_msg='Error: Invalid height value (bip34@-2) for -testactivationheight=name@height.',
186          )
187          self.nodes[0].assert_start_raises_init_error(
188              extra_args=['-testactivationheight='],
189              expected_msg='Error: Invalid format () for -testactivationheight=name@height.',
190          )
191          self.start_node(0, extra_args=[
192              '-stopatheight=207',
193              '-prune=550',
194          ])
195  
196          res = self.nodes[0].getblockchaininfo()
197          # result should have these additional pruning keys if prune=550
198          assert_equal(sorted(res.keys()), sorted(['pruneheight', 'automatic_pruning', 'prune_target_size'] + keys))
199  
200          # check related fields
201          assert res['pruned']
202          assert_equal(res['pruneheight'], 0)
203          assert res['automatic_pruning']
204          assert_equal(res['prune_target_size'], 576716800)
205          assert_greater_than(res['size_on_disk'], 0)
206  
207          assert_equal(res['bits'], nbits_str(REGTEST_N_BITS))
208          assert_equal(res['target'], target_str(REGTEST_TARGET))
209  
210      def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash, status_next):
211          assert height >= 144 and height <= 287
212  
213          assert_equal(gdi_result, {
214            "hash": blockhash,
215            "height": height,
216            "script_flags": ["CHECKLOCKTIMEVERIFY","CHECKSEQUENCEVERIFY","DERSIG","NULLDUMMY","P2SH","TAPROOT","WITNESS"],
217            "deployments": {
218              'bip34': {'type': 'buried', 'active': True, 'height': 2},
219              'bip66': {'type': 'buried', 'active': True, 'height': 3},
220              'bip65': {'type': 'buried', 'active': True, 'height': 4},
221              'csv': {'type': 'buried', 'active': True, 'height': 5},
222              'segwit': {'type': 'buried', 'active': True, 'height': 6},
223              'testdummy': {
224                  'type': 'bip9',
225                  'bip9': {
226                      'bit': 28,
227                      'start_time': 0,
228                      'timeout': 0x7fffffffffffffff,  # testdummy does not have a timeout so is set to the max int64 value
229                      'min_activation_height': 0,
230                      'status': 'started',
231                      'status_next': status_next,
232                      'since': 144,
233                      'statistics': {
234                          'period': 144,
235                          'threshold': 108,
236                          'elapsed': height - 143,
237                          'count': height - 143,
238                          'possible': True,
239                      },
240                      'signalling': '#'*(height-143),
241                  },
242                  'active': False
243              },
244              'taproot': {
245                  'type': 'bip9',
246                  'bip9': {
247                      'start_time': -1,
248                      'timeout': 9223372036854775807,
249                      'min_activation_height': 0,
250                      'status': 'active',
251                      'status_next': 'active',
252                      'since': 0,
253                  },
254                  'height': 0,
255                  'active': True
256              }
257            }
258          })
259  
260      def _test_getdeploymentinfo(self):
261          # Note: continues past -stopatheight height, so must be invoked
262          # after _test_stopatheight
263  
264          self.log.info("Test getdeploymentinfo")
265          self.stop_node(0)
266          self.start_node(0, extra_args=[
267              '-testactivationheight=bip34@2',
268              '-testactivationheight=dersig@3',
269              '-testactivationheight=cltv@4',
270              '-testactivationheight=csv@5',
271              '-testactivationheight=segwit@6',
272          ])
273  
274          gbci207 = self.nodes[0].getblockchaininfo()
275          self.check_signalling_deploymentinfo_result(self.nodes[0].getdeploymentinfo(), gbci207["blocks"], gbci207["bestblockhash"], "started")
276  
277          # block just prior to lock in
278          self.generate(self.wallet, 287 - gbci207["blocks"])
279          gbci287 = self.nodes[0].getblockchaininfo()
280          self.check_signalling_deploymentinfo_result(self.nodes[0].getdeploymentinfo(), gbci287["blocks"], gbci287["bestblockhash"], "locked_in")
281  
282          # calling with an explicit hash works
283          self.check_signalling_deploymentinfo_result(self.nodes[0].getdeploymentinfo(gbci207["bestblockhash"]), gbci207["blocks"], gbci207["bestblockhash"], "started")
284  
285      def _test_verificationprogress(self):
286          self.log.info("Check that verificationprogress is less than 1 when the block tip is old")
287          future = 2 * 60 * 60
288          self.nodes[0].setmocktime(self.nodes[0].getblockchaininfo()["time"] + future + 1)
289          assert_greater_than(1, self.nodes[0].getblockchaininfo()["verificationprogress"])
290  
291          self.log.info("Check that verificationprogress is exactly 1 for a recent block tip")
292          self.nodes[0].setmocktime(self.nodes[0].getblockchaininfo()["time"] + future)
293          assert_equal(1, self.nodes[0].getblockchaininfo()["verificationprogress"])
294  
295          self.log.info("Check that verificationprogress is less than 1 as soon as a new header comes in")
296          self.nodes[0].submitheader(self.generateblock(self.nodes[0], output="raw(55)", transactions=[], submit=False, sync_fun=self.no_op)["hex"])
297          assert_greater_than(1, self.nodes[0].getblockchaininfo()["verificationprogress"])
298  
299      def _test_y2106(self):
300          self.log.info("Check that block timestamps work until year 2106")
301          self.generate(self.nodes[0], 8)[-1]
302          time_2106 = 2**32 - 1
303          self.nodes[0].setmocktime(time_2106)
304          last = self.generate(self.nodes[0], 6)[-1]
305          assert_equal(self.nodes[0].getblockheader(last)["mediantime"], time_2106)
306  
307      def _test_getchaintxstats(self):
308          self.log.info("Test getchaintxstats")
309  
310          # Test `getchaintxstats` invalid extra parameters
311          assert_raises_rpc_error(-1, 'getchaintxstats', self.nodes[0].getchaintxstats, 0, '', 0)
312  
313          # Test `getchaintxstats` invalid `nblocks`
314          assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].getchaintxstats, '')
315          assert_raises_rpc_error(-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[0].getchaintxstats, -1)
316          assert_raises_rpc_error(-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[0].getchaintxstats, self.nodes[0].getblockcount())
317  
318          # Test `getchaintxstats` invalid `blockhash`
319          assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].getchaintxstats, blockhash=0)
320          assert_raises_rpc_error(-8, "blockhash must be of length 64 (not 1, for '0')", self.nodes[0].getchaintxstats, blockhash='0')
321          assert_raises_rpc_error(-8, "blockhash must be hexadecimal string (not 'ZZZ0000000000000000000000000000000000000000000000000000000000000')", self.nodes[0].getchaintxstats, blockhash='ZZZ0000000000000000000000000000000000000000000000000000000000000')
322          assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getchaintxstats, blockhash='0000000000000000000000000000000000000000000000000000000000000000')
323          blockhash = self.nodes[0].getblockhash(HEIGHT)
324          self.nodes[0].invalidateblock(blockhash)
325          assert_raises_rpc_error(-8, "Block is not in main chain", self.nodes[0].getchaintxstats, blockhash=blockhash)
326          self.nodes[0].reconsiderblock(blockhash)
327  
328          chaintxstats = self.nodes[0].getchaintxstats(nblocks=1)
329          # 200 txs plus genesis tx
330          assert_equal(chaintxstats['txcount'], HEIGHT + 1)
331          # tx rate should be 1 per 10 minutes, or 1/600
332          # we have to round because of binary math
333          assert_equal(round(chaintxstats['txrate'] * TIME_RANGE_STEP, 10), Decimal(1))
334  
335          b1_hash = self.nodes[0].getblockhash(1)
336          b1 = self.nodes[0].getblock(b1_hash)
337          b200_hash = self.nodes[0].getblockhash(HEIGHT)
338          b200 = self.nodes[0].getblock(b200_hash)
339          time_diff = b200['mediantime'] - b1['mediantime']
340  
341          chaintxstats = self.nodes[0].getchaintxstats()
342          assert_equal(chaintxstats['time'], b200['time'])
343          assert_equal(chaintxstats['txcount'], HEIGHT + 1)
344          assert_equal(chaintxstats['window_final_block_hash'], b200_hash)
345          assert_equal(chaintxstats['window_final_block_height'], HEIGHT )
346          assert_equal(chaintxstats['window_block_count'], HEIGHT - 1)
347          assert_equal(chaintxstats['window_tx_count'], HEIGHT - 1)
348          assert_equal(chaintxstats['window_interval'], time_diff)
349          assert_equal(round(chaintxstats['txrate'] * time_diff, 10), Decimal(HEIGHT - 1))
350  
351          chaintxstats = self.nodes[0].getchaintxstats(blockhash=b1_hash)
352          assert_equal(chaintxstats['time'], b1['time'])
353          assert_equal(chaintxstats['txcount'], 2)
354          assert_equal(chaintxstats['window_final_block_hash'], b1_hash)
355          assert_equal(chaintxstats['window_final_block_height'], 1)
356          assert_equal(chaintxstats['window_block_count'], 0)
357          assert 'window_tx_count' not in chaintxstats
358          assert 'window_interval' not in chaintxstats
359          assert 'txrate' not in chaintxstats
360  
361      def _test_gettxoutsetinfo(self):
362          node = self.nodes[0]
363          res = node.gettxoutsetinfo()
364  
365          assert_equal(res['total_amount'], Decimal('8725.00000000'))
366          assert_equal(res['transactions'], HEIGHT)
367          assert_equal(res['height'], HEIGHT)
368          assert_equal(res['txouts'], HEIGHT)
369          assert_equal(res['bogosize'], 16800),
370          assert_equal(res['bestblock'], node.getblockhash(HEIGHT))
371          size = res['disk_size']
372          assert size > 6400
373          assert size < 64000
374          assert_equal(len(res['bestblock']), 64)
375          assert_equal(len(res['hash_serialized_3']), 64)
376  
377          self.log.info("Test gettxoutsetinfo works for blockchain with just the genesis block")
378          b1hash = node.getblockhash(1)
379          node.invalidateblock(b1hash)
380  
381          res2 = node.gettxoutsetinfo()
382          assert_equal(res2['transactions'], 0)
383          assert_equal(res2['total_amount'], Decimal('0'))
384          assert_equal(res2['height'], 0)
385          assert_equal(res2['txouts'], 0)
386          assert_equal(res2['bogosize'], 0),
387          assert_equal(res2['bestblock'], node.getblockhash(0))
388          assert_equal(len(res2['hash_serialized_3']), 64)
389  
390          self.log.info("Test gettxoutsetinfo returns the same result after invalidate/reconsider block")
391          node.reconsiderblock(b1hash)
392  
393          res3 = node.gettxoutsetinfo()
394          # The field 'disk_size' is non-deterministic and can thus not be
395          # compared between res and res3.  Everything else should be the same.
396          del res['disk_size'], res3['disk_size']
397          assert_equal(res, res3)
398  
399          self.log.info("Test gettxoutsetinfo hash_type option")
400          # Adding hash_type 'hash_serialized_3', which is the default, should
401          # not change the result.
402          res4 = node.gettxoutsetinfo(hash_type='hash_serialized_3')
403          del res4['disk_size']
404          assert_equal(res, res4)
405  
406          # hash_type none should not return a UTXO set hash.
407          res5 = node.gettxoutsetinfo(hash_type='none')
408          assert 'hash_serialized_3' not in res5
409  
410          # hash_type muhash should return a different UTXO set hash.
411          res6 = node.gettxoutsetinfo(hash_type='muhash')
412          assert 'muhash' in res6
413          assert_not_equal(res['hash_serialized_3'], res6['muhash'])
414  
415          # muhash should not be returned unless requested.
416          for r in [res, res2, res3, res4, res5]:
417              assert 'muhash' not in r
418  
419          # Unknown hash_type raises an error
420          assert_raises_rpc_error(-8, "'foo hash' is not a valid hash_type", node.gettxoutsetinfo, "foo hash")
421  
422      def _test_gettxout(self):
423          self.log.info("Validating gettxout RPC response")
424          node = self.nodes[0]
425  
426          # Get the best block hash and the block, which
427          # should only include the coinbase transaction.
428          best_block_hash = node.getbestblockhash()
429          block = node.getblock(best_block_hash)
430          assert_equal(block['nTx'], 1)
431  
432          # Get the transaction ID of the coinbase tx and
433          # the transaction output.
434          txid = block['tx'][0]
435          txout = node.gettxout(txid, 0)
436  
437          # Validate the gettxout response
438          assert_equal(txout['bestblock'], best_block_hash)
439          assert_equal(txout['confirmations'], 1)
440          assert_equal(txout['value'], 25)
441          assert_equal(txout['scriptPubKey']['address'], self.wallet.get_address())
442          assert_equal(txout['scriptPubKey']['hex'], self.wallet.get_output_script().hex())
443          decoded_script = node.decodescript(self.wallet.get_output_script().hex())
444          assert_equal(txout['scriptPubKey']['asm'], decoded_script['asm'])
445          assert_equal(txout['scriptPubKey']['desc'], decoded_script['desc'])
446          assert_equal(txout['scriptPubKey']['type'], decoded_script['type'])
447          assert_equal(txout['coinbase'], True)
448  
449      def _test_getblockheader(self):
450          self.log.info("Test getblockheader")
451          node = self.nodes[0]
452  
453          assert_raises_rpc_error(-8, "hash must be of length 64 (not 8, for 'nonsense')", node.getblockheader, "nonsense")
454          assert_raises_rpc_error(-8, "hash must be hexadecimal string (not 'ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844')", node.getblockheader, "ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844")
455          assert_raises_rpc_error(-5, "Block not found", node.getblockheader, "0cf7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844")
456  
457          besthash = node.getbestblockhash()
458          secondbesthash = node.getblockhash(HEIGHT - 1)
459          header = node.getblockheader(blockhash=besthash)
460  
461          assert_equal(header['hash'], besthash)
462          assert_equal(header['height'], HEIGHT)
463          assert_equal(header['confirmations'], 1)
464          assert_equal(header['previousblockhash'], secondbesthash)
465          assert_is_hex_string(header['chainwork'])
466          assert_equal(header['nTx'], 1)
467          assert_is_hash_string(header['hash'])
468          assert_is_hash_string(header['previousblockhash'])
469          assert_is_hash_string(header['merkleroot'])
470          assert_equal(header['bits'], nbits_str(REGTEST_N_BITS))
471          assert_equal(header['target'], target_str(REGTEST_TARGET))
472          assert isinstance(header['time'], int)
473          assert_equal(header['mediantime'], TIME_RANGE_MTP)
474          assert isinstance(header['nonce'], int)
475          assert isinstance(header['version'], int)
476          assert isinstance(int(header['versionHex'], 16), int)
477          assert isinstance(header['difficulty'], Decimal)
478  
479          # Test with verbose=False, which should return the header as hex.
480          header_hex = node.getblockheader(blockhash=besthash, verbose=False)
481          assert_is_hex_string(header_hex)
482  
483          header = from_hex(CBlockHeader(), header_hex)
484          assert_equal(header.hash_hex, besthash)
485  
486          assert 'previousblockhash' not in node.getblockheader(node.getblockhash(0))
487          assert 'nextblockhash' not in node.getblockheader(node.getbestblockhash())
488  
489      def _test_getdifficulty(self):
490          self.log.info("Test getdifficulty")
491          difficulty = self.nodes[0].getdifficulty()
492          # 1 hash in 2 should be valid, so difficulty should be 1/2**31
493          # binary => decimal => binary math is why we do this check
494          assert abs(difficulty * 2**31 - 1) < 0.0001
495  
496      def _test_getnetworkhashps(self):
497          self.log.info("Test getnetworkhashps")
498          assert_raises_rpc_error(
499              -3,
500              textwrap.dedent("""
501              Wrong type passed:
502              {
503                  "Position 1 (nblocks)": "JSON value of type string is not of expected type number",
504                  "Position 2 (height)": "JSON value of type array is not of expected type number"
505              }
506              """).strip(),
507              lambda: self.nodes[0].getnetworkhashps("a", []),
508          )
509          assert_raises_rpc_error(
510              -8,
511              "Block does not exist at specified height",
512              lambda: self.nodes[0].getnetworkhashps(100, self.nodes[0].getblockcount() + 1),
513          )
514          assert_raises_rpc_error(
515              -8,
516              "Block does not exist at specified height",
517              lambda: self.nodes[0].getnetworkhashps(100, -10),
518          )
519          assert_raises_rpc_error(
520              -8,
521              "Invalid nblocks. Must be a positive number or -1.",
522              lambda: self.nodes[0].getnetworkhashps(-100),
523          )
524          assert_raises_rpc_error(
525              -8,
526              "Invalid nblocks. Must be a positive number or -1.",
527              lambda: self.nodes[0].getnetworkhashps(0),
528          )
529  
530          # Genesis block height estimate should return 0
531          hashes_per_second = self.nodes[0].getnetworkhashps(100, 0)
532          assert_equal(hashes_per_second, 0)
533  
534          # This should be 2 hashes every 10 minutes or 1/300
535          hashes_per_second = self.nodes[0].getnetworkhashps()
536          assert abs(hashes_per_second * 300 - 1) < 0.0001
537  
538          # Test setting the first param of getnetworkhashps to -1 returns the average network
539          # hashes per second from the last difficulty change.
540          current_block_height = self.nodes[0].getmininginfo()['blocks']
541          blocks_since_last_diff_change = current_block_height % DIFFICULTY_ADJUSTMENT_INTERVAL + 1
542          expected_hashes_per_second_since_diff_change = self.nodes[0].getnetworkhashps(blocks_since_last_diff_change)
543  
544          assert_equal(self.nodes[0].getnetworkhashps(-1), expected_hashes_per_second_since_diff_change)
545  
546          # Ensure long lookups get truncated to chain length
547          hashes_per_second = self.nodes[0].getnetworkhashps(self.nodes[0].getblockcount() + 1000)
548          assert hashes_per_second > 0.003
549  
550      def _test_stopatheight(self):
551          self.log.info("Test stopping at height")
552          assert_equal(self.nodes[0].getblockcount(), HEIGHT)
553          self.generate(self.wallet, 6)
554          assert_equal(self.nodes[0].getblockcount(), HEIGHT + 6)
555          self.log.debug('Node should not stop at this height')
556          assert_raises(subprocess.TimeoutExpired, lambda: self.nodes[0].process.wait(timeout=3))
557          try:
558              self.generatetoaddress(self.nodes[0], 1, self.wallet.get_address(), sync_fun=self.no_op)
559          except (ConnectionError, http.client.BadStatusLine):
560              pass  # The node already shut down before response
561          self.log.debug('Node should stop at this height...')
562          self.nodes[0].wait_until_stopped()
563          self.start_node(0)
564          assert_equal(self.nodes[0].getblockcount(), HEIGHT + 7)
565  
566      def _test_waitforblock(self):
567          self.log.info("Test waitforblock and waitfornewblock")
568          node = self.nodes[0]
569  
570          current_height = node.getblock(node.getbestblockhash())['height']
571          current_hash = node.getblock(node.getbestblockhash())['hash']
572  
573          self.log.debug("Roll the chain back a few blocks and then reconsider it")
574          rollback_height = current_height - 100
575          rollback_hash = node.getblockhash(rollback_height)
576          rollback_header = node.getblockheader(rollback_hash)
577  
578          node.invalidateblock(rollback_hash)
579          assert_equal(node.getblockcount(), rollback_height - 1)
580  
581          self.log.debug("waitforblock should return the same block after its timeout")
582          assert_equal(node.waitforblock(blockhash=current_hash, timeout=1)['hash'], rollback_header['previousblockhash'])
583          assert_raises_rpc_error(-1, "Negative timeout", node.waitforblock, current_hash, -1)
584  
585          node.reconsiderblock(rollback_hash)
586          # The chain has probably already been restored by the time reconsiderblock returns,
587          # but poll anyway.
588          self.wait_until(lambda: node.waitforblock(blockhash=current_hash, timeout=100)['hash'] == current_hash)
589  
590          # roll back again
591          node.invalidateblock(rollback_hash)
592          assert_equal(node.getblockcount(), rollback_height - 1)
593  
594          node.reconsiderblock(rollback_hash)
595          # The chain has probably already been restored by the time reconsiderblock returns,
596          # but poll anyway.
597          self.wait_until(lambda: node.waitfornewblock(current_tip=rollback_header['previousblockhash'])['hash'] == current_hash)
598  
599          assert_raises_rpc_error(-1, "Negative timeout", node.waitfornewblock, -1)
600  
601      def _test_waitforblockheight(self):
602          self.log.info("Test waitforblockheight")
603          node = self.nodes[0]
604          peer = node.add_p2p_connection(P2PInterface())
605  
606          current_height = node.getblock(node.getbestblockhash())['height']
607  
608          # Create a fork somewhere below our current height, invalidate the tip
609          # of that fork, and then ensure that waitforblockheight still
610          # works as expected.
611          #
612          # (Previously this was broken based on setting
613          # `rpc/blockchain.cpp:latestblock` incorrectly.)
614          #
615          fork_height = current_height - 100 # choose something vaguely near our tip
616          fork_hash = node.getblockhash(fork_height)
617          fork_block = node.getblock(fork_hash)
618  
619          def solve_and_send_block(prevhash, height, time):
620              b = create_block(prevhash, create_coinbase(height), time)
621              b.solve()
622              peer.send_and_ping(msg_block(b))
623              return b
624  
625          b1 = solve_and_send_block(int(fork_hash, 16), fork_height+1, fork_block['time'] + 1)
626          b2 = solve_and_send_block(b1.hash_int, fork_height+2, b1.nTime + 1)
627  
628          node.invalidateblock(b2.hash_hex)
629  
630          def assert_waitforheight(height, timeout=2):
631              assert_equal(
632                  node.waitforblockheight(height=height, timeout=timeout)['height'],
633                  current_height)
634  
635          assert_waitforheight(0)
636          assert_waitforheight(current_height - 1)
637          assert_waitforheight(current_height)
638          assert_waitforheight(current_height + 1)
639          assert_raises_rpc_error(-1, "Negative timeout", node.waitforblockheight, current_height, -1)
640  
641      def _test_getblock(self):
642          node = self.nodes[0]
643          fee_per_byte = Decimal('0.00000010')
644          fee_per_kb = 1000 * fee_per_byte
645  
646          self.wallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node)
647          blockhash = self.generate(node, 1)[0]
648  
649          def assert_coinbase_metadata(hash, verbosity):
650              block = node.getblock(hash, verbosity)
651              coinbase_tx = node.getblock(hash, 2)["tx"][0]
652  
653              expected_keys = {"version", "locktime", "sequence", "coinbase"}
654              if "txinwitness" in coinbase_tx["vin"][0]:
655                  expected_keys.add("witness")
656              assert_equal(set(block["coinbase_tx"].keys()), expected_keys)
657  
658              assert_equal(block["coinbase_tx"]["version"], coinbase_tx["version"])
659              assert_equal(block["coinbase_tx"]["locktime"], coinbase_tx["locktime"])
660              assert_equal(block["coinbase_tx"]["sequence"], coinbase_tx["vin"][0]["sequence"])
661              assert_equal(block["coinbase_tx"]["coinbase"], coinbase_tx["vin"][0]["coinbase"])
662  
663              witness_stack = coinbase_tx["vin"][0].get("txinwitness")
664              if witness_stack is None:
665                  assert "witness" not in block["coinbase_tx"]
666              else:
667                  assert_equal(block["coinbase_tx"]["witness"], witness_stack[0])
668  
669          def assert_hexblock_hashes(verbosity):
670              block = node.getblock(blockhash, verbosity)
671              assert_equal(blockhash, hash256(bytes.fromhex(block[:160]))[::-1].hex())
672  
673          def assert_fee_not_in_block(hash, verbosity):
674              block = node.getblock(hash, verbosity)
675              assert 'fee' not in block['tx'][1]
676  
677          def assert_fee_in_block(hash, verbosity):
678              block = node.getblock(hash, verbosity)
679              tx = block['tx'][1]
680              assert 'fee' in tx
681              assert_equal(tx['fee'], tx['vsize'] * fee_per_byte)
682  
683          def assert_vin_contains_prevout(verbosity):
684              block = node.getblock(blockhash, verbosity)
685              tx = block["tx"][1]
686              total_vin = Decimal("0.00000000")
687              total_vout = Decimal("0.00000000")
688              for vin in tx["vin"]:
689                  assert "prevout" in vin
690                  assert_equal(set(vin["prevout"].keys()), set(("value", "height", "generated", "scriptPubKey")))
691                  assert_equal(vin["prevout"]["generated"], True)
692                  total_vin += vin["prevout"]["value"]
693              for vout in tx["vout"]:
694                  total_vout += vout["value"]
695              assert_equal(total_vin, total_vout + tx["fee"])
696  
697          def assert_vin_does_not_contain_prevout(hash, verbosity):
698              block = node.getblock(hash, verbosity)
699              tx = block["tx"][1]
700              if isinstance(tx, str):
701                  # In verbosity level 1, only the transaction hashes are written
702                  pass
703              else:
704                  for vin in tx["vin"]:
705                      assert "prevout" not in vin
706  
707          self.log.info("Test that getblock with verbosity 0 hashes to expected value")
708          assert_hexblock_hashes(0)
709          assert_hexblock_hashes(False)
710  
711          self.log.info("Test that getblock with verbosity 1 doesn't include fee")
712          assert_fee_not_in_block(blockhash, 1)
713          assert_fee_not_in_block(blockhash, True)
714  
715          self.log.info("Test getblock coinbase metadata fields")
716          assert_coinbase_metadata(blockhash, 1)
717  
718          self.log.info('Test that getblock with verbosity 2 and 3 includes expected fee')
719          assert_fee_in_block(blockhash, 2)
720          assert_fee_in_block(blockhash, 3)
721  
722          self.log.info("Test that getblock with verbosity 1 and 2 does not include prevout")
723          assert_vin_does_not_contain_prevout(blockhash, 1)
724          assert_vin_does_not_contain_prevout(blockhash, 2)
725  
726          self.log.info("Test that getblock with verbosity 3 includes prevout")
727          assert_vin_contains_prevout(3)
728  
729          self.log.info("Test getblock with invalid verbosity type returns proper error message")
730          assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", node.getblock, blockhash, "2")
731  
732          self.log.info("Test that getblock doesn't work with deleted Undo data")
733  
734          def move_block_file(old, new):
735              old_path = self.nodes[0].blocks_path / old
736              new_path = self.nodes[0].blocks_path / new
737              old_path.rename(new_path)
738  
739          # Move instead of deleting so we can restore chain state afterwards
740          move_block_file('rev00000.dat', 'rev_wrong')
741  
742          assert_raises_rpc_error(-32603, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.", lambda: node.getblock(blockhash, 2))
743          assert_raises_rpc_error(-32603, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.", lambda: node.getblock(blockhash, 3))
744  
745          # Restore chain state
746          move_block_file('rev_wrong', 'rev00000.dat')
747  
748          assert 'previousblockhash' not in node.getblock(node.getblockhash(0))
749          assert 'nextblockhash' not in node.getblock(node.getbestblockhash())
750  
751          self.log.info("Test getblock when only header is known")
752          current_height = node.getblock(node.getbestblockhash())['height']
753          block_time = node.getblock(node.getbestblockhash())['time'] + 1
754          block = create_block(int(blockhash, 16), create_coinbase(current_height + 1, nValue=100), block_time)
755          block.solve()
756          node.submitheader(block.serialize().hex())
757          assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", lambda: node.getblock(block.hash_hex))
758  
759          self.log.info("Test getblock when block data is available but undo data isn't")
760          # Submits a block building on the header-only block, so it can't be connected and has no undo data
761          tx = create_tx_with_script(block.vtx[0], 0, script_sig=bytes([OP_TRUE]), amount=50 * COIN)
762          block_noundo = create_block(block.hash_int, create_coinbase(current_height + 2, nValue=100), block_time + 1, txlist=[tx])
763          block_noundo.solve()
764          node.submitblock(block_noundo.serialize().hex())
765  
766          assert_fee_not_in_block(block_noundo.hash_hex, 2)
767          assert_fee_not_in_block(block_noundo.hash_hex, 3)
768          assert_vin_does_not_contain_prevout(block_noundo.hash_hex, 2)
769          assert_vin_does_not_contain_prevout(block_noundo.hash_hex, 3)
770  
771          self.log.info("Test getblock when block is missing")
772          move_block_file('blk00000.dat', 'blk00000.dat.bak')
773          assert_raises_rpc_error(-1, "Block not found on disk", node.getblock, blockhash)
774          move_block_file('blk00000.dat.bak', 'blk00000.dat')
775  
776  
777  if __name__ == '__main__':
778      BlockchainTest(__file__).main()