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) 2019-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"""Tests NODE_COMPACT_FILTERS (BIP 157/158).7Tests that a node configured with -blockfilterindex and -peerblockfilters signals8NODE_COMPACT_FILTERS and can serve cfilters, cfheaders and cfcheckpts.9"""11from test_framework.messages import (12FILTER_TYPE_BASIC,13NODE_COMPACT_FILTERS,14hash256,15msg_getcfcheckpt,16msg_getcfheaders,17msg_getcfilters,18ser_uint256,19uint256_from_str,20)21from test_framework.p2p import P2PInterface22from test_framework.test_framework import BitcoinTestFramework23from test_framework.util import (24assert_not_equal,25assert_equal,26)28class FiltersClient(P2PInterface):29def __init__(self):30super().__init__()31# Store the cfilters received.32self.cfilters = []34def pop_cfilters(self):35cfilters = self.cfilters36self.cfilters = []37return cfilters39def on_cfilter(self, message):40"""Store cfilters received in a list."""41self.cfilters.append(message)44class CompactFiltersTest(BitcoinTestFramework):45def set_test_params(self):46self.setup_clean_chain = True47self.rpc_timeout = 48048self.num_nodes = 249self.extra_args = [50["-blockfilterindex", "-peerblockfilters"],51["-blockfilterindex"],52]54def run_test(self):55# Node 0 supports COMPACT_FILTERS, node 1 does not.56peer_0 = self.nodes[0].add_p2p_connection(FiltersClient())57peer_1 = self.nodes[1].add_p2p_connection(FiltersClient())59# Nodes 0 & 1 share the same first 999 blocks in the chain.60self.generate(self.nodes[0], 999)62# Stale blocks by disconnecting nodes 0 & 1, mining, then reconnecting63self.disconnect_nodes(0, 1)65stale_block_hash = self.generate(self.nodes[0], 1, sync_fun=self.no_op)[0]66self.nodes[0].syncwithvalidationinterfacequeue()67assert_equal(self.nodes[0].getblockcount(), 1000)69self.generate(self.nodes[1], 1001, sync_fun=self.no_op)70assert_equal(self.nodes[1].getblockcount(), 2000)72# Check that nodes have signalled NODE_COMPACT_FILTERS correctly.73assert_not_equal(peer_0.nServices & NODE_COMPACT_FILTERS, 0)74assert_equal(peer_1.nServices & NODE_COMPACT_FILTERS, 0)76# Check that the localservices is as expected.77assert_not_equal(int(self.nodes[0].getnetworkinfo()['localservices'], 16) & NODE_COMPACT_FILTERS, 0)78assert_equal(int(self.nodes[1].getnetworkinfo()['localservices'], 16) & NODE_COMPACT_FILTERS, 0)80self.log.info("get cfcheckpt on chain to be re-orged out.")81request = msg_getcfcheckpt(82filter_type=FILTER_TYPE_BASIC,83stop_hash=int(stale_block_hash, 16),84)85peer_0.send_and_ping(message=request)86response = peer_0.last_message['cfcheckpt']87assert_equal(response.filter_type, request.filter_type)88assert_equal(response.stop_hash, request.stop_hash)89assert_equal(len(response.headers), 1)91self.log.info("Reorg node 0 to a new chain.")92self.connect_nodes(0, 1)93self.sync_blocks(timeout=600)94self.nodes[0].syncwithvalidationinterfacequeue()96main_block_hash = self.nodes[0].getblockhash(1000)97assert_not_equal(main_block_hash, stale_block_hash, error_message="node 0 chain did not reorganize")99self.log.info("Check that peers can fetch cfcheckpt on active chain.")100tip_hash = self.nodes[0].getbestblockhash()101request = msg_getcfcheckpt(102filter_type=FILTER_TYPE_BASIC,103stop_hash=int(tip_hash, 16),104)105peer_0.send_and_ping(request)106response = peer_0.last_message['cfcheckpt']107assert_equal(response.filter_type, request.filter_type)108assert_equal(response.stop_hash, request.stop_hash)110main_cfcheckpt = self.nodes[0].getblockfilter(main_block_hash, 'basic')['header']111tip_cfcheckpt = self.nodes[0].getblockfilter(tip_hash, 'basic')['header']112assert_equal(113response.headers,114[int(header, 16) for header in (main_cfcheckpt, tip_cfcheckpt)],115)117self.log.info("Check that peers can fetch cfcheckpt on stale chain.")118request = msg_getcfcheckpt(119filter_type=FILTER_TYPE_BASIC,120stop_hash=int(stale_block_hash, 16),121)122peer_0.send_and_ping(request)123response = peer_0.last_message['cfcheckpt']125stale_cfcheckpt = self.nodes[0].getblockfilter(stale_block_hash, 'basic')['header']126assert_equal(127response.headers,128[int(header, 16) for header in (stale_cfcheckpt, )],129)131self.log.info("Check that peers can fetch cfheaders on active chain.")132request = msg_getcfheaders(133filter_type=FILTER_TYPE_BASIC,134start_height=1,135stop_hash=int(main_block_hash, 16),136)137peer_0.send_and_ping(request)138response = peer_0.last_message['cfheaders']139main_cfhashes = response.hashes140assert_equal(len(main_cfhashes), 1000)141assert_equal(142compute_last_header(response.prev_header, response.hashes),143int(main_cfcheckpt, 16),144)146self.log.info("Check that peers can fetch cfheaders on stale chain.")147request = msg_getcfheaders(148filter_type=FILTER_TYPE_BASIC,149start_height=1,150stop_hash=int(stale_block_hash, 16),151)152peer_0.send_and_ping(request)153response = peer_0.last_message['cfheaders']154stale_cfhashes = response.hashes155assert_equal(len(stale_cfhashes), 1000)156assert_equal(157compute_last_header(response.prev_header, response.hashes),158int(stale_cfcheckpt, 16),159)161self.log.info("Check that peers can fetch cfilters.")162stop_hash = self.nodes[0].getblockhash(10)163request = msg_getcfilters(164filter_type=FILTER_TYPE_BASIC,165start_height=1,166stop_hash=int(stop_hash, 16),167)168peer_0.send_and_ping(request)169response = peer_0.pop_cfilters()170assert_equal(len(response), 10)172self.log.info("Check that cfilter responses are correct.")173for cfilter, cfhash, height in zip(response, main_cfhashes, range(1, 11)):174block_hash = self.nodes[0].getblockhash(height)175assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC)176assert_equal(cfilter.block_hash, int(block_hash, 16))177computed_cfhash = uint256_from_str(hash256(cfilter.filter_data))178assert_equal(computed_cfhash, cfhash)180self.log.info("Check that peers can fetch cfilters for stale blocks.")181request = msg_getcfilters(182filter_type=FILTER_TYPE_BASIC,183start_height=1000,184stop_hash=int(stale_block_hash, 16),185)186peer_0.send_and_ping(request)187response = peer_0.pop_cfilters()188assert_equal(len(response), 1)190cfilter = response[0]191assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC)192assert_equal(cfilter.block_hash, int(stale_block_hash, 16))193computed_cfhash = uint256_from_str(hash256(cfilter.filter_data))194assert_equal(computed_cfhash, stale_cfhashes[999])196self.log.info("Requests to node 1 without NODE_COMPACT_FILTERS results in disconnection.")197requests = [198msg_getcfcheckpt(199filter_type=FILTER_TYPE_BASIC,200stop_hash=int(main_block_hash, 16),201),202msg_getcfheaders(203filter_type=FILTER_TYPE_BASIC,204start_height=1000,205stop_hash=int(main_block_hash, 16),206),207msg_getcfilters(208filter_type=FILTER_TYPE_BASIC,209start_height=1000,210stop_hash=int(main_block_hash, 16),211),212]213for request in requests:214peer_1 = self.nodes[1].add_p2p_connection(P2PInterface())215with self.nodes[1].assert_debug_log(expected_msgs=["requested unsupported block filter type"]):216peer_1.send_without_ping(request)217peer_1.wait_for_disconnect()219self.log.info("Check that invalid requests result in disconnection.")220requests = [221# Requesting too many filters results in disconnection.222(223msg_getcfilters(224filter_type=FILTER_TYPE_BASIC,225start_height=0,226stop_hash=int(main_block_hash, 16),227), "requested too many cfilters/cfheaders"228),229# Requesting too many filter headers results in disconnection.230(231msg_getcfheaders(232filter_type=FILTER_TYPE_BASIC,233start_height=0,234stop_hash=int(tip_hash, 16),235), "requested too many cfilters/cfheaders"236),237# Requesting unknown filter type results in disconnection.238(239msg_getcfcheckpt(240filter_type=255,241stop_hash=int(main_block_hash, 16),242), "requested unsupported block filter type"243),244# Requesting unknown hash results in disconnection.245(246msg_getcfcheckpt(247filter_type=FILTER_TYPE_BASIC,248stop_hash=123456789,249), "requested invalid block hash"250),
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.