Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2017-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.6#7# Test getblockstats rpc call8#10from test_framework.blocktools import COINBASE_MATURITY11from test_framework.test_framework import BitcoinTestFramework12from test_framework.util import (13assert_equal,14assert_raises_rpc_error,15wallet_importprivkey,16)17import json18import os20TESTSDIR = os.path.dirname(os.path.realpath(__file__))22class GetblockstatsTest(BitcoinTestFramework):24start_height = 10125max_stat_pos = 227def add_options(self, parser):28parser.add_argument('--gen-test-data', dest='gen_test_data',29default=False, action='store_true',30help='Generate test data')31parser.add_argument('--test-data', dest='test_data',32default='data/rpc_getblockstats.json',33action='store', metavar='FILE',34help='Test data file')36def set_test_params(self):37self.num_nodes = 138self.setup_clean_chain = True40def get_stats(self):41return [self.nodes[0].getblockstats(hash_or_height=self.start_height + i) for i in range(self.max_stat_pos+1)]43def generate_test_data(self, filename):44mocktime = 152510722545self.nodes[0].setmocktime(mocktime)46self.nodes[0].createwallet(wallet_name='test')47privkey = self.nodes[0].get_deterministic_priv_key().key48wallet_importprivkey(self.nodes[0], privkey, 0)50self.generate(self.nodes[0], COINBASE_MATURITY + 1)52address = self.nodes[0].get_deterministic_priv_key().address53self.nodes[0].sendtoaddress(address=address, amount=10, subtractfeefromamount=True)54self.generate(self.nodes[0], 1)56self.nodes[0].sendtoaddress(address=address, amount=10, subtractfeefromamount=True)57self.nodes[0].sendtoaddress(address=address, amount=10, subtractfeefromamount=False)58self.fee_rate=30059self.nodes[0].sendtoaddress(address=address, amount=1, subtractfeefromamount=True, fee_rate=self.fee_rate)60# Send to OP_RETURN output to test its exclusion from statistics61self.nodes[0].send(outputs={"data": "21"}, fee_rate=self.fee_rate)62self.sync_all()63self.generate(self.nodes[0], 1)65self.expected_stats = self.get_stats()67blocks = []68tip = self.nodes[0].getbestblockhash()69blockhash = None70height = 071while tip != blockhash:72blockhash = self.nodes[0].getblockhash(height)73blocks.append(self.nodes[0].getblock(blockhash, 0))74height += 176to_dump = {77'blocks': blocks,78'mocktime': int(mocktime),79'stats': self.expected_stats,80}81with open(filename, 'w') as f:82json.dump(to_dump, f, sort_keys=True, indent=2)84def load_test_data(self, filename):85with open(filename, 'r') as f:86d = json.load(f)87blocks = d['blocks']88mocktime = d['mocktime']89self.expected_stats = d['stats']91# Set the timestamps from the file so that the nodes can get out of Initial Block Download92self.nodes[0].setmocktime(mocktime)93self.sync_all()95for b in blocks:96self.nodes[0].submitblock(b)99def run_test(self):100test_data = os.path.join(TESTSDIR, self.options.test_data)101if self.options.gen_test_data:102self.generate_test_data(test_data)103else:104self.load_test_data(test_data)106self.sync_all()107stats = self.get_stats()109# Make sure all valid statistics are included but nothing else is110expected_keys = self.expected_stats[0].keys()111assert_equal(set(stats[0].keys()), set(expected_keys))113assert_equal(stats[0]['height'], self.start_height)114assert_equal(stats[self.max_stat_pos]['height'], self.start_height + self.max_stat_pos)116for i in range(self.max_stat_pos+1):117self.log.info('Checking block %d' % (i))118assert_equal(stats[i], self.expected_stats[i])120# Check selecting block by hash too121blockhash = self.expected_stats[i]['blockhash']122stats_by_hash = self.nodes[0].getblockstats(hash_or_height=blockhash)123assert_equal(stats_by_hash, self.expected_stats[i])125# Make sure each stat can be queried on its own126for stat in expected_keys:127for i in range(self.max_stat_pos+1):128result = self.nodes[0].getblockstats(hash_or_height=self.start_height + i, stats=[stat])129assert_equal(list(result.keys()), [stat])130if result[stat] != self.expected_stats[i][stat]:131self.log.info('result[%s] (%d) failed, %r != %r' % (132stat, i, result[stat], self.expected_stats[i][stat]))133assert_equal(result[stat], self.expected_stats[i][stat])135# Make sure only the selected statistics are included (more than one)136some_stats = {'minfee', 'maxfee'}137stats = self.nodes[0].getblockstats(hash_or_height=1, stats=list(some_stats))138assert_equal(set(stats.keys()), some_stats)140# Test invalid parameters raise the proper json exceptions141tip = self.start_height + self.max_stat_pos142assert_raises_rpc_error(-8, 'Target block height %d after current tip %d' % (tip+1, tip),143self.nodes[0].getblockstats, hash_or_height=tip+1)144assert_raises_rpc_error(-8, 'Target block height %d is negative' % (-1),145self.nodes[0].getblockstats, hash_or_height=-1)147# Make sure not valid stats aren't allowed148inv_sel_stat = 'asdfghjkl'149inv_stats = [150[inv_sel_stat],151['minfee', inv_sel_stat],152[inv_sel_stat, 'minfee'],153['minfee', inv_sel_stat, 'maxfee'],154]155for inv_stat in inv_stats:156assert_raises_rpc_error(-8, f"Invalid selected statistic '{inv_sel_stat}'",157self.nodes[0].getblockstats, hash_or_height=1, stats=inv_stat)159# Make sure we aren't always returning inv_sel_stat as the culprit stat160assert_raises_rpc_error(-8, f"Invalid selected statistic 'aaa{inv_sel_stat}'",161self.nodes[0].getblockstats, hash_or_height=1, stats=['minfee', f'aaa{inv_sel_stat}'])162# Mainchain's genesis block shouldn't be found on regtest163assert_raises_rpc_error(-5, 'Block not found', self.nodes[0].getblockstats,164hash_or_height='000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f')166# Invalid number of args167assert_raises_rpc_error(-1, 'getblockstats hash_or_height ( stats )', self.nodes[0].getblockstats, '00', 1, 2)168assert_raises_rpc_error(-1, 'getblockstats hash_or_height ( stats )', self.nodes[0].getblockstats)170self.log.info('Test block height 0')171genesis_stats = self.nodes[0].getblockstats(0)172assert_equal(genesis_stats["blockhash"], "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206")173assert_equal(genesis_stats["utxo_increase"], 1)174assert_equal(genesis_stats["utxo_size_inc"], 116)175assert_equal(genesis_stats["utxo_increase_actual"], 0)176assert_equal(genesis_stats["utxo_size_inc_actual"], 0)178self.log.info('Test tip including OP_RETURN')179tip_stats = self.nodes[0].getblockstats(tip)180assert_equal(tip_stats["utxo_increase"], 6)181assert_equal(tip_stats["utxo_size_inc"], 435)182assert_equal(tip_stats["utxo_increase_actual"], 4)183assert_equal(tip_stats["utxo_size_inc_actual"], 296)185self.log.info("Test when only header is known")186block = self.generateblock(self.nodes[0], output="raw(55)", transactions=[], submit=False)187self.nodes[0].submitheader(block["hex"])188assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", lambda: self.nodes[0].getblockstats(block['hash']))190self.log.info('Test when block is missing')191(self.nodes[0].blocks_path / 'blk00000.dat').rename(self.nodes[0].blocks_path / 'blk00000.dat.backup')192assert_raises_rpc_error(-1, 'Block not found on disk', self.nodes[0].getblockstats, hash_or_height=1)193(self.nodes[0].blocks_path / 'blk00000.dat.backup').rename(self.nodes[0].blocks_path / 'blk00000.dat')196if __name__ == '__main__':197GetblockstatsTest(__file__).main()
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.