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.
251assert_equal(snapshot['blocks'], SNAPSHOT_BASE_HEIGHT)253# Both states should have the same nBits and target254assert_equal(normal['bits'], nbits_str(REGTEST_N_BITS))255assert_equal(normal['bits'], snapshot['bits'])256assert_equal(normal['target'], target_str(REGTEST_TARGET))257assert_equal(normal['target'], snapshot['target'])259# Now lets sync the nodes and wait for the background validation to finish260self.connect_nodes(0, 3)261self.sync_blocks(nodes=(n0, n3))262self.wait_until(lambda: len(n3.getchainstates()['chainstates']) == 1)264def test_snapshot_not_on_most_work_chain(self, dump_output_path):265self.log.info("Test snapshot is not loaded when the node knows the headers of another chain with more work.")266node0 = self.nodes[0]267node1 = self.nodes[1]268# Create an alternative chain of 2 new blocks, forking off the main chain at the block before the snapshot block.269# This simulates a longer chain than the main chain when submitting these two block headers to node 1 because it is only aware of270# the main chain headers up to the snapshot height.271parent_block_hash = node0.getblockhash(SNAPSHOT_BASE_HEIGHT - 1)272block_time = node0.getblock(node0.getbestblockhash())['time'] + 1273fork_block1 = create_block(int(parent_block_hash, 16), height=SNAPSHOT_BASE_HEIGHT, ntime=block_time)274fork_block1.solve()275fork_block2 = create_block(fork_block1.hash_int, height=SNAPSHOT_BASE_HEIGHT + 1, ntime=block_time + 1)276fork_block2.solve()277node1.submitheader(fork_block1.serialize().hex())278node1.submitheader(fork_block2.serialize().hex())279msg = "A forked headers-chain with more work than the chain with the snapshot base block header exists. Please proceed to sync without AssumeUtxo."280assert_raises_rpc_error(-32603, msg, node1.loadtxoutset, dump_output_path)281# Cleanup: submit two more headers of the snapshot chain to node 1, so that it is the most-work chain again and loading282# the snapshot in future subtests succeeds283main_block1 = node0.getblock(node0.getblockhash(SNAPSHOT_BASE_HEIGHT + 1), 0)284main_block2 = node0.getblock(node0.getblockhash(SNAPSHOT_BASE_HEIGHT + 2), 0)285node1.submitheader(main_block1)286node1.submitheader(main_block2)288def test_sync_from_assumeutxo_node(self, snapshot):289"""290This test verifies that:2911. An IBD node can sync headers from an AssumeUTXO node at any time.2922. IBD nodes do not request historical blocks from AssumeUTXO nodes while they are syncing the background-chain.2933. The assumeUTXO node dynamically adjusts the network services it offers according to its state.2944. IBD nodes can fully sync from AssumeUTXO nodes after they finish the background-chain sync.295"""296self.log.info("Testing IBD-sync from assumeUTXO node")297# Node2 starts clean and loads the snapshot.298# Node3 starts clean and seeks to sync-up from snapshot_node.299miner = self.nodes[0]300snapshot_node = self.nodes[2]301ibd_node = self.nodes[3]303# Start test fresh by cleaning up node directories304for node in (snapshot_node, ibd_node):305self.stop_node(node.index)306self.cleanup_folder(node.chain_path)307self.start_node(node.index, extra_args=self.extra_args[node.index])309# Sync-up headers chain on snapshot_node to load snapshot310headers_provider_conn = snapshot_node.add_p2p_connection(P2PInterface())311headers_provider_conn.wait_for_getheaders()312msg = msg_headers()313for block_num in range(1, miner.getblockcount()+1):314msg.headers.append(from_hex(CBlockHeader(), miner.getblockheader(miner.getblockhash(block_num), verbose=False)))315headers_provider_conn.send_without_ping(msg)317# Ensure headers arrived318default_value = {'status': ''} # No status319headers_tip_hash = miner.getbestblockhash()320self.wait_until(lambda: next(filter(lambda x: x['hash'] == headers_tip_hash, snapshot_node.getchaintips()), default_value)['status'] == "headers-only")321snapshot_node.disconnect_p2ps()323# Load snapshot324snapshot_node.loadtxoutset(snapshot['path'])326# Connect nodes and verify the ibd_node can sync-up the headers-chain from the snapshot_node327self.connect_nodes(ibd_node.index, snapshot_node.index)328snapshot_block_hash = snapshot['base_hash']329self.wait_until(lambda: next(filter(lambda x: x['hash'] == snapshot_block_hash, ibd_node.getchaintips()), default_value)['status'] == "headers-only")331# Once the headers-chain is synced, the ibd_node must avoid requesting historical blocks from the snapshot_node.332# If it does request such blocks, the snapshot_node will ignore requests it cannot fulfill, causing the ibd_node333# to stall. This stall could last for up to 10 min, ultimately resulting in an abrupt disconnection due to the334# ibd_node's perceived unresponsiveness.335ensure_for(duration=3, f=lambda: len(ibd_node.getpeerinfo()[0]['inflight']) == 0)337# Now disconnect nodes and finish background chain sync338self.disconnect_nodes(ibd_node.index, snapshot_node.index)339self.connect_nodes(snapshot_node.index, miner.index)340self.sync_blocks(nodes=(miner, snapshot_node))341# Check the base snapshot block was stored and ensure node signals full-node service support342self.wait_until(lambda: not try_rpc(-1, "Block not available (not fully downloaded)", snapshot_node.getblock, snapshot_block_hash))343self.wait_until(lambda: 'NETWORK' in snapshot_node.getnetworkinfo()['localservicesnames'])345# Now that the snapshot_node is synced, verify the ibd_node can sync from it346self.connect_nodes(snapshot_node.index, ibd_node.index)347assert 'NETWORK' in ibd_node.getpeerinfo()[0]['servicesnames']348self.sync_blocks(nodes=(ibd_node, snapshot_node))350def test_sync_to_most_work_chain_after_background_validation(self):351"""352After background validation completes, node should be able353to download and process blocks from peers without the snapshot block in their chain.354"""355self.log.info("Testing sync to the most-work chain without the snapshot block after background validation")357forking_node = self.nodes[0]358snapshot_node = self.nodes[2] # Has already completed background validation360self.log.info("Forking node switches to an alternative chain that forks one block before the snapshot block")361fork_point = SNAPSHOT_BASE_HEIGHT - 1362forking_node_old_height = forking_node.getblockcount()363forking_node_old_chainwork = int(forking_node.getblockchaininfo()['chainwork'], 16)364forking_node.invalidateblock(forking_node.getblockhash(fork_point + 1))366self.log.info("Mine one more block than original chain to make the new chain have most work")367self.generate(forking_node, nblocks=(forking_node_old_height - fork_point) + 1, sync_fun=self.no_op)368assert int(forking_node.getblockchaininfo()['chainwork'], 16) > forking_node_old_chainwork370self.log.info("Snapshot node should reorg to the most-work chain without the snapshot block")371self.sync_blocks(nodes=(snapshot_node, forking_node))373def assert_only_network_limited_service(self, node):374node_services = node.getnetworkinfo()['localservicesnames']375assert 'NETWORK' not in node_services376assert 'NETWORK_LIMITED' in node_services378@contextlib.contextmanager379def assert_disk_cleanup(self, node, assumeutxo_used):380"""381Ensure an assumeutxo node is cleaning up the background chainstate382"""383msg = []384if assumeutxo_used:385# Check that the snapshot actually existed before restart386assert (node.datadir_path / node.chain / "chainstate_snapshot").exists()387msg = ["cleaning up unneeded background chainstate"]389with node.assert_debug_log(msg):390yield392assert not (node.datadir_path / node.chain / "chainstate_snapshot").exists()394def run_test(self):395"""396Bring up two (disconnected) nodes, mine some new blocks on the first,397and generate a UTXO snapshot.399Load the snapshot into the second, ensure it syncs to tip and completes400background validation when connected to the first.401"""402n0 = self.nodes[0]403n1 = self.nodes[1]404n2 = self.nodes[2]405n3 = self.nodes[3]407self.mini_wallet = MiniWallet(n0)409# Mock time for a deterministic chain410for n in self.nodes:411n.setmocktime(n.getblockheader(n.getbestblockhash())['time'])413# Generate a series of blocks that `n0` will have in the snapshot,414# but that n1 and n2 don't yet see.415assert_equal(n0.getblockcount(), START_HEIGHT)416blocks = {START_HEIGHT: Block(n0.getbestblockhash(), 1, START_HEIGHT + 1)}417for i in range(100):418block_tx = 1419if i % 3 == 0:420self.mini_wallet.send_self_transfer(from_node=n0)421block_tx += 1422self.generate(n0, nblocks=1, sync_fun=self.no_op)423height = n0.getblockcount()424hash = n0.getbestblockhash()425blocks[height] = Block(hash, block_tx, blocks[height-1].chain_tx + block_tx)426if i == 4:427# Create a stale block that forks off the main chain before the snapshot.428temp_invalid = n0.getbestblockhash()429n0.invalidateblock(temp_invalid)430stale_hash = self.generateblock(n0, output="raw(aaaa)", transactions=[], sync_fun=self.no_op)["hash"]431n0.invalidateblock(stale_hash)432n0.reconsiderblock(temp_invalid)433stale_block = n0.getblock(stale_hash, 0)436self.log.info("-- Testing assumeutxo + some indexes + pruning")438assert_equal(n0.getblockcount(), SNAPSHOT_BASE_HEIGHT)439assert_equal(n1.getblockcount(), START_HEIGHT)441self.log.info(f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}")442dump_output = n0.dumptxoutset('utxos.dat', "latest")444self.log.info("Test loading snapshot when the node tip is on the same block as the snapshot")445assert_equal(n0.getblockcount(), SNAPSHOT_BASE_HEIGHT)446assert_equal(n0.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)447self.test_snapshot_with_less_work(dump_output['path'])449self.log.info("Test loading snapshot when headers are not synced")450self.test_headers_not_synced(dump_output['path'])452# In order for the snapshot to activate, we have to ferry over the new453# headers to n1 and n2 so that they see the header of the snapshot's454# base block while disconnected from n0.455for i in range(1, 300):456block = n0.getblock(n0.getblockhash(i), 0)457# make n1 and n2 aware of the new header, but don't give them the458# block.459n1.submitheader(block)460n2.submitheader(block)461n3.submitheader(block)463# Ensure everyone is seeing the same headers.464for n in self.nodes:465assert_equal(n.getblockchaininfo()["headers"], SNAPSHOT_BASE_HEIGHT)467assert_equal(n0.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)469def check_dump_output(output):470assert_equal(471output['txoutset_hash'],472"106b2c56233e378a824cf0d5ff2be42ed32c72f1605c9be288d00942908a40ac")473assert_equal(output["nchaintx"], blocks[SNAPSHOT_BASE_HEIGHT].chain_tx)475check_dump_output(dump_output)477# Mine more blocks on top of the snapshot that n1 hasn't yet seen. This478# will allow us to test n1's sync-to-tip on top of a snapshot.479self.generate(n0, nblocks=100, sync_fun=self.no_op)481assert_equal(n0.getblockcount(), FINAL_HEIGHT)482assert_equal(n1.getblockcount(), START_HEIGHT)484assert_equal(n0.getblockchaininfo()["blocks"], FINAL_HEIGHT)486self.log.info("Check that dumptxoutset works for past block heights")487# rollback defaults to the snapshot base height488dump_output2 = n0.dumptxoutset('utxos2.dat', "rollback")489check_dump_output(dump_output2)490assert_equal(sha256sum_file(dump_output['path']), sha256sum_file(dump_output2['path']))492# Rollback with specific height493dump_output3 = n0.dumptxoutset('utxos3.dat', rollback=SNAPSHOT_BASE_HEIGHT)494check_dump_output(dump_output3)495assert_equal(sha256sum_file(dump_output['path']), sha256sum_file(dump_output3['path']))497# Specified height that is not a snapshot height498prev_snap_height = SNAPSHOT_BASE_HEIGHT - 1499dump_output4 = n0.dumptxoutset(path='utxos4.dat', rollback=prev_snap_height)500assert_equal(
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.