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) 2014-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 the pruning code.7WARNING:8This test uses 4GB of disk space.9"""10import os12from test_framework.blocktools import (13MIN_BLOCKS_TO_KEEP,14create_block,15create_coinbase,16)17from test_framework.script import (18CScript,19OP_NOP,20OP_RETURN,21)22from test_framework.test_framework import BitcoinTestFramework23from test_framework.util import (24assert_equal,25assert_greater_than,26assert_raises_rpc_error,27try_rpc,28)30# Rescans start at the earliest block up to 2 hours before a key timestamp, so31# the manual prune RPC avoids pruning blocks in the same window to be32# compatible with pruning based on key creation time.33TIMESTAMP_WINDOW = 2 * 60 * 6035def mine_large_blocks(node, n):36# Make a large scriptPubKey for the coinbase transaction. This is OP_RETURN37# followed by 950k of OP_NOP. This would be non-standard in a non-coinbase38# transaction but is consensus valid.40# Set the nTime if this is the first time this function has been called.41# A static variable ensures that time is monotonicly increasing and is therefore42# different for each block created => blockhash is unique.43if "nTime" not in mine_large_blocks.__dict__:44mine_large_blocks.nTime = 046# Get the block parameters for the first block47big_script = CScript([OP_RETURN] + [OP_NOP] * 950000)48best_block = node.getblock(node.getbestblockhash())49height = int(best_block["height"]) + 150mine_large_blocks.nTime = max(mine_large_blocks.nTime, int(best_block["time"])) + 151previousblockhash = int(best_block["hash"], 16)53for _ in range(n):54block = create_block(hashprev=previousblockhash, ntime=mine_large_blocks.nTime, coinbase=create_coinbase(height, script_pubkey=big_script))55block.solve()57# Submit to the node58node.submitblock(block.serialize().hex())60previousblockhash = block.hash_int61height += 162mine_large_blocks.nTime += 164def calc_usage(blockdir):65return sum(os.path.getsize(blockdir + f) for f in os.listdir(blockdir) if os.path.isfile(os.path.join(blockdir, f))) / (1024. * 1024.)67class PruneTest(BitcoinTestFramework):68def set_test_params(self):69self.setup_clean_chain = True70self.num_nodes = 671self.uses_wallet = None73# Create nodes 0 and 1 to mine.74# Create node 2 to test pruning.75self.full_node_default_args = ["-maxreceivebuffer=20000", "-checkblocks=5"]76# Create nodes 3 and 4 to test manual pruning (they will be re-started with manual pruning later)77# Create nodes 5 to test wallet in prune mode, but do not connect78self.extra_args = [79self.full_node_default_args,80self.full_node_default_args,81["-maxreceivebuffer=20000", "-prune=550"],82["-maxreceivebuffer=20000"],83["-maxreceivebuffer=20000"],84["-prune=550", "-blockfilterindex=1"],85]86self.rpc_timeout = 12088def setup_network(self):89self.setup_nodes()91self.prunedir = os.path.join(self.nodes[2].blocks_path, '')93self.connect_nodes(0, 1)94self.connect_nodes(1, 2)95self.connect_nodes(0, 2)96self.connect_nodes(0, 3)97self.connect_nodes(0, 4)98self.sync_blocks(self.nodes[0:5])100def setup_nodes(self):101self.add_nodes(self.num_nodes, self.extra_args)102self.start_nodes()103if self.is_wallet_compiled():104self.import_deterministic_coinbase_privkeys()106def create_big_chain(self):107# Start by creating some coinbases we can spend later108self.generate(self.nodes[1], 200, sync_fun=lambda: self.sync_blocks(self.nodes[0:2]))109self.generate(self.nodes[0], 150, sync_fun=self.no_op)111# Then mine enough full blocks to create more than 550MiB of data112mine_large_blocks(self.nodes[0], 645)114self.sync_blocks(self.nodes[0:5])116def test_invalid_command_line_options(self):117self.stop_node(0)118self.nodes[0].assert_start_raises_init_error(119expected_msg='Error: Prune cannot be configured with a negative value.',120extra_args=['-prune=-1'],121)122self.nodes[0].assert_start_raises_init_error(123expected_msg='Error: Prune configured below the minimum of 550 MiB. Please use a higher number.',124extra_args=['-prune=549'],125)126self.nodes[0].assert_start_raises_init_error(127expected_msg='Error: Prune mode is incompatible with -txindex.',128extra_args=['-prune=550', '-txindex'],129)130self.nodes[0].assert_start_raises_init_error(131expected_msg='Error: Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead.',132extra_args=['-prune=550', '-reindex-chainstate'],133)135def test_rescan_blockchain(self):136self.restart_node(0, ["-prune=550"])137assert_raises_rpc_error(-1, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.", self.nodes[0].rescanblockchain)139def test_height_min(self):140assert os.path.isfile(os.path.join(self.prunedir, "blk00000.dat")), "blk00000.dat is missing, pruning too early"141self.log.info("Success")142self.log.info(f"Though we're already using more than 550MiB, current usage: {calc_usage(self.prunedir)}")143self.log.info("Mining 25 more blocks should cause the first block file to be pruned")144# Pruning doesn't run until we're allocating another chunk, 20 full blocks past the height cutoff will ensure this145mine_large_blocks(self.nodes[0], 25)147# Wait for blk00000.dat to be pruned148self.wait_until(lambda: not os.path.isfile(os.path.join(self.prunedir, "blk00000.dat")), timeout=30)150self.log.info("Success")151usage = calc_usage(self.prunedir)152self.log.info(f"Usage should be below target: {usage}")153assert_greater_than(550, usage)155def create_chain_with_staleblocks(self):156# Create stale blocks in manageable sized chunks157self.log.info("Mine 24 (stale) blocks on Node 1, followed by 25 (main chain) block reorg from Node 0, for 12 rounds")159for _ in range(12):160# Disconnect node 0 so it can mine a longer reorg chain without knowing about node 1's soon-to-be-stale chain161# Node 2 stays connected, so it hears about the stale blocks and then reorg's when node0 reconnects162self.disconnect_nodes(0, 1)163self.disconnect_nodes(0, 2)164# Mine 24 blocks in node 1165mine_large_blocks(self.nodes[1], 24)167# Reorg back with 25 block chain from node 0168mine_large_blocks(self.nodes[0], 25)170# Create connections in the order so both nodes can see the reorg at the same time171self.connect_nodes(0, 1)172self.connect_nodes(0, 2)173self.sync_blocks(self.nodes[0:3])175self.log.info(f"Usage can be over target because of high stale rate: {calc_usage(self.prunedir)}")177def reorg_test(self):178# Node 1 will mine a 300 block chain starting 287 blocks back from Node 0 and Node 2's tip179# This will cause Node 2 to do a reorg requiring 288 blocks of undo data to the reorg_test chain181height = self.nodes[1].getblockcount()182self.log.info(f"Current block height: {height}")184self.forkheight = height - 287185self.forkhash = self.nodes[1].getblockhash(self.forkheight)186self.log.info(f"Invalidating block {self.forkhash} at height {self.forkheight}")187self.nodes[1].invalidateblock(self.forkhash)189# We've now switched to our previously mined-24 block fork on node 1, but that's not what we want190# So invalidate that fork as well, until we're on the same chain as node 0/2 (but at an ancestor 288 blocks ago)191mainchainhash = self.nodes[0].getblockhash(self.forkheight - 1)192curhash = self.nodes[1].getblockhash(self.forkheight - 1)193while curhash != mainchainhash:194self.nodes[1].invalidateblock(curhash)195curhash = self.nodes[1].getblockhash(self.forkheight - 1)197assert_equal(self.nodes[1].getblockcount(), self.forkheight - 1)198self.log.info(f"New best height: {self.nodes[1].getblockcount()}")200# Disconnect node1 and generate the new chain201self.disconnect_nodes(0, 1)202self.disconnect_nodes(1, 2)204self.log.info("Generating new longer chain of 300 more blocks")205self.generate(self.nodes[1], 300, sync_fun=self.no_op)207self.log.info("Reconnect nodes")208self.connect_nodes(0, 1)209self.connect_nodes(1, 2)210self.sync_blocks(self.nodes[0:3], timeout=120)212self.log.info(f"Verify height on node 2: {self.nodes[2].getblockcount()}")213self.log.info(f"Usage possibly still high because of stale blocks in block files: {calc_usage(self.prunedir)}")215self.log.info("Mine 220 more large blocks so we have requisite history")217mine_large_blocks(self.nodes[0], 220)218self.sync_blocks(self.nodes[0:3], timeout=120)220usage = calc_usage(self.prunedir)221self.log.info(f"Usage should be below target: {usage}")222assert_greater_than(550, usage)224def reorg_back(self):225# Verify that a block on the old main chain fork has been pruned away226assert_raises_rpc_error(-1, "Block not available (pruned data)", self.nodes[2].getblock, self.forkhash)227with self.nodes[2].assert_debug_log(expected_msgs=["Block verification stopping at height", "(no data)"]):228assert not self.nodes[2].verifychain(checklevel=4, nblocks=0)229self.log.info(f"Will need to redownload block {self.forkheight}")231# Verify that we have enough history to reorg back to the fork point232# Although this is more than 288 blocks, because this chain was written more recently233# and only its other 299 small and 220 large blocks are in the block files after it,234# it is expected to still be retained235self.nodes[2].getblock(self.nodes[2].getblockhash(self.forkheight))237first_reorg_height = self.nodes[2].getblockcount()238block_hash_1295 = self.nodes[2].getblockhash(1295)239self.nodes[2].invalidateblock(block_hash_1295)240goalbestheight = self.mainchainheight241goalbesthash = self.mainchainhash2243# As of 0.10 the current block download logic is not able to reorg to the original chain created in244# create_chain_with_stale_blocks because it doesn't know of any peer that's on that chain from which to245# redownload its missing blocks.246# Invalidate the reorg_test chain in node 0 as well, it can successfully switch to the original chain247# because it has all the block data.248# However it must mine enough blocks to have a more work chain than the reorg_test chain in order249# to trigger node 2's block download logic.250# At this point node 2 is within 288 blocks of the fork point so it will preserve its ability to reorg
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.