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) 2015-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 behavior of -maxuploadtarget.7* Verify that getdata requests for old blocks (>1week) are dropped8if uploadtarget has been reached.9* Verify that getdata requests for recent blocks are respected even10if uploadtarget has been reached.11* Verify that mempool requests lead to a disconnect if uploadtarget has been reached.12* Verify that the upload counters are reset after 24 hours.13"""14from collections import defaultdict15import time17from test_framework.messages import (18CInv,19MSG_BLOCK,20msg_getdata,21msg_mempool,22)23from test_framework.p2p import P2PInterface24from test_framework.test_framework import BitcoinTestFramework25from test_framework.util import (26assert_equal,27mine_large_block,28)29from test_framework.wallet import MiniWallet32UPLOAD_TARGET_MB = 80035class TestP2PConn(P2PInterface):36def __init__(self):37super().__init__()38self.block_receive_map = defaultdict(int)40def on_inv(self, message):41pass43def on_block(self, message):44self.block_receive_map[message.block.hash_int] += 146class MaxUploadTest(BitcoinTestFramework):48def set_test_params(self):49self.setup_clean_chain = True50self.num_nodes = 151self.extra_args = [[52f"-maxuploadtarget={UPLOAD_TARGET_MB}M",53]]55def assert_uploadtarget_state(self, *, target_reached, serve_historical_blocks):56"""Verify the node's current upload target state via the `getnettotals` RPC call."""57uploadtarget = self.nodes[0].getnettotals()["uploadtarget"]58assert_equal(uploadtarget["target_reached"], target_reached)59assert_equal(uploadtarget["serve_historical_blocks"], serve_historical_blocks)61def run_test(self):62# Initially, neither historical blocks serving limit nor total limit are reached63self.assert_uploadtarget_state(target_reached=False, serve_historical_blocks=True)65# Before we connect anything, we first set the time on the node66# to be in the past, otherwise things break because the CNode67# time counters can't be reset backward after initialization68old_time = int(time.time() - 2*60*60*24*7)69self.nodes[0].setmocktime(old_time)71# Generate some old blocks72self.wallet = MiniWallet(self.nodes[0])73self.generate(self.wallet, 130)75# p2p_conns[0] will only request old blocks76# p2p_conns[1] will only request new blocks77# p2p_conns[2] will test resetting the counters78p2p_conns = []80for _ in range(3):81# Don't use v2transport in this test (too slow with the unoptimized python ChaCha20 implementation)82p2p_conns.append(self.nodes[0].add_p2p_connection(TestP2PConn(), supports_v2_p2p=False))84# Now mine a big block85mine_large_block(self, self.wallet, self.nodes[0])87# Store the hash; we'll request this later88big_old_block = self.nodes[0].getbestblockhash()89old_block_size = self.nodes[0].getblock(big_old_block, True)['size']90big_old_block = int(big_old_block, 16)92# Advance to two days ago93self.nodes[0].setmocktime(int(time.time()) - 2*60*60*24)95# Mine one more block, so that the prior block looks old96mine_large_block(self, self.wallet, self.nodes[0])98# We'll be requesting this new block too99big_new_block = self.nodes[0].getbestblockhash()100big_new_block = int(big_new_block, 16)102# p2p_conns[0] will test what happens if we just keep requesting the103# the same big old block too many times (expect: disconnect)105getdata_request = msg_getdata()106getdata_request.inv.append(CInv(MSG_BLOCK, big_old_block))108max_bytes_per_day = UPLOAD_TARGET_MB * 1024 *1024109daily_buffer = 144 * 4000000110max_bytes_available = max_bytes_per_day - daily_buffer111success_count = max_bytes_available // old_block_size113# 576MB will be reserved for relaying new blocks, so expect this to114# succeed for ~235 tries.115for i in range(success_count):116p2p_conns[0].send_and_ping(getdata_request)117assert_equal(p2p_conns[0].block_receive_map[big_old_block], i+1)119assert_equal(len(self.nodes[0].getpeerinfo()), 3)120# At most a couple more tries should succeed (depending on how long121# the test has been running so far).122with self.nodes[0].assert_debug_log(expected_msgs=["historical block serving limit reached, disconnecting peer=0"]):123for _ in range(3):124p2p_conns[0].send_without_ping(getdata_request)125p2p_conns[0].wait_for_disconnect()126assert_equal(len(self.nodes[0].getpeerinfo()), 2)127self.log.info("Peer 0 disconnected after downloading old block too many times")129# Historical blocks serving limit is reached by now, but total limit still isn't130self.assert_uploadtarget_state(target_reached=False, serve_historical_blocks=False)132# Requesting the current block on p2p_conns[1] should succeed indefinitely,133# even when over the max upload target.134# We'll try 800 times135getdata_request.inv = [CInv(MSG_BLOCK, big_new_block)]136for i in range(800):137p2p_conns[1].send_and_ping(getdata_request)138assert_equal(p2p_conns[1].block_receive_map[big_new_block], i+1)140# Both historical blocks serving limit and total limit are reached141self.assert_uploadtarget_state(target_reached=True, serve_historical_blocks=False)143self.log.info("Peer 1 able to repeatedly download new block")145# But if p2p_conns[1] tries for an old block, it gets disconnected too.146getdata_request.inv = [CInv(MSG_BLOCK, big_old_block)]147with self.nodes[0].assert_debug_log(expected_msgs=["historical block serving limit reached, disconnecting peer=1"]):148p2p_conns[1].send_without_ping(getdata_request)149p2p_conns[1].wait_for_disconnect()150assert_equal(len(self.nodes[0].getpeerinfo()), 1)152self.log.info("Peer 1 disconnected after trying to download old block")154self.log.info("Advancing system time on node to clear counters...")156# If we advance the time by 24 hours, then the counters should reset,157# and p2p_conns[2] should be able to retrieve the old block.158self.nodes[0].setmocktime(int(time.time()))159p2p_conns[2].sync_with_ping()160p2p_conns[2].send_and_ping(getdata_request)161assert_equal(p2p_conns[2].block_receive_map[big_old_block], 1)162self.assert_uploadtarget_state(target_reached=False, serve_historical_blocks=True)164self.log.info("Peer 2 able to download old block")166self.nodes[0].disconnect_p2ps()168self.log.info("Restarting node 0 with download permission, bloom filter support and 1MB maxuploadtarget")169self.restart_node(0, ["[email protected]", "-peerbloomfilters", "-maxuploadtarget=1"])170# Total limit isn't reached after restart, but 1 MB is too small to serve historical blocks171self.assert_uploadtarget_state(target_reached=False, serve_historical_blocks=False)173# Reconnect to self.nodes[0]174peer = self.nodes[0].add_p2p_connection(TestP2PConn(), supports_v2_p2p=False)176# Sending mempool message shouldn't disconnect peer, as total limit isn't reached yet177peer.send_and_ping(msg_mempool())179#retrieve 20 blocks which should be enough to break the 1MB limit180getdata_request.inv = [CInv(MSG_BLOCK, big_new_block)]181for i in range(20):182peer.send_and_ping(getdata_request)183assert_equal(peer.block_receive_map[big_new_block], i+1)185# Total limit is exceeded186self.assert_uploadtarget_state(target_reached=True, serve_historical_blocks=False)188getdata_request.inv = [CInv(MSG_BLOCK, big_old_block)]189peer.send_and_ping(getdata_request)191self.log.info("Peer still connected after trying to download old block (download permission)")192peer_info = self.nodes[0].getpeerinfo()193assert_equal(len(peer_info), 1) # node is still connected194assert_equal(peer_info[0]['permissions'], ['download'])196self.log.info("Peer gets disconnected for a mempool request after limit is reached")197with self.nodes[0].assert_debug_log(expected_msgs=["mempool request with bandwidth limit reached, disconnecting peer=0"]):198peer.send_without_ping(msg_mempool())199peer.wait_for_disconnect()201self.log.info("Test passing an unparsable value to -maxuploadtarget throws an error")202self.stop_node(0)203self.nodes[0].assert_start_raises_init_error(extra_args=["-maxuploadtarget=abc"], expected_msg="Error: Unable to parse -maxuploadtarget: 'abc'")205if __name__ == '__main__':206MaxUploadTest(__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.