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) 2021-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 for assumeutxo, a means of quickly bootstrapping a node using6a serialized version of the UTXO set at a certain height, which corresponds7to a hash that has been compiled into bitcoind.9The assumeutxo value generated and used here is committed to in10`CRegTestParams::m_assumeutxo_data` in `src/kernel/chainparams.cpp`.11"""12import contextlib14from dataclasses import dataclass15from test_framework.blocktools import (16create_block,17)18from test_framework.compressor import (19compress_amount,20)21from test_framework.messages import (22CBlockHeader,23from_hex,24MAGIC_BYTES,25MAX_MONEY,26msg_headers,27ser_varint,28tx_from_hex,29)30from test_framework.p2p import (31P2PInterface,32)33from test_framework.test_framework import BitcoinTestFramework34from test_framework.util import (35assert_approx,36assert_equal,37assert_not_equal,38assert_raises_rpc_error,39dumb_sync_blocks,40ensure_for,41sha256sum_file,42try_rpc,43)44from test_framework.wallet import (45getnewdestination,46MiniWallet,47)48from test_framework.blocktools import (49REGTEST_N_BITS,50REGTEST_TARGET,51nbits_str,52target_str,53)55START_HEIGHT = 19956SNAPSHOT_BASE_HEIGHT = 29957FINAL_HEIGHT = 39958COMPLETE_IDX = {'synced': True, 'best_block_height': FINAL_HEIGHT}61class AssumeutxoTest(BitcoinTestFramework):63def set_test_params(self):64"""Use the pregenerated, deterministic chain up to height 199."""65self.num_nodes = 466self.rpc_timeout = 12067self.extra_args = [68[],69["-fastprune", "-prune=1", "-blockfilterindex=1", "-coinstatsindex=1"],70["-persistmempool=0","-txindex=1", "-blockfilterindex=1", "-coinstatsindex=1"],71[]72]74def setup_network(self):75"""Start with the nodes disconnected so that one can generate a snapshot76including blocks the other hasn't yet seen."""77self.add_nodes(4)78self.start_nodes(extra_args=self.extra_args)80def test_invalid_snapshot_scenarios(self, valid_snapshot_path):81self.log.info("Test different scenarios of loading invalid snapshot files")82with open(valid_snapshot_path, 'rb') as f:83valid_snapshot_contents = f.read()84bad_snapshot_path = valid_snapshot_path + '.mod'85node = self.nodes[1]87def expected_error(msg):88assert_raises_rpc_error(-32603, f"Unable to load UTXO snapshot: Population failed: {msg}", node.loadtxoutset, bad_snapshot_path)90self.log.info(" - snapshot file with invalid file magic")91parsing_error_code = -2292bad_magic = 0xf00f00f00093with open(bad_snapshot_path, 'wb') as f:94f.write(bad_magic.to_bytes(5, "big") + valid_snapshot_contents[5:])95assert_raises_rpc_error(parsing_error_code, "Unable to parse metadata: Invalid UTXO set snapshot magic bytes. Please check if this is indeed a snapshot file or if you are using an outdated snapshot format.", node.loadtxoutset, bad_snapshot_path)97self.log.info(" - snapshot file with unsupported version")98for version in [0, 1, 3]:99with open(bad_snapshot_path, 'wb') as f:100f.write(valid_snapshot_contents[:5] + version.to_bytes(2, "little") + valid_snapshot_contents[7:])101assert_raises_rpc_error(parsing_error_code, f"Unable to parse metadata: Version of snapshot {version} does not match any of the supported versions.", node.loadtxoutset, bad_snapshot_path)103self.log.info(" - snapshot file with mismatching network magic")104invalid_magics = [105# magic, name, real106[MAGIC_BYTES["mainnet"], "main", True],107[MAGIC_BYTES["testnet4"], "testnet4", True],108[MAGIC_BYTES["signet"], "signet", True],109[0x00000000.to_bytes(4, 'big'), "", False],110[0xffffffff.to_bytes(4, 'big'), "", False],111]112for [magic, name, real] in invalid_magics:113with open(bad_snapshot_path, 'wb') as f:114f.write(valid_snapshot_contents[:7] + magic + valid_snapshot_contents[11:])115if real:116assert_raises_rpc_error(parsing_error_code, f"Unable to parse metadata: The network of the snapshot ({name}) does not match the network of this node (regtest).", node.loadtxoutset, bad_snapshot_path)117else:118assert_raises_rpc_error(parsing_error_code, "Unable to parse metadata: This snapshot has been created for an unrecognized network. This could be a custom signet, a new testnet or possibly caused by data corruption.", node.loadtxoutset, bad_snapshot_path)120self.log.info(" - snapshot file referring to a block that is not in the assumeutxo parameters")121prev_block_hash = self.nodes[0].getblockhash(SNAPSHOT_BASE_HEIGHT - 1)122bogus_block_hash = "0" * 64 # Represents any unknown block hash123for bad_block_hash in [bogus_block_hash, prev_block_hash]:124with open(bad_snapshot_path, 'wb') as f:125f.write(valid_snapshot_contents[:11] + bytes.fromhex(bad_block_hash)[::-1] + valid_snapshot_contents[43:])127msg = f"Unable to load UTXO snapshot: assumeutxo block hash in snapshot metadata not recognized (hash: {bad_block_hash}). The following snapshot heights are available: 110, 200, 299."128assert_raises_rpc_error(-32603, msg, node.loadtxoutset, bad_snapshot_path)130self.log.info(" - snapshot file with wrong number of coins")131valid_num_coins = int.from_bytes(valid_snapshot_contents[43:43 + 8], "little")132for off in [-1, +1]:133with open(bad_snapshot_path, 'wb') as f:134f.write(valid_snapshot_contents[:43])135f.write((valid_num_coins + off).to_bytes(8, "little"))136f.write(valid_snapshot_contents[43 + 8:])137expected_error(msg="Bad snapshot - coins left over after deserializing 298 coins." if off == -1 else "Bad snapshot format or truncated snapshot after deserializing 299 coins.")139self.log.info(" - snapshot file with alternated but parsable UTXO data results in different hash")140cases = [141# (content, offset, wrong_hash, custom_message)142[b"\xff" * 32, 0, "43bb0fcc50f3789d50ce41891810bbc571d549a32e2bbaabfc036f73ae2a23f9", None], # wrong outpoint hash143[(2).to_bytes(1, "little"), 32, None, "Bad snapshot data after deserializing 2 coins."], # wrong txid coins count144[b"\xfd\xff\xff", 32, None, "Mismatch in coins count in snapshot metadata and actual snapshot data"], # txid coins count exceeds coins left145[b"\x01", 33, "0b0e96fd9a556157a68d9e6fe6e2c7562169c730347565ce0725032f824bea34", None], # wrong outpoint index146[b"\x83", 34, "52084c732f760fbcfb804d854815a9145111e93d7c7f3f7bfcb0ade1ea4f6b99", None], # wrong coin code VARINT147[b"\x80", 34, "c1a1308fcf04cbb14fc777467eb5e98e77a921b66eac4ad47877d0b6ed7b15b1", None], # another wrong coin code148[b"\x84\x58", 34, None, "Bad snapshot data after deserializing 0 coins"], # wrong coin case with height 364 and coinbase 0149[150# compressed txout value + scriptpubkey151ser_varint(compress_amount(MAX_MONEY + 1)) + ser_varint(0),152# txid + coins per txid + vout + coin height15332 + 1 + 1 + 2,154None,155"Bad snapshot data after deserializing 0 coins - bad tx out value"156], # Amount exceeds MAX_MONEY157]159for content, offset, wrong_hash, custom_message in cases:160with open(bad_snapshot_path, "wb") as f:161# Prior to offset: Snapshot magic, snapshot version, network magic, hash, coins count162f.write(valid_snapshot_contents[:(5 + 2 + 4 + 32 + 8 + offset)])163f.write(content)164f.write(valid_snapshot_contents[(5 + 2 + 4 + 32 + 8 + offset + len(content)):])166msg = custom_message if custom_message is not None else f"Bad snapshot content hash: expected 106b2c56233e378a824cf0d5ff2be42ed32c72f1605c9be288d00942908a40ac, got {wrong_hash}."167expected_error(msg)169def test_headers_not_synced(self, valid_snapshot_path):170for node in self.nodes[1:]:171msg = "Unable to load UTXO snapshot: The base block header (0c552ced4721c249a389eb9b08cb8da261cd46f0e7b5f9d064d48f3113406853) must appear in the headers chain. Make sure all headers are syncing, and call loadtxoutset again."172assert_raises_rpc_error(-32603, msg, node.loadtxoutset, valid_snapshot_path)174def test_invalid_chainstate_scenarios(self):175self.log.info("Test different scenarios of invalid snapshot chainstate in datadir")177self.log.info(" - snapshot chainstate referring to a block that is not in the assumeutxo parameters")178self.stop_node(0)179chainstate_snapshot_path = self.nodes[0].chain_path / "chainstate_snapshot"180chainstate_snapshot_path.mkdir()181with open(chainstate_snapshot_path / "base_blockhash", 'wb') as f:182f.write(b'z' * 32)184def expected_error(log_msg="", error_msg=""):185with self.nodes[0].assert_debug_log([log_msg]):186self.nodes[0].assert_start_raises_init_error(expected_msg=error_msg)188expected_error_msg = "Error: A fatal internal error occurred, see debug.log for details: Assumeutxo data not found for the given blockhash '7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a'."189error_details = "Assumeutxo data not found for the given blockhash"190expected_error(log_msg=error_details, error_msg=expected_error_msg)192# resurrect node again193(chainstate_snapshot_path / "base_blockhash").unlink()194chainstate_snapshot_path.rmdir()195self.start_node(0)197def test_invalid_mempool_state(self, dump_output_path):198self.log.info("Test bitcoind should fail when mempool not empty.")199node=self.nodes[2]200tx = MiniWallet(node).send_self_transfer(from_node=node)202assert tx['txid'] in node.getrawmempool()204# Attempt to load the snapshot on Node 2 and expect it to fail205msg = "Unable to load UTXO snapshot: Can't activate a snapshot when mempool not empty"206assert_raises_rpc_error(-32603, msg, node.loadtxoutset, dump_output_path)208self.restart_node(2, extra_args=self.extra_args[2])210def test_invalid_file_path(self):211self.log.info("Test bitcoind should fail when file path is invalid.")212node = self.nodes[0]213path = node.datadir_path / node.chain / "invalid" / "path"214assert_raises_rpc_error(-8, "Couldn't open file {} for reading.".format(path), node.loadtxoutset, path)216def test_snapshot_with_less_work(self, dump_output_path):217self.log.info("Test bitcoind should fail when snapshot has less accumulated work than this node.")218node = self.nodes[0]219msg = "Unable to load UTXO snapshot: Population failed: Work does not exceed active chainstate."220assert_raises_rpc_error(-32603, msg, node.loadtxoutset, dump_output_path)222def test_snapshot_block_invalidated(self, dump_output_path):223self.log.info("Test snapshot is not loaded when base block is invalid.")224node = self.nodes[0]225# We are testing the case where the base block is invalidated itself226# and also the case where one of its parents is invalidated.227for height in [SNAPSHOT_BASE_HEIGHT, SNAPSHOT_BASE_HEIGHT - 1]:228block_hash = node.getblockhash(height)229node.invalidateblock(block_hash)230assert_equal(node.getblockcount(), height - 1)231msg = "Unable to load UTXO snapshot: The base block header (0c552ced4721c249a389eb9b08cb8da261cd46f0e7b5f9d064d48f3113406853) is part of an invalid chain."232assert_raises_rpc_error(-32603, msg, node.loadtxoutset, dump_output_path)233node.reconsiderblock(block_hash)235def test_snapshot_in_a_divergent_chain(self, dump_output_path):236n0 = self.nodes[0]237n3 = self.nodes[3]238assert_equal(n0.getblockcount(), FINAL_HEIGHT)239assert_equal(n3.getblockcount(), START_HEIGHT)241self.log.info("Check importing a snapshot where current chain-tip is not an ancestor of the snapshot block but has less work")242# Generate a divergent chain in n3 up to 298243self.generate(n3, nblocks=99, sync_fun=self.no_op)244assert_equal(n3.getblockcount(), SNAPSHOT_BASE_HEIGHT - 1)246# Try importing the snapshot and assert its success247loaded = n3.loadtxoutset(dump_output_path)248assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)249normal, snapshot = n3.getchainstates()["chainstates"]250assert_equal(normal['blocks'], START_HEIGHT + 99)
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.