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) 2022-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 initial headers download and timeout behavior7Test that we only try to initially sync headers from one peer (until our chain8is close to caught up), and that each block announcement results in only one9additional peer receiving a getheaders message.11Also test peer timeout during initial headers sync, including normal peer12disconnection vs noban peer behavior.13"""15from test_framework.test_framework import BitcoinTestFramework16from test_framework.messages import (17CInv,18MSG_BLOCK,19msg_headers,20msg_inv,21)22from test_framework.p2p import (23p2p_lock,24P2PInterface,25)26from test_framework.util import (27assert_equal,28)29import math30import random31import time33# Constants from net_processing34HEADERS_DOWNLOAD_TIMEOUT_BASE_SEC = 15 * 6035HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER_MS = 136POW_TARGET_SPACING_SEC = 10 * 6039def calculate_headers_timeout(best_header_time, current_time):40seconds_since_best_header = current_time - best_header_time41# Using ceil ensures the calculated timeout is >= actual timeout and not lower42# because of precision loss from conversion to seconds43variable_timeout_sec = math.ceil(HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER_MS / 1_000 *44seconds_since_best_header / POW_TARGET_SPACING_SEC)45return int(current_time + HEADERS_DOWNLOAD_TIMEOUT_BASE_SEC + variable_timeout_sec)48class HeadersSyncTest(BitcoinTestFramework):49def set_test_params(self):50self.setup_clean_chain = True51self.num_nodes = 153def announce_random_block(self, peers):54new_block_announcement = msg_inv(inv=[CInv(MSG_BLOCK, random.randrange(1<<256))])55for p in peers:56p.send_and_ping(new_block_announcement)58def assert_single_getheaders_recipient(self, peers):59count = 060receiving_peer = None61for p in peers:62with p2p_lock:63if "getheaders" in p.last_message:64count += 165receiving_peer = p66assert_equal(count, 1)67return receiving_peer69def test_initial_headers_sync(self):70self.log.info("Test initial headers sync")72self.log.info("Adding a peer to node0")73peer1 = self.nodes[0].add_p2p_connection(P2PInterface())74best_block_hash = int(self.nodes[0].getbestblockhash(), 16)76# Wait for peer1 to receive a getheaders77peer1.wait_for_getheaders(block_hash=best_block_hash)78# An empty reply will clear the outstanding getheaders request,79# allowing additional getheaders requests to be sent to this peer in80# the future.81peer1.send_without_ping(msg_headers())83self.log.info("Connecting two more peers to node0")84# Connect 2 more peers; they should not receive a getheaders yet85peer2 = self.nodes[0].add_p2p_connection(P2PInterface())86peer3 = self.nodes[0].add_p2p_connection(P2PInterface())88all_peers = [peer1, peer2, peer3]90self.log.info("Verify that peer2 and peer3 don't receive a getheaders after connecting")91for p in all_peers:92p.sync_with_ping()93with p2p_lock:94assert "getheaders" not in peer2.last_message95assert "getheaders" not in peer3.last_message97self.log.info("Have all peers announce a new block")98self.announce_random_block(all_peers)100self.log.info("Check that peer1 receives a getheaders in response")101peer1.wait_for_getheaders(block_hash=best_block_hash)102peer1.send_without_ping(msg_headers()) # Send empty response, see above104self.log.info("Check that exactly 1 of {peer2, peer3} received a getheaders in response")105peer_receiving_getheaders = self.assert_single_getheaders_recipient([peer2, peer3])106peer_receiving_getheaders.send_without_ping(msg_headers()) # Send empty response, see above108self.log.info("Announce another new block, from all peers")109self.announce_random_block(all_peers)111self.log.info("Check that peer1 receives a getheaders in response")112peer1.wait_for_getheaders(block_hash=best_block_hash)114self.log.info("Check that the remaining peer received a getheaders as well")115expected_peer = peer2116if peer2 == peer_receiving_getheaders:117expected_peer = peer3119expected_peer.wait_for_getheaders(block_hash=best_block_hash)121def setup_timeout_test_peers(self):122self.log.info("Add peer1 and check it receives an initial getheaders request")123node = self.nodes[0]124with node.assert_debug_log(expected_msgs=["initial getheaders (0) to peer=0"]):125peer1 = node.add_p2p_connection(P2PInterface())126peer1.wait_for_getheaders(block_hash=int(node.getbestblockhash(), 16))128self.log.info("Add outbound peer2")129# This peer has to be outbound otherwise the stalling peer is130# protected from disconnection131peer2 = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="outbound-full-relay")133assert_equal(node.num_test_p2p_connections(), 2)134return peer1, peer2136def trigger_headers_timeout(self):137self.log.info("Trigger the headers download timeout by advancing mock time")138# The node has not received any headers from peers yet139# So the best header time is the genesis block time140best_header_time = self.nodes[0].getblockchaininfo()["time"]141current_time = self.nodes[0].mocktime142timeout = calculate_headers_timeout(best_header_time, current_time)143# The calculated timeout above is always >= actual timeout, but we still need144# +1 to trigger the timeout when both values are equal145self.nodes[0].setmocktime(timeout + 1)147def test_normal_peer_timeout(self):148self.log.info("Test peer disconnection on header timeout")149self.restart_node(0)150self.nodes[0].setmocktime(int(time.time()))151peer1, peer2 = self.setup_timeout_test_peers()153with self.nodes[0].assert_debug_log(["Timeout downloading headers, disconnecting peer=0"]):154self.trigger_headers_timeout()156self.log.info("Check that stalling peer1 is disconnected")157peer1.wait_for_disconnect()158assert_equal(self.nodes[0].num_test_p2p_connections(), 1)160self.log.info("Check that peer2 receives a getheaders request")161peer2.wait_for_getheaders(block_hash=int(self.nodes[0].getbestblockhash(), 16))163def test_noban_peer_timeout(self):164self.log.info("Test noban peer on header timeout")165self.restart_node(0, extra_args=['[email protected]'])166self.nodes[0].setmocktime(int(time.time()))167peer1, peer2 = self.setup_timeout_test_peers()169with self.nodes[0].assert_debug_log(["Timeout downloading headers from noban peer, not disconnecting peer=0"]):170self.trigger_headers_timeout()172self.log.info("Check that noban peer1 is not disconnected")173peer1.sync_with_ping()174assert_equal(self.nodes[0].num_test_p2p_connections(), 2)176self.log.info("Check that exactly 1 of {peer1, peer2} receives a getheaders")177self.assert_single_getheaders_recipient([peer1, peer2])179def run_test(self):180self.test_initial_headers_sync()181self.test_normal_peer_timeout()182self.test_noban_peer_timeout()185if __name__ == '__main__':186HeadersSyncTest(__file__).main()
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.