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