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_y2106() 105 assert self.nodes[0].verifychain(4, 0) 106 107 def mine_chain(self): 108 self.log.info(f"Generate {HEIGHT} blocks after the genesis block in ten-minute steps") 109 for t in range(TIME_GENESIS_BLOCK, TIME_RANGE_END, TIME_RANGE_STEP): 110 self.nodes[0].setmocktime(t) 111 self.generate(self.wallet, 1) 112 assert_equal(self.nodes[0].getblockchaininfo()['blocks'], HEIGHT) 113 114 def _test_prune_disk_space(self): 115 self.log.info("Test that a manually pruned node does not run into " 116 "integer overflow on first start up") 117 self.restart_node(0, extra_args=["-prune=1"]) 118 self.log.info("Avoid warning when assumed chain size is enough") 119 self.restart_node(0, extra_args=["-prune=123456789"]) 120 121 def _test_max_future_block_time(self): 122 self.stop_node(0) 123 self.log.info("A block tip of more than MAX_FUTURE_BLOCK_TIME in the future raises an error") 124 self.nodes[0].assert_start_raises_init_error( 125 extra_args=[f"-mocktime={TIME_RANGE_TIP - MAX_FUTURE_BLOCK_TIME - 1}"], 126 expected_msg=": The block database contains a block which appears to be from the future." 127 " This may be due to your computer's date and time being set incorrectly." 128 f" Only rebuild the block database if you are sure that your computer's date and time are correct.{os.linesep}" 129 "Please restart with -reindex or -reindex-chainstate to recover.", 130 ) 131 self.log.info("A block tip of MAX_FUTURE_BLOCK_TIME in the future is fine") 132 self.start_node(0, extra_args=[f"-mocktime={TIME_RANGE_TIP - MAX_FUTURE_BLOCK_TIME}"]) 133 134 def _test_getblockchaininfo(self): 135 self.log.info("Test getblockchaininfo") 136 137 keys = [ 138 'bestblockhash', 139 'bits', 140 'blocks', 141 'chain', 142 'chainwork', 143 'difficulty', 144 'headers', 145 'initialblockdownload', 146 'mediantime', 147 'pruned', 148 'size_on_disk', 149 'target', 150 'time', 151 'verificationprogress', 152 'warnings', 153 ] 154 res = self.nodes[0].getblockchaininfo() 155 156 assert_equal(res['time'], TIME_RANGE_END - TIME_RANGE_STEP) 157 assert_equal(res['mediantime'], TIME_RANGE_MTP) 158 159 # result should have these additional pruning keys if manual pruning is enabled 160 assert_equal(sorted(res.keys()), sorted(['pruneheight', 'automatic_pruning'] + keys)) 161 162 # size_on_disk should be > 0 163 assert_greater_than(res['size_on_disk'], 0) 164 165 # pruneheight should be greater or equal to 0 166 assert_greater_than_or_equal(res['pruneheight'], 0) 167 168 # check other pruning fields given that prune=1 169 assert res['pruned'] 170 assert not res['automatic_pruning'] 171 172 self.restart_node(0, ['-stopatheight=207']) 173 res = self.nodes[0].getblockchaininfo() 174 # should have exact keys 175 assert_equal(sorted(res.keys()), keys) 176 177 self.stop_node(0) 178 self.nodes[0].assert_start_raises_init_error( 179 extra_args=['-testactivationheight=name@2'], 180 expected_msg='Error: Invalid name (name@2) for -testactivationheight=name@height.', 181 ) 182 self.nodes[0].assert_start_raises_init_error( 183 extra_args=['-testactivationheight=bip34@-2'], 184 expected_msg='Error: Invalid height value (bip34@-2) for -testactivationheight=name@height.', 185 ) 186 self.nodes[0].assert_start_raises_init_error( 187 extra_args=['-testactivationheight='], 188 expected_msg='Error: Invalid format () for -testactivationheight=name@height.', 189 ) 190 self.start_node(0, extra_args=[ 191 '-stopatheight=207', 192 '-prune=550', 193 ]) 194 195 res = self.nodes[0].getblockchaininfo() 196 # result should have these additional pruning keys if prune=550 197 assert_equal(sorted(res.keys()), sorted(['pruneheight', 'automatic_pruning', 'prune_target_size'] + keys)) 198 199 # check related fields 200 assert res['pruned'] 201 assert_equal(res['pruneheight'], 0) 202 assert res['automatic_pruning'] 203 assert_equal(res['prune_target_size'], 576716800) 204 assert_greater_than(res['size_on_disk'], 0) 205 206 assert_equal(res['bits'], nbits_str(REGTEST_N_BITS)) 207 assert_equal(res['target'], target_str(REGTEST_TARGET)) 208 209 def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash, status_next): 210 assert height >= 144 and height <= 287 211 212 assert_equal(gdi_result, { 213 "hash": blockhash, 214 "height": height, 215 "deployments": { 216 'bip34': {'type': 'buried', 'active': True, 'height': 2}, 217 'bip66': {'type': 'buried', 'active': True, 'height': 3}, 218 'bip65': {'type': 'buried', 'active': True, 'height': 4}, 219 'csv': {'type': 'buried', 'active': True, 'height': 5}, 220 'segwit': {'type': 'buried', 'active': True, 'height': 6}, 221 'testdummy': { 222 'type': 'bip9', 223 'bip9': { 224 'bit': 28, 225 'start_time': 0, 226 'timeout': 0x7fffffffffffffff, # testdummy does not have a timeout so is set to the max int64 value 227 'min_activation_height': 0, 228 'status': 'started', 229 'status_next': status_next, 230 'since': 144, 231 'statistics': { 232 'period': 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_not_equal(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(timeout=100)['hash'] == current_hash) 583 assert_raises_rpc_error(-1, "Negative timeout", node.waitfornewblock, -1) 584 585 def _test_waitforblockheight(self): 586 self.log.info("Test waitforblockheight") 587 node = self.nodes[0] 588 peer = node.add_p2p_connection(P2PInterface()) 589 590 current_height = node.getblock(node.getbestblockhash())['height'] 591 592 # Create a fork somewhere below our current height, invalidate the tip 593 # of that fork, and then ensure that waitforblockheight still 594 # works as expected. 595 # 596 # (Previously this was broken based on setting 597 # `rpc/blockchain.cpp:latestblock` incorrectly.) 598 # 599 fork_height = current_height - 100 # choose something vaguely near our tip 600 fork_hash = node.getblockhash(fork_height) 601 fork_block = node.getblock(fork_hash) 602 603 def solve_and_send_block(prevhash, height, time): 604 b = create_block(prevhash, create_coinbase(height), time) 605 b.solve() 606 peer.send_and_ping(msg_block(b)) 607 return b 608 609 b1 = solve_and_send_block(int(fork_hash, 16), fork_height+1, fork_block['time'] + 1) 610 b2 = solve_and_send_block(b1.sha256, fork_height+2, b1.nTime + 1) 611 612 node.invalidateblock(b2.hash) 613 614 def assert_waitforheight(height, timeout=2): 615 assert_equal( 616 node.waitforblockheight(height=height, timeout=timeout)['height'], 617 current_height) 618 619 assert_waitforheight(0) 620 assert_waitforheight(current_height - 1) 621 assert_waitforheight(current_height) 622 assert_waitforheight(current_height + 1) 623 assert_raises_rpc_error(-1, "Negative timeout", node.waitforblockheight, current_height, -1) 624 625 def _test_getblock(self): 626 node = self.nodes[0] 627 fee_per_byte = Decimal('0.00000010') 628 fee_per_kb = 1000 * fee_per_byte 629 630 self.wallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node) 631 blockhash = self.generate(node, 1)[0] 632 633 def assert_hexblock_hashes(verbosity): 634 block = node.getblock(blockhash, verbosity) 635 assert_equal(blockhash, hash256(bytes.fromhex(block[:160]))[::-1].hex()) 636 637 def assert_fee_not_in_block(hash, verbosity): 638 block = node.getblock(hash, verbosity) 639 assert 'fee' not in block['tx'][1] 640 641 def assert_fee_in_block(hash, verbosity): 642 block = node.getblock(hash, verbosity) 643 tx = block['tx'][1] 644 assert 'fee' in tx 645 assert_equal(tx['fee'], tx['vsize'] * fee_per_byte) 646 647 def assert_vin_contains_prevout(verbosity): 648 block = node.getblock(blockhash, verbosity) 649 tx = block["tx"][1] 650 total_vin = Decimal("0.00000000") 651 total_vout = Decimal("0.00000000") 652 for vin in tx["vin"]: 653 assert "prevout" in vin 654 assert_equal(set(vin["prevout"].keys()), set(("value", "height", "generated", "scriptPubKey"))) 655 assert_equal(vin["prevout"]["generated"], True) 656 total_vin += vin["prevout"]["value"] 657 for vout in tx["vout"]: 658 total_vout += vout["value"] 659 assert_equal(total_vin, total_vout + tx["fee"]) 660 661 def assert_vin_does_not_contain_prevout(hash, verbosity): 662 block = node.getblock(hash, verbosity) 663 tx = block["tx"][1] 664 if isinstance(tx, str): 665 # In verbosity level 1, only the transaction hashes are written 666 pass 667 else: 668 for vin in tx["vin"]: 669 assert "prevout" not in vin 670 671 self.log.info("Test that getblock with verbosity 0 hashes to expected value") 672 assert_hexblock_hashes(0) 673 assert_hexblock_hashes(False) 674 675 self.log.info("Test that getblock with verbosity 1 doesn't include fee") 676 assert_fee_not_in_block(blockhash, 1) 677 assert_fee_not_in_block(blockhash, True) 678 679 self.log.info('Test that getblock with verbosity 2 and 3 includes expected fee') 680 assert_fee_in_block(blockhash, 2) 681 assert_fee_in_block(blockhash, 3) 682 683 self.log.info("Test that getblock with verbosity 1 and 2 does not include prevout") 684 assert_vin_does_not_contain_prevout(blockhash, 1) 685 assert_vin_does_not_contain_prevout(blockhash, 2) 686 687 self.log.info("Test that getblock with verbosity 3 includes prevout") 688 assert_vin_contains_prevout(3) 689 690 self.log.info("Test getblock with invalid verbosity type returns proper error message") 691 assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", node.getblock, blockhash, "2") 692 693 self.log.info("Test that getblock doesn't work with deleted Undo data") 694 695 def move_block_file(old, new): 696 old_path = self.nodes[0].blocks_path / old 697 new_path = self.nodes[0].blocks_path / new 698 old_path.rename(new_path) 699 700 # Move instead of deleting so we can restore chain state afterwards 701 move_block_file('rev00000.dat', 'rev_wrong') 702 703 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)) 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, 3)) 705 706 # Restore chain state 707 move_block_file('rev_wrong', 'rev00000.dat') 708 709 assert 'previousblockhash' not in node.getblock(node.getblockhash(0)) 710 assert 'nextblockhash' not in node.getblock(node.getbestblockhash()) 711 712 self.log.info("Test getblock when only header is known") 713 current_height = node.getblock(node.getbestblockhash())['height'] 714 block_time = node.getblock(node.getbestblockhash())['time'] + 1 715 block = create_block(int(blockhash, 16), create_coinbase(current_height + 1, nValue=100), block_time) 716 block.solve() 717 node.submitheader(block.serialize().hex()) 718 assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", lambda: node.getblock(block.hash)) 719 720 self.log.info("Test getblock when block data is available but undo data isn't") 721 # Submits a block building on the header-only block, so it can't be connected and has no undo data 722 tx = create_tx_with_script(block.vtx[0], 0, script_sig=bytes([OP_TRUE]), amount=50 * COIN) 723 block_noundo = create_block(block.sha256, create_coinbase(current_height + 2, nValue=100), block_time + 1, txlist=[tx]) 724 block_noundo.solve() 725 node.submitblock(block_noundo.serialize().hex()) 726 727 assert_fee_not_in_block(block_noundo.hash, 2) 728 assert_fee_not_in_block(block_noundo.hash, 3) 729 assert_vin_does_not_contain_prevout(block_noundo.hash, 2) 730 assert_vin_does_not_contain_prevout(block_noundo.hash, 3) 731 732 self.log.info("Test getblock when block is missing") 733 move_block_file('blk00000.dat', 'blk00000.dat.bak') 734 assert_raises_rpc_error(-1, "Block not found on disk", node.getblock, blockhash) 735 move_block_file('blk00000.dat.bak', 'blk00000.dat') 736 737 738 if __name__ == '__main__': 739 BlockchainTest(__file__).main()