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.
501dump_output4['txoutset_hash'],502"147d1789e3e29a62e3e15b80078b50603afb5e1e70ea07001c94666afbde38df")503assert_not_equal(sha256sum_file(dump_output['path']), sha256sum_file(dump_output4['path']))505# Use a hash instead of a height506prev_snap_hash = n0.getblockhash(prev_snap_height)507dump_output5 = n0.dumptxoutset('utxos5.dat', rollback=prev_snap_hash)508assert_equal(sha256sum_file(dump_output4['path']), sha256sum_file(dump_output5['path']))510# Ensure n0 is back at the tip511assert_equal(n0.getblockchaininfo()["blocks"], FINAL_HEIGHT)513self.test_snapshot_with_less_work(dump_output['path'])514self.test_invalid_mempool_state(dump_output['path'])515self.test_invalid_snapshot_scenarios(dump_output['path'])516self.test_invalid_chainstate_scenarios()517self.test_invalid_file_path()518self.test_snapshot_block_invalidated(dump_output['path'])519self.test_snapshot_not_on_most_work_chain(dump_output['path'])521# Prune-node sanity check522assert 'NETWORK' not in n1.getnetworkinfo()['localservicesnames']524self.log.info(f"Loading snapshot into second node from {dump_output['path']}")525# This node's tip is on an ancestor block of the snapshot, which should526# be the normal case527loaded = n1.loadtxoutset(dump_output['path'])528assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT)529assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)531self.log.info("Confirm that local services remain unchanged")532# Since n1 is a pruned node, the 'NETWORK' service flag must always be unset.533self.assert_only_network_limited_service(n1)535self.log.info("Check that UTXO-querying RPCs operate on snapshot chainstate")536snapshot_hash = loaded['tip_hash']537snapshot_num_coins = loaded['coins_loaded']538# coinstatsindex might be not caught up yet and is not relevant for this test, so don't use it539utxo_info = n1.gettxoutsetinfo(use_index=False)540assert_equal(utxo_info['txouts'], snapshot_num_coins)541assert_equal(utxo_info['height'], SNAPSHOT_BASE_HEIGHT)542assert_equal(utxo_info['bestblock'], snapshot_hash)544self.log.info("Check that getblockchaininfo returns information about the background validation process")545expected_keys = [546"snapshotheight",547"blocks",548"bestblockhash",549"mediantime",550"chainwork",551"verificationprogress"552]553res = n1.getblockchaininfo()554assert "backgroundvalidation" in res.keys()555bv_res = res["backgroundvalidation"]556assert_equal(sorted(expected_keys), sorted(bv_res.keys()))557assert_equal(bv_res["snapshotheight"], SNAPSHOT_BASE_HEIGHT)558assert_equal(bv_res["blocks"], START_HEIGHT)559assert_equal(bv_res["bestblockhash"], n1.getblockhash(START_HEIGHT))560block = n1.getblockheader(bv_res["bestblockhash"])561assert_equal(bv_res["mediantime"], block["mediantime"])562assert_equal(bv_res["chainwork"], block["chainwork"])563background_tx_count = n1.getchaintxstats(blockhash=bv_res["bestblockhash"])["txcount"]564snapshot_tx_count = n1.getchaintxstats(blockhash=snapshot_hash)["txcount"]565expected_verification_progress = background_tx_count / snapshot_tx_count566assert_approx(bv_res["verificationprogress"], expected_verification_progress, vspan=0.01)568# find coinbase output at snapshot height on node0 and scan for it on node1,569# where the block is not available, but the snapshot was loaded successfully570coinbase_tx = n0.getblock(snapshot_hash, verbosity=2)['tx'][0]571assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", n1.getblock, snapshot_hash)572coinbase_output_descriptor = coinbase_tx['vout'][0]['scriptPubKey']['desc']573scan_result = n1.scantxoutset('start', [coinbase_output_descriptor])574assert_equal(scan_result['success'], True)575assert_equal(scan_result['txouts'], snapshot_num_coins)576assert_equal(scan_result['height'], SNAPSHOT_BASE_HEIGHT)577assert_equal(scan_result['bestblock'], snapshot_hash)578scan_utxos = [(coin['txid'], coin['vout']) for coin in scan_result['unspents']]579assert (coinbase_tx['txid'], 0) in scan_utxos581txout_result = n1.gettxout(coinbase_tx['txid'], 0)582assert_equal(txout_result['scriptPubKey']['desc'], coinbase_output_descriptor)584def check_tx_counts(final: bool) -> None:585"""Check nTx and nChainTx intermediate values right after loading586the snapshot, and final values after the snapshot is validated."""587for height, block in blocks.items():588tx = n1.getblockheader(block.hash)["nTx"]589stats = n1.getchaintxstats(nblocks=1, blockhash=block.hash)590chain_tx = stats.get("txcount", None)591window_tx_count = stats.get("window_tx_count", None)592tx_rate = stats.get("txrate", None)593window_interval = stats.get("window_interval")595# Intermediate nTx of the starting block should be set, but nTx of596# later blocks should be 0 before they are downloaded.597# The window_tx_count of one block is equal to the blocks tx count.598# If the window tx count is unknown, the value is missing.599# The tx_rate is calculated from window_tx_count and window_interval600# when possible.601if final or height == START_HEIGHT:602assert_equal(tx, block.tx)603assert_equal(window_tx_count, tx)604if window_interval > 0:605assert_approx(tx_rate, window_tx_count / window_interval, vspan=0.1)606else:607assert_equal(tx_rate, None)608else:609assert_equal(tx, 0)610assert_equal(window_tx_count, None)612# Intermediate nChainTx of the starting block and snapshot block613# should be set, but others should be None until they are downloaded.614if final or height in (START_HEIGHT, SNAPSHOT_BASE_HEIGHT):615assert_equal(chain_tx, block.chain_tx)616else:617assert_equal(chain_tx, None)619check_tx_counts(final=False)621normal, snapshot = n1.getchainstates()["chainstates"]622assert_equal(normal['blocks'], START_HEIGHT)623assert_equal(normal.get('snapshot_blockhash'), None)624assert_equal(normal['validated'], True)625assert_equal(snapshot['blocks'], SNAPSHOT_BASE_HEIGHT)626assert_equal(snapshot['snapshot_blockhash'], dump_output['base_hash'])627assert_equal(snapshot['validated'], False)629assert_equal(n1.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)631self.log.info("Submit a stale block that forked off the chain before the snapshot")632# Normally a block like this would not be downloaded, but if it is633# submitted early before the background chain catches up to the fork634# point, it winds up in m_blocks_unlinked and triggers a corner case635# that previously crashed CheckBlockIndex.636n1.submitblock(stale_block)637n1.getchaintips()638n1.getblock(stale_hash)640self.log.info("Submit a spending transaction for a snapshot chainstate coin to the mempool")641# spend the coinbase output of the first block that is not available on node1642spend_coin_blockhash = n1.getblockhash(START_HEIGHT + 1)643assert_raises_rpc_error(-1, "Block not available (not fully downloaded)", n1.getblock, spend_coin_blockhash)644prev_tx = n0.getblock(spend_coin_blockhash, 3)['tx'][0]645prevout = {"txid": prev_tx['txid'], "vout": 0, "scriptPubKey": prev_tx['vout'][0]['scriptPubKey']['hex']}646privkey = n0.get_deterministic_priv_key().key647raw_tx = n1.createrawtransaction([prevout], {getnewdestination()[2]: 24.99})648signed_tx = n1.signrawtransactionwithkey(raw_tx, [privkey], [prevout])['hex']649signed_txid = tx_from_hex(signed_tx).txid_hex651assert n1.gettxout(prev_tx['txid'], 0) is not None652n1.sendrawtransaction(signed_tx)653assert signed_txid in n1.getrawmempool()654assert not n1.gettxout(prev_tx['txid'], 0)656PAUSE_HEIGHT = FINAL_HEIGHT - 40658self.log.info(f"Sync node up to height {PAUSE_HEIGHT}")659# During snapshot tip sync, the node must remain in 'limited' mode.660self.assert_only_network_limited_service(n1)661dumb_sync_blocks(src=n0, dst=n1, height=PAUSE_HEIGHT)663self.log.info("Checking that blocks are segmented on disk")664assert self.has_blockfile(n1, "00000"), "normal blockfile missing"665assert self.has_blockfile(n1, "00001"), "assumed blockfile missing"666assert not self.has_blockfile(n1, "00002"), "too many blockfiles"668# The node must remain in 'limited' mode669self.assert_only_network_limited_service(n1)671# Send snapshot block to n1 out of order. This makes the test less672# realistic because normally the snapshot block is one of the last673# blocks downloaded, but its useful to test because it triggers more674# corner cases in ReceivedBlockTransactions() and CheckBlockIndex()675# setting and testing nChainTx values, and it exposed previous bugs.676snapshot_hash = n0.getblockhash(SNAPSHOT_BASE_HEIGHT)677snapshot_block = n0.getblock(snapshot_hash, 0)678n1.submitblock(snapshot_block)680self.connect_nodes(0, 1)682self.log.info(f"Ensuring snapshot chain syncs to tip. ({FINAL_HEIGHT})")683self.wait_until(lambda: n1.getchainstates()['chainstates'][-1]['blocks'] == FINAL_HEIGHT)684self.sync_blocks(nodes=(n0, n1))686self.log.info("Ensuring background validation completes")687self.wait_until(lambda: len(n1.getchainstates()['chainstates']) == 1)689# Since n1 is a pruned node, it will not signal NODE_NETWORK after690# completing the background sync.691self.assert_only_network_limited_service(n1)693# Ensure indexes have synced.694completed_idx_state = {695'basic block filter index': COMPLETE_IDX,696'coinstatsindex': COMPLETE_IDX,697}698self.wait_until(lambda: n1.getindexinfo() == completed_idx_state)700self.log.info("Re-check nTx and nChainTx values")701check_tx_counts(final=True)703for i in (0, 1):704n = self.nodes[i]705self.log.info(f"Restarting node {i} to ensure (Check|Load)BlockIndex passes")706with self.assert_disk_cleanup(n, i == 1):707self.restart_node(i, extra_args=self.extra_args[i])709assert_equal(n.getblockchaininfo()["blocks"], FINAL_HEIGHT)711chainstate, = n.getchainstates()['chainstates']712assert_equal(chainstate['blocks'], FINAL_HEIGHT)714if i != 0:715# Ensure indexes have synced for the assumeutxo node716self.wait_until(lambda: n.getindexinfo() == completed_idx_state)719# Node 2: all indexes + reindex720# -----------------------------722self.log.info("-- Testing all indexes + reindex")723assert_equal(n2.getblockcount(), START_HEIGHT)724assert 'NETWORK' in n2.getnetworkinfo()['localservicesnames'] # sanity check726self.log.info(f"Loading snapshot into third node from {dump_output['path']}")727loaded = n2.loadtxoutset(dump_output['path'])728assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT)729assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)731# Even though n2 is a full node, it will unset the 'NETWORK' service flag during snapshot loading.732# This indicates other peers that the node will temporarily not provide historical blocks.733self.log.info("Check node2 updated the local services during snapshot load")734self.assert_only_network_limited_service(n2)736for reindex_arg in ['-reindex=1', '-reindex-chainstate=1']:737self.log.info(f"Check that restarting with {reindex_arg} will delete the snapshot chainstate")738self.restart_node(2, extra_args=[reindex_arg, *self.extra_args[2]])739assert_equal(1, len(n2.getchainstates()["chainstates"]))740for i in range(1, 300):741block = n0.getblock(n0.getblockhash(i), 0)742n2.submitheader(block)743loaded = n2.loadtxoutset(dump_output['path'])744assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT)745assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)747normal, snapshot = n2.getchainstates()['chainstates']748assert_equal(normal['blocks'], START_HEIGHT)749assert_equal(normal.get('snapshot_blockhash'), None)750assert_equal(normal['validated'], True)
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.