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