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.
251if self.nodes[2].getblockcount() < self.mainchainheight:252blocks_to_mine = first_reorg_height + 1 - self.mainchainheight253self.log.info(f"Rewind node 0 to prev main chain to mine longer chain to trigger redownload. Blocks needed: {blocks_to_mine}")254self.nodes[0].invalidateblock(block_hash_1295)255assert_equal(self.nodes[0].getblockcount(), self.mainchainheight)256assert_equal(self.nodes[0].getbestblockhash(), self.mainchainhash2)257goalbesthash = self.generate(self.nodes[0], blocks_to_mine, sync_fun=self.no_op)[-1]258goalbestheight = first_reorg_height + 1260self.log.info("Verify node 2 reorged back to the main chain, some blocks of which it had to redownload")261# Wait for Node 2 to reorg to proper height262self.wait_until(lambda: self.nodes[2].getblockcount() >= goalbestheight, timeout=900)263assert_equal(self.nodes[2].getbestblockhash(), goalbesthash)264# Verify we can now have the data for a block previously pruned265assert_equal(self.nodes[2].getblock(self.forkhash)["height"], self.forkheight)267def manual_test(self, node_number, use_timestamp):268# at this point, node has 995 blocks and has not yet run in prune mode269self.start_node(node_number)270node = self.nodes[node_number]271assert_equal(node.getblockcount(), 995)272assert_raises_rpc_error(-1, "Cannot prune blocks because node is not in prune mode", node.pruneblockchain, 500)274# now re-start in manual pruning mode275self.restart_node(node_number, extra_args=["-prune=1"])276node = self.nodes[node_number]277assert_equal(node.getblockcount(), 995)279def height(index):280if use_timestamp:281return node.getblockheader(node.getblockhash(index))["time"] + TIMESTAMP_WINDOW282else:283return index285def prune(index):286ret = node.pruneblockchain(height=height(index))287assert_equal(ret + 1, node.getblockchaininfo()['pruneheight'])289def has_block(index):290return os.path.isfile(os.path.join(self.nodes[node_number].blocks_path, f"blk{index:05}.dat"))292# should not prune because chain tip of node 3 (995) < PruneAfterHeight (1000)293assert_raises_rpc_error(-1, "Blockchain is too short for pruning", node.pruneblockchain, height(500))295# Save block transaction count before pruning, assert value296block1_details = node.getblock(node.getblockhash(1))297assert_equal(block1_details["nTx"], len(block1_details["tx"]))299# mine 6 blocks so we are at height 1001 (i.e., above PruneAfterHeight)300self.generate(node, 6, sync_fun=self.no_op)301assert_equal(node.getblockchaininfo()["blocks"], 1001)303# prune parameter in the future (block or timestamp) should raise an exception304future_parameter = height(1001) + 5305if use_timestamp:306assert_raises_rpc_error(-8, "Could not find block with at least the specified timestamp", node.pruneblockchain, future_parameter)307else:308assert_raises_rpc_error(-8, "Blockchain is shorter than the attempted prune height", node.pruneblockchain, future_parameter)310# Pruned block should still know the number of transactions311assert_equal(node.getblockheader(node.getblockhash(1))["nTx"], block1_details["nTx"])313# negative heights should raise an exception314assert_raises_rpc_error(-8, "Negative block height", node.pruneblockchain, -10)316# height=100 too low to prune first block file so this is a no-op317prune(100)318assert has_block(0), "blk00000.dat is missing when should still be there"320# Does nothing321node.pruneblockchain(height(0))322assert has_block(0), "blk00000.dat is missing when should still be there"324# height=500 should prune first file325prune(500)326assert not has_block(0), "blk00000.dat is still there, should be pruned by now"327assert has_block(1), "blk00001.dat is missing when should still be there"329# height=650 should prune second file330prune(650)331assert not has_block(1), "blk00001.dat is still there, should be pruned by now"333# height=1000 should not prune anything more, because tip-288 is in blk00002.dat.334prune(1000)335assert has_block(2), "blk00002.dat is still there, should be pruned by now"337# advance the tip so blk00002.dat and blk00003.dat can be pruned (the last 288 blocks should now be in blk00004.dat)338self.generate(node, MIN_BLOCKS_TO_KEEP, sync_fun=self.no_op)339prune(1000)340assert not has_block(2), "blk00002.dat is still there, should be pruned by now"341assert not has_block(3), "blk00003.dat is still there, should be pruned by now"343# stop node, start back up with auto-prune at 550 MiB, make sure still runs344self.restart_node(node_number, extra_args=["-prune=550"])346self.log.info("Success")348def test_wallet_rescan(self):349# check that the pruning node's wallet is still in good shape350self.log.info("Stop and start pruning node to trigger wallet rescan")351self.restart_node(2, extra_args=["-prune=550"])353self.wait_until(lambda: self.nodes[2].getwalletinfo()["scanning"] is False)354self.wait_until(lambda: self.nodes[2].getwalletinfo()["lastprocessedblock"]["height"] == self.nodes[2].getblockcount())356# check that wallet loads successfully when restarting a pruned node after IBD.357# this was reported to fail in #7494.358self.restart_node(5, extra_args=["-prune=550", "-blockfilterindex=1"]) # restart to trigger rescan360self.wait_until(lambda: self.nodes[5].getwalletinfo()["scanning"] is False)361self.wait_until(lambda: self.nodes[5].getwalletinfo()["lastprocessedblock"]["height"] == self.nodes[0].getblockcount())363def run_test(self):364self.log.info("Warning! This test requires 4GB of disk space")366self.log.info("Mining a big blockchain of 995 blocks")367self.create_big_chain()368# Chain diagram key:369# * blocks on main chain370# +,&,$,@ blocks on other forks371# X invalidated block372# N1 Node 1373#374# Start by mining a simple chain that all nodes have375# N0=N1=N2 **...*(995)377# stop manual-pruning node with 995 blocks378self.stop_node(3)379self.stop_node(4)381self.log.info("Check that we haven't started pruning yet because we're below PruneAfterHeight")382self.test_height_min()383# Extend this chain past the PruneAfterHeight384# N0=N1=N2 **...*(1020)386self.log.info("Check that we'll exceed disk space target if we have a very high stale block rate")387self.create_chain_with_staleblocks()388# Disconnect N0389# And mine a 24 block chain on N1 and a separate 25 block chain on N0390# N1=N2 **...*+...+(1044)391# N0 **...**...**(1045)392#393# reconnect nodes causing reorg on N1 and N2394# N1=N2 **...*(1020) *...**(1045)395# \396# +...+(1044)397#398# repeat this process until you have 12 stale forks hanging off the399# main chain on N1 and N2400# N0 *************************...***************************(1320)401#402# N1=N2 **...*(1020) *...**(1045) *.. ..**(1295) *...**(1320)403# \ \ \404# +...+(1044) &.. $...$(1319)406# Save some current chain state for later use407self.mainchainheight = self.nodes[2].getblockcount() # 1320408self.mainchainhash2 = self.nodes[2].getblockhash(self.mainchainheight)410self.log.info("Check that we can survive a 288 block reorg still")411self.reorg_test() # (1033, )412# Now create a 288 block reorg by mining a longer chain on N1413# First disconnect N1414# Then invalidate 1033 on main chain and 1032 on fork so height is 1032 on main chain415# N1 **...*(1020) **...**(1032)X..416# \417# ++...+(1031)X..418#419# Now mine 300 more blocks on N1420# N1 **...*(1020) **...**(1032) @@...@(1332)421# \ \422# \ X...423# \ \424# ++...+(1031)X.. ..425#426# Reconnect nodes and mine 220 more blocks on N1427# N1 **...*(1020) **...**(1032) @@...@@@(1552)428# \ \429# \ X...430# \ \431# ++...+(1031)X.. ..432#433# N2 **...*(1020) **...**(1032) @@...@@@(1552)434# \ \435# \ *...**(1320)436# \ \437# ++...++(1044) ..438#439# N0 ********************(1032) @@...@@@(1552)440# \441# *...**(1320)443self.log.info("Test that we can rerequest a block we previously pruned if needed for a reorg")444self.reorg_back()445# Verify that N2 still has block 1033 on current chain (@), but not on main chain (*)446# Invalidate 1033 on current chain (@) on N2 and we should be able to reorg to447# original main chain (*), but will require redownload of some blocks448# In order to have a peer we think we can download from, must also perform this invalidation449# on N0 and mine a new longest chain to trigger.450# Final result:451# N0 ********************(1032) **...****(1553)452# \453# X@...@@@(1552)454#455# N2 **...*(1020) **...**(1032) **...****(1553)456# \ \457# \ X@...@@@(1552)458# \459# +..460#461# N1 doesn't change because 1033 on main chain (*) is invalid463self.log.info("Test manual pruning with block indices")464self.manual_test(3, use_timestamp=False)466self.log.info("Test manual pruning with timestamps")467self.manual_test(4, use_timestamp=True)469self.log.info("Syncing node 5 to node 0")470self.connect_nodes(0, 5)471self.sync_blocks([self.nodes[0], self.nodes[5]], wait=5, timeout=300)473self.log.info("Test prune with a new index")474self.restart_node(0, extra_args=["-prune=550", "-blockfilterindex=1"])475node = self.nodes[0]476self.wait_until(lambda: node.getindexinfo()["basic block filter index"]["best_block_height"] >= 10, timeout=300)478if self.is_wallet_compiled():479self.log.info("Test wallet re-scan")480self.test_wallet_rescan()482self.log.info("Test it's not possible to rescan beyond pruned data")483self.test_rescan_blockchain()485self.log.info("Test invalid pruning command line options")486self.test_invalid_command_line_options()488self.log.info("Test scanblocks can not return pruned data")489self.test_scanblocks_pruned()491self.log.info("Test pruneheight reflects the presence of block and undo data")492self.test_pruneheight_undo_presence()494self.log.info("Done")496def test_scanblocks_pruned(self):497node = self.nodes[5]498genesis_blockhash = node.getblockhash(0)499false_positive_spk = bytes.fromhex("001400000000000000000000000000000000000cadcb")
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.