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"""Test BIP434 feature negotiation."""8from test_framework.messages import (9msg_version,10ser_compact_size,11)12from test_framework.p2p import (13P2PInterface,14P2P_SERVICES,15P2P_SUBVERSION,16P2P_VERSION_RELAY,17)18from test_framework.test_framework import BitcoinTestFramework19from test_framework.util import assert_equal21# Pre-BIP434 protocol version22PRE_FEATURE_VERSION = 7001623# Protocol version which enables BIP434 FEATURE negotiation24FEATURE_VERSION = 7001725# BIP434 wire-format limits26MIN_FEATUREID_LENGTH = 427MAX_FEATUREID_LENGTH = 8028MAX_FEATUREDATA_LENGTH = 51231class RawFeature:32"""A FEATURE message with a hand-crafted payload."""33msgtype = b"feature"35def __init__(self, payload):36self.payload = payload38def serialize(self):39return self.payload41def __repr__(self):42return f"RawFeature(payload_len={len(self.payload)})"45def feature_wire(feature_id, feature_data, *, trailing=b""):46"""Build a FEATURE payload plus optional trailing bytes."""47if isinstance(feature_id, str):48feature_id = feature_id.encode()49return (ser_compact_size(len(feature_id)) + feature_id50+ ser_compact_size(len(feature_data)) + feature_data51+ trailing)54def _version_msg(nversion):55v = msg_version()56v.nVersion = nversion57v.strSubVer = P2P_SUBVERSION58v.nServices = P2P_SERVICES59v.relay = P2P_VERSION_RELAY60return v63class FeaturePeer(P2PInterface):64"""P2PInterface that counts FEATURE messages received."""66def __init__(self):67super().__init__()68self.got_feature_count = 069self.last_feature = None71def on_feature(self, message):72self.got_feature_count += 173self.last_feature = message76class FeaturePeerNoVerack(FeaturePeer):77"""Peer that records but does not auto-reply to the node's VERSION, so the78node stays in the post-VERSION / pre-VERACK window where FEATURE messages79are valid to send."""81def on_version(self, message):82self.nServices = message.nServices83self.relay = message.relay86class P2PBIP434FeatureTest(BitcoinTestFramework):87def set_test_params(self):88self.num_nodes = 189# peertimeout=999 prevents the node from kicking the peer for being idle90self.extra_args = [["-debug=net", "-peertimeout=999"]]92def run_test(self):93self.test_advertised_version()94self.test_no_feature_to_pre_70017_peer()95self.test_features_announced_to_modern_peer()96self.test_feature_after_verack_disconnects()97self.test_feature_before_version_ignored()98self.test_feature_id_length_boundaries()99self.test_feature_data_length_boundaries()100self.test_trailing_bytes_disconnect()101self.test_truncated_feature_id_disconnect()102self.test_truncated_feature_data_disconnect()103self.test_non_ascii_feature_id_accepted()104self.test_many_features_in_handshake()105self.test_recv_feature_from_pre_70017_peer()107def _silent_peer(self, *, nversion=FEATURE_VERSION):108peer = self.nodes[0].add_p2p_connection(109FeaturePeerNoVerack(),110send_version=False, wait_for_verack=False,111)112peer.send_without_ping(_version_msg(nversion))113peer.wait_for_verack()114return peer116def _expect_accept(self, payload, *, log_substring="unknown feature advertised",117nversion=FEATURE_VERSION):118peer = self._silent_peer(nversion=nversion)119with self.nodes[0].assert_debug_log([log_substring], timeout=2):120peer.send_without_ping(RawFeature(payload))121assert peer.is_connected, "peer disconnected after a well-formed FEATURE"122self.nodes[0].disconnect_p2ps()124def _expect_disconnect(self, payload, *, log_substring="invalid feature payload",125nversion=FEATURE_VERSION):126peer = self._silent_peer(nversion=nversion)127with self.nodes[0].assert_debug_log([log_substring], timeout=2):128peer.send_without_ping(RawFeature(payload))129peer.wait_for_disconnect()131def test_advertised_version(self):132self.log.info("Test that node advertises FEATURE to peer with protocol version 70017")133peer = self.nodes[0].add_p2p_connection(FeaturePeer())134assert_equal(peer.last_message["version"].nVersion, FEATURE_VERSION)135self.nodes[0].disconnect_p2ps()137def test_no_feature_to_pre_70017_peer(self):138self.log.info("Test that node doesn't send FEATURE to a peer with protocol version <70017")139peer = self.nodes[0].add_p2p_connection(140FeaturePeer(), send_version=False, wait_for_verack=False,141)142peer.send_without_ping(_version_msg(PRE_FEATURE_VERSION))143peer.wait_for_verack()144# The node's full handshake has now been delivered to us; if any145# FEATURE would have been sent it would be in last_message by now.146assert_equal(peer.got_feature_count, 0)147self.nodes[0].disconnect_p2ps()149def test_features_announced_to_modern_peer(self):150self.log.info("Test that node announces correct number of features to a 70017 peer")151peer = self.nodes[0].add_p2p_connection(FeaturePeer())152assert_equal(peer.got_feature_count, 0)153self.nodes[0].disconnect_p2ps()155def test_feature_after_verack_disconnects(self):156self.log.info("Test that FEATURE after VERACK triggers disconnect")157peer = self.nodes[0].add_p2p_connection(FeaturePeer())158peer.sync_with_ping() # ensure node has set fSuccessfullyConnected159with self.nodes[0].assert_debug_log(["feature received after verack"], timeout=2):160peer.send_without_ping(RawFeature(feature_wire(b"abcd", b"")))161peer.wait_for_disconnect()163def test_feature_before_version_ignored(self):164self.log.info("Test that FEATURE before any VERSION is silently ignored")165peer = self.nodes[0].add_p2p_connection(166FeaturePeer(), send_version=False, wait_for_verack=False,167)168with self.nodes[0].assert_debug_log(169["non-version message before version handshake"], timeout=2,170):171peer.send_without_ping(RawFeature(feature_wire(b"abcd", b"")))172assert peer.is_connected173self.nodes[0].disconnect_p2ps()175def test_feature_id_length_boundaries(self):176self.log.info("Test feature_id length boundaries")177for length, accept in [(0, False),178(3, False),179(MIN_FEATUREID_LENGTH, True),180(MAX_FEATUREID_LENGTH, True),181(MAX_FEATUREID_LENGTH + 1, False)]:182payload = feature_wire(b"a" * length, b"")183if accept:184self._expect_accept(payload)185else:186self._expect_disconnect(payload)188def test_feature_data_length_boundaries(self):189self.log.info("Test feature_data length boundaries")190for length, accept in [(0, True),191(MAX_FEATUREDATA_LENGTH, True),192(MAX_FEATUREDATA_LENGTH + 1, False)]:193payload = feature_wire(b"abcd", b"\x00" * length)194if accept:195self._expect_accept(payload)196else:197self._expect_disconnect(payload)199def test_trailing_bytes_disconnect(self):200self.log.info("Test that trailing bytes after data triggers disconnect")201self._expect_disconnect(202feature_wire(b"abcd", b"", trailing=b"\x00"),203)205def test_truncated_feature_id_disconnect(self):206self.log.info("Test that truncated feature_id triggers disconnect")207payload = ser_compact_size(10) + b"abcde"208self._expect_disconnect(payload)210def test_truncated_feature_data_disconnect(self):211self.log.info("Test that truncated feature_data triggers disconnect")212payload = (ser_compact_size(MIN_FEATUREID_LENGTH) + b"abcd"213+ ser_compact_size(10) + b"xx")214self._expect_disconnect(payload)216def test_non_ascii_feature_id_accepted(self):217self.log.info("Test that feature_id with non-ASCII bytes is still accepted")218# BIP says SHOULD, not MUST, on this219self._expect_accept(feature_wire(b"\x00\xff\x01\x7f", b""))221def test_many_features_in_handshake(self):222self.log.info("Test multiple FEATURE advertisements")223peer = self._silent_peer()224with self.nodes[0].assert_debug_log(["unknown feature advertised"], timeout=2):225for i in range(16):226peer.send_without_ping(227RawFeature(feature_wire(f"feat{i:04d}".encode(), b""))228)229assert peer.is_connected230self.nodes[0].disconnect_p2ps()232def test_recv_feature_from_pre_70017_peer(self):233self.log.info("Test that FEATURE from <70017 peer triggers disconnect")234self._expect_disconnect(235feature_wire(b"abcd", b""),236log_substring="feature received with incompatible version",237nversion=PRE_FEATURE_VERSION,238)241if __name__ == "__main__":242P2PBIP434FeatureTest(__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.