rpc_blockchain.py raw

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