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) The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.6"""7Test that an attacker can't degrade compact block relay by sending unsolicited8mutated blocks to clear in-flight blocktxn requests from other honest peers.9"""11from test_framework.p2p import P2PInterface12from test_framework.messages import (13BlockTransactions,14msg_cmpctblock,15msg_block,16msg_blocktxn,17msg_headers,18HeaderAndShortIDs,19msg_sendcmpct,20)21from test_framework.test_framework import BitcoinTestFramework22from test_framework.blocktools import (23COINBASE_MATURITY,24create_block,25add_witness_commitment,26NORMAL_GBT_REQUEST_PARAMS,27)28from test_framework.util import assert_equal29from test_framework.wallet import MiniWallet30import copy32class MutatedBlocksTest(BitcoinTestFramework):33def set_test_params(self):34self.setup_clean_chain = True35self.num_nodes = 136self.extra_args = [37[38"-testactivationheight=segwit@1", # causes unconnected headers/blocks to not have segwit considered deployed39],40]42def run_test(self):43self.wallet = MiniWallet(self.nodes[0])44self.generate(self.wallet, COINBASE_MATURITY)46honest_relayer = self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="outbound-full-relay")47honest_relayer.send_and_ping(msg_sendcmpct())48attacker = self.nodes[0].add_p2p_connection(P2PInterface())50# Create new block with two transactions (coinbase + 1 self-transfer).51# The self-transfer transaction is needed to trigger a compact block52# `getblocktxn` roundtrip.53tx = self.wallet.create_self_transfer()["tx"]54block = create_block(tmpl=self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS), txlist=[tx])55add_witness_commitment(block)56block.solve()58# Create mutated version of the block by changing the transaction59# version on the self-transfer.60mutated_block = copy.deepcopy(block)61mutated_block.vtx[1].version = 463# Send block header through the honest relayer64honest_relayer.send_without_ping(msg_headers([block]))65honest_relayer.wait_for_getdata([block.hash_int], timeout=30)67# Announce the new block via a compact block through the honest relayer68cmpctblock = HeaderAndShortIDs()69cmpctblock.initialize_from_block(block, use_witness=True)70honest_relayer.send_without_ping(msg_cmpctblock(cmpctblock.to_p2p()))72# Wait for a `getblocktxn` that attempts to fetch the self-transfer73def self_transfer_requested():74if not honest_relayer.last_message.get('getblocktxn'):75return False77get_block_txn = honest_relayer.last_message['getblocktxn']78return get_block_txn.block_txn_request.blockhash == block.hash_int and \79get_block_txn.block_txn_request.indexes == [1]80honest_relayer.wait_until(self_transfer_requested, timeout=5)82# Block at height 101 should be the only one in flight from peer 083peer_info_prior_to_attack = self.nodes[0].getpeerinfo()84assert_equal(peer_info_prior_to_attack[0]['id'], 0)85assert_equal([101], peer_info_prior_to_attack[0]["inflight"])87# Attempt to clear the honest relayer's download request by sending the88# mutated block (as the attacker).89with self.nodes[0].assert_debug_log(expected_msgs=["Block mutated: bad-txnmrklroot, hashMerkleRoot mismatch"]):90attacker.send_without_ping(msg_block(mutated_block))91# Attacker should get disconnected for sending a mutated block92attacker.wait_for_disconnect(timeout=5)94# Block at height 101 should *still* be the only block in-flight from95# peer 096peer_info_after_attack = self.nodes[0].getpeerinfo()97assert_equal(peer_info_after_attack[0]['id'], 0)98assert_equal([101], peer_info_after_attack[0]["inflight"])100# The honest relayer should be able to complete relaying the block by101# sending the blocktxn that was requested.102block_txn = msg_blocktxn()103block_txn.block_transactions = BlockTransactions(blockhash=block.hash_int, transactions=[tx])104honest_relayer.send_and_ping(block_txn)105assert_equal(self.nodes[0].getbestblockhash(), block.hash_hex)107# Check that unexpected-witness mutation check doesn't trigger on a header that doesn't connect to anything108assert_equal(len(self.nodes[0].getpeerinfo()), 1)109attacker = self.nodes[0].add_p2p_connection(P2PInterface())110block_missing_prev = copy.deepcopy(block)111block_missing_prev.hashPrevBlock = 123112block_missing_prev.solve()114# Check that non-connecting block causes disconnect115assert_equal(len(self.nodes[0].getpeerinfo()), 2)116with self.nodes[0].assert_debug_log(expected_msgs=["AcceptBlock FAILED (prev-blk-not-found)"]):117attacker.send_without_ping(msg_block(block_missing_prev))118attacker.wait_for_disconnect(timeout=5)121if __name__ == '__main__':122MutatedBlocksTest(__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.