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) 2020-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.5"""Test coinstatsindex across nodes.7Test that the values returned by gettxoutsetinfo are consistent8between a node running the coinstatsindex and a node without9the index.10"""12from decimal import Decimal14from test_framework.blocktools import (15COINBASE_MATURITY,16create_block,17create_coinbase,18)19from test_framework.messages import (20COIN,21CTxOut,22)23from test_framework.script import (24CScript,25OP_FALSE,26OP_RETURN,27)28from test_framework.test_framework import BitcoinTestFramework29from test_framework.util import (30assert_not_equal,31assert_equal,32assert_raises_rpc_error,33)34from test_framework.wallet import (35MiniWallet,36getnewdestination,37)40class CoinStatsIndexTest(BitcoinTestFramework):41def set_test_params(self):42self.setup_clean_chain = True43self.num_nodes = 244self.extra_args = [45[],46["-coinstatsindex"]47]49def run_test(self):50self.wallet = MiniWallet(self.nodes[0])51self._test_coin_stats_index()52self._test_use_index_option()53self._test_reorg_index()54self._test_index_rejects_hash_serialized()55self._test_init_index_after_reorg()57def block_sanity_check(self, block_info):58block_subsidy = 5059assert_equal(60block_info['prevout_spent'] + block_subsidy,61block_info['new_outputs_ex_coinbase'] + block_info['coinbase'] + block_info['unspendable']62)64def sync_index_node(self):65self.wait_until(lambda: self.nodes[1].getindexinfo()['coinstatsindex']['synced'] is True)67def _test_coin_stats_index(self):68node = self.nodes[0]69index_node = self.nodes[1]70# Both none and muhash options allow the usage of the index71index_hash_options = ['none', 'muhash']73# Generate a normal transaction and mine it74self.generate(self.wallet, COINBASE_MATURITY + 1)75self.wallet.send_self_transfer(from_node=node)76self.generate(node, 1)78self.log.info("Test that gettxoutsetinfo() output is consistent with or without coinstatsindex option")79res0 = node.gettxoutsetinfo('none')81# The fields 'disk_size' and 'transactions' do not exist on the index82del res0['disk_size'], res0['transactions']84for hash_option in index_hash_options:85res1 = index_node.gettxoutsetinfo(hash_option)86# The fields 'block_info' and 'total_unspendable_amount' only exist on the index87del res1['block_info'], res1['total_unspendable_amount']88res1.pop('muhash', None)90# Everything left should be the same91assert_equal(res1, res0)93self.log.info("Test that gettxoutsetinfo() can get fetch data on specific heights with index")95# Generate a new tip96self.generate(node, 5)98for hash_option in index_hash_options:99# Fetch old stats by height100res2 = index_node.gettxoutsetinfo(hash_option, 102)101del res2['block_info'], res2['total_unspendable_amount']102res2.pop('muhash', None)103assert_equal(res0, res2)105# Fetch old stats by hash106res3 = index_node.gettxoutsetinfo(hash_option, res0['bestblock'])107del res3['block_info'], res3['total_unspendable_amount']108res3.pop('muhash', None)109assert_equal(res0, res3)111# It does not work without coinstatsindex112assert_raises_rpc_error(-8, "Querying specific block heights requires coinstatsindex", node.gettxoutsetinfo, hash_option, 102)114self.log.info("Test gettxoutsetinfo() with index and verbose flag")116for hash_option in index_hash_options:117# Genesis block is unspendable118res4 = index_node.gettxoutsetinfo(hash_option, 0)119assert_equal(res4['total_unspendable_amount'], 50)120assert_equal(res4['block_info'], {121'unspendable': 50,122'prevout_spent': 0,123'new_outputs_ex_coinbase': 0,124'coinbase': 0,125'unspendables': {126'genesis_block': 50,127'bip30': 0,128'scripts': 0,129'unclaimed_rewards': 0130}131})132self.block_sanity_check(res4['block_info'])134# Test an older block height that included a normal tx135res5 = index_node.gettxoutsetinfo(hash_option, 102)136assert_equal(res5['total_unspendable_amount'], 50)137assert_equal(res5['block_info'], {138'unspendable': 0,139'prevout_spent': 50,140'new_outputs_ex_coinbase': Decimal('49.99968800'),141'coinbase': Decimal('50.00031200'),142'unspendables': {143'genesis_block': 0,144'bip30': 0,145'scripts': 0,146'unclaimed_rewards': 0,147}148})149self.block_sanity_check(res5['block_info'])151# Generate and send a normal tx with two outputs152tx1 = self.wallet.send_to(153from_node=node,154scriptPubKey=self.wallet.get_output_script(),155amount=21 * COIN,156)158# Find the right position of the 21 BTC output159tx1_out_21 = self.wallet.get_utxo(txid=tx1["txid"], vout=tx1["sent_vout"])161# Generate and send another tx with an OP_RETURN output (which is unspendable)162tx2 = self.wallet.create_self_transfer(utxo_to_spend=tx1_out_21)['tx']163tx2_val = '20.99'164tx2.vout = [CTxOut(int(Decimal(tx2_val) * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))]165tx2_hex = tx2.serialize().hex()166self.nodes[0].sendrawtransaction(tx2_hex, 0, tx2_val)168# Include both txs in a block169self.generate(self.nodes[0], 1)171for hash_option in index_hash_options:172# Check all amounts were registered correctly173res6 = index_node.gettxoutsetinfo(hash_option, 108)174assert_equal(res6['total_unspendable_amount'], Decimal('70.99000000'))175assert_equal(res6['block_info'], {176'unspendable': Decimal('20.99000000'),177'prevout_spent': 71,178'new_outputs_ex_coinbase': Decimal('49.99999000'),179'coinbase': Decimal('50.01001000'),180'unspendables': {181'genesis_block': 0,182'bip30': 0,183'scripts': Decimal('20.99000000'),184'unclaimed_rewards': 0,185}186})187self.block_sanity_check(res6['block_info'])189# Create a coinbase that does not claim full subsidy and also190# has two outputs191cb = create_coinbase(109, nValue=35)192cb.vout.append(CTxOut(5 * COIN, CScript([OP_FALSE])))194# Generate a block that includes previous coinbase195tip = self.nodes[0].getbestblockhash()196block_time = self.nodes[0].getblock(tip)['time'] + 1197block = create_block(int(tip, 16), cb, ntime=block_time)198block.solve()199self.nodes[0].submitblock(block.serialize().hex())200self.sync_all()202for hash_option in index_hash_options:203res7 = index_node.gettxoutsetinfo(hash_option, 109)204assert_equal(res7['total_unspendable_amount'], Decimal('80.99000000'))205assert_equal(res7['block_info'], {206'unspendable': 10,207'prevout_spent': 0,208'new_outputs_ex_coinbase': 0,209'coinbase': 40,210'unspendables': {211'genesis_block': 0,212'bip30': 0,213'scripts': 0,214'unclaimed_rewards': 10215}216})217self.block_sanity_check(res7['block_info'])219self.log.info("Test that the index is robust across restarts")221res8 = index_node.gettxoutsetinfo('muhash')222self.restart_node(1, extra_args=self.extra_args[1])223res9 = index_node.gettxoutsetinfo('muhash')224assert_equal(res8, res9)226self.generate(index_node, 1, sync_fun=self.no_op)227res10 = index_node.gettxoutsetinfo('muhash')228assert res8['txouts'] < res10['txouts']230self.log.info("Test that the index works with -reindex")232self.restart_node(1, extra_args=["-coinstatsindex", "-reindex"])233self.sync_index_node()234res11 = index_node.gettxoutsetinfo('muhash')235assert_equal(res11, res10)237self.log.info("Test that the index works with -reindex-chainstate")239self.restart_node(1, extra_args=["-coinstatsindex", "-reindex-chainstate"])240self.sync_index_node()241res12 = index_node.gettxoutsetinfo('muhash')242assert_equal(res12, res10)244self.log.info("Test obtaining info for a non-existent block hash")245assert_raises_rpc_error(-5, "Block not found", index_node.gettxoutsetinfo, hash_type="none", hash_or_height="ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", use_index=True)247def _test_use_index_option(self):248self.log.info("Test use_index option for nodes running the index")250self.connect_nodes(0, 1)
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.