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