2016-05-06 11:23:48 +02:00
#!/usr/bin/env python3
# Copyright (c) 2014-2016 The Bitcoin Core developers
2015-10-08 10:22:50 +02:00
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
2019-01-07 10:55:35 +01:00
""" Test RPCs related to blockchainstate.
2015-10-08 10:22:50 +02:00
2019-01-07 10:55:35 +01:00
Test the following RPCs :
2017-11-10 21:48:00 +01:00
- getblockchaininfo
2019-01-07 10:55:35 +01:00
- gettxoutsetinfo
2017-04-21 17:12:12 +02:00
- getdifficulty
- getbestblockhash
- getblockhash
- getblockheader
2017-06-05 16:25:12 +02:00
- getchaintxstats
2017-05-14 12:43:33 +02:00
- getnetworkhashps
2019-01-07 10:55:35 +01:00
- verifychain
Tests correspond to code in rpc / blockchain . cpp .
"""
2015-10-08 10:22:50 +02:00
2015-12-02 18:12:23 +01:00
from decimal import Decimal
2019-07-11 23:22:52 +02:00
import http . client
2017-06-22 10:50:24 +02:00
import subprocess
2015-10-08 10:22:50 +02:00
2017-09-06 20:02:08 +02:00
from test_framework . test_framework import BitcoinTestFramework
2015-10-08 10:22:50 +02:00
from test_framework . util import (
assert_equal ,
2017-10-09 16:53:12 +02:00
assert_greater_than ,
assert_greater_than_or_equal ,
2017-06-22 10:50:24 +02:00
assert_raises ,
2019-09-25 11:34:51 +02:00
assert_raises_rpc_error ,
2015-12-09 18:01:34 +01:00
assert_is_hex_string ,
assert_is_hash_string ,
2019-02-25 17:44:18 +01:00
set_node_times ,
2015-10-08 10:22:50 +02:00
)
2018-03-15 16:55:50 +01:00
from test_framework . blocktools import (
create_block ,
create_coinbase ,
2019-02-25 17:44:18 +01:00
TIME_GENESIS_BLOCK ,
2018-03-15 16:55:50 +01:00
)
from test_framework . messages import (
2020-08-21 14:33:41 +02:00
CBlockHeader ,
FromHex ,
2018-03-15 16:55:50 +01:00
msg_block ,
)
from test_framework . mininode import (
P2PInterface ,
)
2015-10-08 10:22:50 +02:00
class BlockchainTest ( BitcoinTestFramework ) :
2017-09-01 18:47:13 +02:00
def set_test_params ( self ) :
2019-02-25 17:44:18 +01:00
self . setup_clean_chain = True
2016-05-20 15:16:51 +02:00
self . num_nodes = 1
2019-12-09 19:52:38 +01:00
self . supports_cli = False
2015-10-08 10:22:50 +02:00
2018-09-13 12:33:15 +02:00
def run_test ( self ) :
2019-02-25 17:44:18 +01:00
self . mine_chain ( )
2018-09-10 22:58:15 +02:00
self . restart_node ( 0 , extra_args = [ ' -stopatheight=207 ' , ' -prune=1 ' , ' -txindex=0 ' ] ) # Set extra args with pruning after rescan is complete
2020-01-12 23:50:32 +01:00
# Actual tests
2017-09-25 13:16:08 +02:00
self . _test_getblockchaininfo ( )
2017-06-05 16:25:12 +02:00
self . _test_getchaintxstats ( )
2015-12-09 18:01:34 +01:00
self . _test_gettxoutsetinfo ( )
self . _test_getblockheader ( )
2017-04-21 17:12:12 +02:00
self . _test_getdifficulty ( )
2017-05-14 12:43:33 +02:00
self . _test_getnetworkhashps ( )
2017-06-22 10:50:24 +02:00
self . _test_stopatheight ( )
2018-03-15 16:55:50 +01:00
self . _test_waitforblockheight ( )
2017-06-22 10:50:24 +02:00
assert self . nodes [ 0 ] . verifychain ( 4 , 0 )
2015-12-09 18:01:34 +01:00
2019-02-25 17:44:18 +01:00
def mine_chain ( self ) :
self . log . info ( ' Create some old blocks ' )
address = self . nodes [ 0 ] . get_deterministic_priv_key ( ) . address
for t in range ( TIME_GENESIS_BLOCK , TIME_GENESIS_BLOCK + 200 * 156 , 156 ) :
# 156 sec steps from genesis block time
set_node_times ( self . nodes , t )
self . nodes [ 0 ] . generatetoaddress ( 1 , address )
assert_equal ( self . nodes [ 0 ] . getblockchaininfo ( ) [ ' blocks ' ] , 200 )
2017-09-25 13:16:08 +02:00
def _test_getblockchaininfo ( self ) :
self . log . info ( " Test getblockchaininfo " )
keys = [
' bestblockhash ' ,
' bip9_softforks ' ,
' blocks ' ,
' chain ' ,
' chainwork ' ,
' difficulty ' ,
' headers ' ,
2017-11-10 21:48:00 +01:00
' initialblockdownload ' ,
2017-09-25 13:16:08 +02:00
' mediantime ' ,
' pruned ' ,
2017-10-09 16:53:12 +02:00
' size_on_disk ' ,
2017-09-25 13:16:08 +02:00
' softforks ' ,
' verificationprogress ' ,
2017-09-28 13:46:00 +02:00
' warnings ' ,
2017-09-25 13:16:08 +02:00
]
res = self . nodes [ 0 ] . getblockchaininfo ( )
2017-10-09 16:53:12 +02:00
# result should have these additional pruning keys if manual pruning is enabled
assert_equal ( sorted ( res . keys ( ) ) , sorted ( [ ' pruneheight ' , ' automatic_pruning ' ] + keys ) )
# size_on_disk should be > 0
assert_greater_than ( res [ ' size_on_disk ' ] , 0 )
2017-09-25 13:16:08 +02:00
# pruneheight should be greater or equal to 0
2017-10-09 16:53:12 +02:00
assert_greater_than_or_equal ( res [ ' pruneheight ' ] , 0 )
# check other pruning fields given that prune=1
assert res [ ' pruned ' ]
assert not res [ ' automatic_pruning ' ]
2017-09-25 13:16:08 +02:00
2021-06-17 20:26:27 +02:00
self . restart_node ( 0 , [ ' -stopatheight=207 ' , ' -txindex=0 ' ] , expected_stderr = ' Warning: You are starting with governance validation disabled. This is expected because you are running a pruned node. ' )
2017-09-25 13:16:08 +02:00
res = self . nodes [ 0 ] . getblockchaininfo ( )
# should have exact keys
assert_equal ( sorted ( res . keys ( ) ) , keys )
2020-04-17 07:52:06 +02:00
self . restart_node ( 0 , [ ' -stopatheight=207 ' , ' -prune=550 ' , ' -txindex=0 ' ] )
2017-10-09 16:53:12 +02:00
res = self . nodes [ 0 ] . getblockchaininfo ( )
# result should have these additional pruning keys if prune=550
assert_equal ( sorted ( res . keys ( ) ) , sorted ( [ ' pruneheight ' , ' automatic_pruning ' , ' prune_target_size ' ] + keys ) )
# check related fields
assert res [ ' pruned ' ]
assert_equal ( res [ ' pruneheight ' ] , 0 )
assert res [ ' automatic_pruning ' ]
assert_equal ( res [ ' prune_target_size ' ] , 576716800 )
assert_greater_than ( res [ ' size_on_disk ' ] , 0 )
2017-06-05 16:25:12 +02:00
def _test_getchaintxstats ( self ) :
2018-02-02 17:57:01 +01:00
self . log . info ( " Test getchaintxstats " )
2018-02-26 16:35:56 +01:00
# Test `getchaintxstats` invalid extra parameters
assert_raises_rpc_error ( - 1 , ' getchaintxstats ' , self . nodes [ 0 ] . getchaintxstats , 0 , ' ' , 0 )
# Test `getchaintxstats` invalid `nblocks`
assert_raises_rpc_error ( - 1 , " JSON value is not an integer as expected " , self . nodes [ 0 ] . getchaintxstats , ' ' )
assert_raises_rpc_error ( - 8 , " Invalid block count: should be between 0 and the block ' s height - 1 " , self . nodes [ 0 ] . getchaintxstats , - 1 )
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 ( ) )
# Test `getchaintxstats` invalid `blockhash`
assert_raises_rpc_error ( - 1 , " JSON value is not a string as expected " , self . nodes [ 0 ] . getchaintxstats , blockhash = 0 )
assert_raises_rpc_error ( - 5 , " Block not found " , self . nodes [ 0 ] . getchaintxstats , blockhash = ' 0 ' )
blockhash = self . nodes [ 0 ] . getblockhash ( 200 )
self . nodes [ 0 ] . invalidateblock ( blockhash )
assert_raises_rpc_error ( - 8 , " Block is not in main chain " , self . nodes [ 0 ] . getchaintxstats , blockhash = blockhash )
self . nodes [ 0 ] . reconsiderblock ( blockhash )
2018-11-13 22:49:17 +01:00
chaintxstats = self . nodes [ 0 ] . getchaintxstats ( nblocks = 1 )
2017-06-05 16:25:12 +02:00
# 200 txs plus genesis tx
assert_equal ( chaintxstats [ ' txcount ' ] , 201 )
2019-07-04 17:15:52 +02:00
# tx rate should be 1 per ~2.6 minutes (156 seconds), or 1/156
2017-06-05 16:25:12 +02:00
# we have to round because of binary math
2019-07-04 17:15:52 +02:00
assert_equal ( round ( chaintxstats [ ' txrate ' ] * 156 , 10 ) , Decimal ( 1 ) )
2017-06-05 16:25:12 +02:00
2018-02-02 17:57:01 +01:00
b1_hash = self . nodes [ 0 ] . getblockhash ( 1 )
b1 = self . nodes [ 0 ] . getblock ( b1_hash )
b200_hash = self . nodes [ 0 ] . getblockhash ( 200 )
b200 = self . nodes [ 0 ] . getblock ( b200_hash )
2017-10-02 15:21:26 +02:00
time_diff = b200 [ ' mediantime ' ] - b1 [ ' mediantime ' ]
chaintxstats = self . nodes [ 0 ] . getchaintxstats ( )
assert_equal ( chaintxstats [ ' time ' ] , b200 [ ' time ' ] )
assert_equal ( chaintxstats [ ' txcount ' ] , 201 )
2018-02-02 17:57:01 +01:00
assert_equal ( chaintxstats [ ' window_final_block_hash ' ] , b200_hash )
2019-08-29 16:41:29 +02:00
assert_equal ( chaintxstats [ ' window_final_block_height ' ] , 200 )
2017-10-02 15:21:26 +02:00
assert_equal ( chaintxstats [ ' window_block_count ' ] , 199 )
assert_equal ( chaintxstats [ ' window_tx_count ' ] , 199 )
assert_equal ( chaintxstats [ ' window_interval ' ] , time_diff )
assert_equal ( round ( chaintxstats [ ' txrate ' ] * time_diff , 10 ) , Decimal ( 199 ) )
2018-02-02 17:57:01 +01:00
chaintxstats = self . nodes [ 0 ] . getchaintxstats ( blockhash = b1_hash )
2017-10-02 15:21:26 +02:00
assert_equal ( chaintxstats [ ' time ' ] , b1 [ ' time ' ] )
assert_equal ( chaintxstats [ ' txcount ' ] , 2 )
2018-02-02 17:57:01 +01:00
assert_equal ( chaintxstats [ ' window_final_block_hash ' ] , b1_hash )
2019-08-29 16:41:29 +02:00
assert_equal ( chaintxstats [ ' window_final_block_height ' ] , 1 )
2017-10-02 15:21:26 +02:00
assert_equal ( chaintxstats [ ' window_block_count ' ] , 0 )
2021-08-27 21:03:02 +02:00
assert ' window_tx_count ' not in chaintxstats
assert ' window_interval ' not in chaintxstats
assert ' txrate ' not in chaintxstats
2017-10-02 15:21:26 +02:00
2015-12-09 18:01:34 +01:00
def _test_gettxoutsetinfo ( self ) :
2015-10-08 10:22:50 +02:00
node = self . nodes [ 0 ]
res = node . gettxoutsetinfo ( )
2016-05-06 11:23:48 +02:00
assert_equal ( res [ ' total_amount ' ] , Decimal ( ' 98214.28571450 ' ) )
assert_equal ( res [ ' transactions ' ] , 200 )
assert_equal ( res [ ' height ' ] , 200 )
assert_equal ( res [ ' txouts ' ] , 200 )
2018-09-13 12:33:15 +02:00
assert_equal ( res [ ' bogosize ' ] , 15000 ) ,
2017-06-02 00:47:58 +02:00
size = res [ ' disk_size ' ]
assert size > 6400
assert size < 64000
2016-05-06 11:23:48 +02:00
assert_equal ( len ( res [ ' bestblock ' ] ) , 64 )
assert_equal ( len ( res [ ' hash_serialized_2 ' ] ) , 64 )
2015-10-08 10:22:50 +02:00
2019-03-08 09:05:10 +01:00
self . log . info ( " Test that gettxoutsetinfo() works for blockchain with just the genesis block " )
2017-05-26 20:56:52 +02:00
b1hash = node . getblockhash ( 1 )
node . invalidateblock ( b1hash )
res2 = node . gettxoutsetinfo ( )
assert_equal ( res2 [ ' transactions ' ] , 0 )
assert_equal ( res2 [ ' total_amount ' ] , Decimal ( ' 0 ' ) )
assert_equal ( res2 [ ' height ' ] , 0 )
assert_equal ( res2 [ ' txouts ' ] , 0 )
2017-06-06 11:26:28 +02:00
assert_equal ( res2 [ ' bogosize ' ] , 0 ) ,
2017-05-26 20:56:52 +02:00
assert_equal ( res2 [ ' bestblock ' ] , node . getblockhash ( 0 ) )
2017-06-02 00:47:58 +02:00
assert_equal ( len ( res2 [ ' hash_serialized_2 ' ] ) , 64 )
2017-05-26 20:56:52 +02:00
2019-03-08 09:05:10 +01:00
self . log . info ( " Test that gettxoutsetinfo() returns the same result after invalidate/reconsider block " )
2017-05-26 20:56:52 +02:00
node . reconsiderblock ( b1hash )
res3 = node . gettxoutsetinfo ( )
2018-08-11 03:31:06 +02:00
# The field 'disk_size' is non-deterministic and can thus not be
# compared between res and res3. Everything else should be the same.
del res [ ' disk_size ' ] , res3 [ ' disk_size ' ]
assert_equal ( res , res3 )
2017-05-26 20:56:52 +02:00
2022-02-26 11:54:04 +01:00
self . log . info ( " Test hash_type option for gettxoutsetinfo() " )
# Adding hash_type 'hash_serialized_2', which is the default, should
# not change the result.
res4 = node . gettxoutsetinfo ( hash_type = ' hash_serialized_2 ' )
del res4 [ ' disk_size ' ]
assert_equal ( res , res4 )
# hash_type none should not return a UTXO set hash.
res5 = node . gettxoutsetinfo ( hash_type = ' none ' )
assert ' hash_serialized_2 ' not in res5
2022-02-26 11:54:28 +01:00
# hash_type muhash should return a different UTXO set hash.
res6 = node . gettxoutsetinfo ( hash_type = ' muhash ' )
assert ' muhash ' in res6
assert ( res [ ' hash_serialized_2 ' ] != res6 [ ' muhash ' ] )
# muhash should not be included in gettxoutset unless requested.
for r in [ res , res2 , res3 , res4 , res5 ] :
assert ' muhash ' not in r
# Unknown hash_type raises an error
assert_raises_rpc_error ( - 8 , " foohash is not a valid hash_type " , node . gettxoutsetinfo , " foohash " )
2015-12-09 18:01:34 +01:00
def _test_getblockheader ( self ) :
node = self . nodes [ 0 ]
2018-02-02 17:57:01 +01:00
assert_raises_rpc_error ( - 5 , " Block not found " , node . getblockheader , " nonsense " )
2015-12-09 18:01:34 +01:00
besthash = node . getbestblockhash ( )
secondbesthash = node . getblockhash ( 199 )
2018-11-13 22:49:17 +01:00
header = node . getblockheader ( blockhash = besthash )
2015-12-09 18:01:34 +01:00
assert_equal ( header [ ' hash ' ] , besthash )
assert_equal ( header [ ' height ' ] , 200 )
assert_equal ( header [ ' confirmations ' ] , 1 )
assert_equal ( header [ ' previousblockhash ' ] , secondbesthash )
assert_is_hex_string ( header [ ' chainwork ' ] )
2018-06-14 19:38:19 +02:00
assert_equal ( header [ ' nTx ' ] , 1 )
2015-12-09 18:01:34 +01:00
assert_is_hash_string ( header [ ' hash ' ] )
assert_is_hash_string ( header [ ' previousblockhash ' ] )
assert_is_hash_string ( header [ ' merkleroot ' ] )
assert_is_hash_string ( header [ ' bits ' ] , length = None )
assert isinstance ( header [ ' time ' ] , int )
assert isinstance ( header [ ' mediantime ' ] , int )
assert isinstance ( header [ ' nonce ' ] , int )
assert isinstance ( header [ ' version ' ] , int )
2016-04-05 15:48:49 +02:00
assert isinstance ( int ( header [ ' versionHex ' ] , 16 ) , int )
2016-01-18 15:17:48 +01:00
assert isinstance ( header [ ' difficulty ' ] , Decimal )
2015-10-08 10:22:50 +02:00
2020-08-21 14:33:41 +02:00
# Test with verbose=False, which should return the header as hex.
header_hex = node . getblockheader ( blockhash = besthash , verbose = False )
assert_is_hex_string ( header_hex )
header = FromHex ( CBlockHeader ( ) , header_hex )
header . calc_sha256 ( )
assert_equal ( header . hash , besthash )
2017-04-21 17:12:12 +02:00
def _test_getdifficulty ( self ) :
difficulty = self . nodes [ 0 ] . getdifficulty ( )
# 1 hash in 2 should be valid, so difficulty should be 1/2**31
# binary => decimal => binary math is why we do this check
assert abs ( difficulty * 2 * * 31 - 1 ) < 0.0001
2017-05-14 12:43:33 +02:00
def _test_getnetworkhashps ( self ) :
hashes_per_second = self . nodes [ 0 ] . getnetworkhashps ( )
2019-06-19 20:05:39 +02:00
# This should be 2 hashes every 2.6 minutes (156 seconds) or 1/78
assert abs ( hashes_per_second * 78 - 1 ) < 0.0001
2017-05-14 12:43:33 +02:00
2017-06-22 10:50:24 +02:00
def _test_stopatheight ( self ) :
assert_equal ( self . nodes [ 0 ] . getblockcount ( ) , 200 )
2018-09-17 20:00:41 +02:00
self . nodes [ 0 ] . generatetoaddress ( 6 , self . nodes [ 0 ] . get_deterministic_priv_key ( ) . address )
2017-06-22 10:50:24 +02:00
assert_equal ( self . nodes [ 0 ] . getblockcount ( ) , 206 )
self . log . debug ( ' Node should not stop at this height ' )
2017-08-15 23:34:07 +02:00
assert_raises ( subprocess . TimeoutExpired , lambda : self . nodes [ 0 ] . process . wait ( timeout = 3 ) )
2019-07-11 23:22:52 +02:00
try :
2018-09-17 20:00:41 +02:00
self . nodes [ 0 ] . generatetoaddress ( 1 , self . nodes [ 0 ] . get_deterministic_priv_key ( ) . address )
2019-07-11 23:22:52 +02:00
except ( ConnectionError , http . client . BadStatusLine ) :
pass # The node already shut down before response
2017-06-22 10:50:24 +02:00
self . log . debug ( ' Node should stop at this height... ' )
2017-09-06 20:02:08 +02:00
self . nodes [ 0 ] . wait_until_stopped ( )
2020-04-17 07:52:06 +02:00
self . start_node ( 0 , [ ' -txindex=0 ' ] )
2017-06-22 10:50:24 +02:00
assert_equal ( self . nodes [ 0 ] . getblockcount ( ) , 207 )
2018-03-15 16:55:50 +01:00
def _test_waitforblockheight ( self ) :
self . log . info ( " Test waitforblockheight " )
node = self . nodes [ 0 ]
node . add_p2p_connection ( P2PInterface ( ) )
current_height = node . getblock ( node . getbestblockhash ( ) ) [ ' height ' ]
# Create a fork somewhere below our current height, invalidate the tip
# of that fork, and then ensure that waitforblockheight still
# works as expected.
#
# (Previously this was broken based on setting
# `rpc/blockchain.cpp:latestblock` incorrectly.)
#
b20hash = node . getblockhash ( 20 )
b20 = node . getblock ( b20hash )
def solve_and_send_block ( prevhash , height , time ) :
b = create_block ( prevhash , create_coinbase ( height ) , time )
b . solve ( )
node . p2p . send_message ( msg_block ( b ) )
node . p2p . sync_with_ping ( )
return b
b21f = solve_and_send_block ( int ( b20hash , 16 ) , 21 , b20 [ ' time ' ] + 1 )
b22f = solve_and_send_block ( b21f . sha256 , 22 , b21f . nTime + 1 )
node . invalidateblock ( b22f . hash )
def assert_waitforheight ( height , timeout = 2 ) :
assert_equal (
2018-11-13 22:49:17 +01:00
node . waitforblockheight ( height = height , timeout = timeout ) [ ' height ' ] ,
2018-03-15 16:55:50 +01:00
current_height )
assert_waitforheight ( 0 )
assert_waitforheight ( current_height - 1 )
assert_waitforheight ( current_height )
assert_waitforheight ( current_height + 1 )
2017-06-22 10:50:24 +02:00
2015-10-08 10:22:50 +02:00
if __name__ == ' __main__ ' :
BlockchainTest ( ) . main ( )