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 the ZMQ notification interface."""6import os7import struct8import tempfile9from io import BytesIO11from test_framework.address import (12ADDRESS_BCRT1_P2WSH_OP_TRUE,13ADDRESS_BCRT1_UNSPENDABLE,14)15from test_framework.blocktools import (16add_witness_commitment,17create_block,18)19from test_framework.test_framework import BitcoinTestFramework20from test_framework.messages import (21CBlock,22hash256,23tx_from_hex,24)25from test_framework.util import (26assert_equal,27assert_raises_rpc_error,28ensure_for,29p2p_port,30)31from test_framework.wallet import (32MiniWallet,33)34from test_framework.netutil import test_ipv6_local, test_unix_socket37# Test may be skipped and not have zmq installed38try:39import zmq40except ImportError:41pass43def hash256_reversed(byte_str):44return hash256(byte_str)[::-1]46class ZMQSubscriber:47def __init__(self, socket, topic):48self.sequence = None # no sequence number received yet49self.socket = socket50self.topic = topic52self.socket.setsockopt(zmq.SUBSCRIBE, self.topic)54# Receive message from publisher and verify that topic and sequence match55def _receive_from_publisher_and_check(self):56topic, body, seq = self.socket.recv_multipart()57# Topic should match the subscriber topic.58assert_equal(topic, self.topic)59# Sequence should be incremental.60received_seq = struct.unpack('<I', seq)[-1]61if self.sequence is None:62self.sequence = received_seq63else:64assert_equal(received_seq, self.sequence)65self.sequence += 166return body68def receive(self):69return self._receive_from_publisher_and_check()71def receive_sequence(self):72body = self._receive_from_publisher_and_check()73hash = body[:32].hex()74label = chr(body[32])75mempool_sequence = None if len(body) != 32+1+8 else struct.unpack("<Q", body[32+1:])[0]76if mempool_sequence is not None:77assert label in ("A", "R")78else:79assert label in ("D", "C")80return (hash, label, mempool_sequence)83class ZMQTestSetupBlock:84"""Helper class for setting up a ZMQ test via the "sync up" procedure.85Generates a block on the specified node on instantiation and provides a86method to check whether a ZMQ notification matches, i.e. the event was87caused by this generated block. Assumes that a notification either contains88the generated block's hash, it's (coinbase) transaction id, the raw block or89raw transaction data.90"""91def __init__(self, test_framework, node):92self.block_hash = test_framework.generate(node, 1, sync_fun=test_framework.no_op)[0]93coinbase = node.getblock(self.block_hash, 2)['tx'][0]94self.tx_hash = coinbase['txid']95self.raw_tx = coinbase['hex']96self.raw_block = node.getblock(self.block_hash, 0)98def caused_notification(self, notification):99return (100self.block_hash in notification101or self.tx_hash in notification102or self.raw_block in notification103or self.raw_tx in notification104)107class ZMQTest (BitcoinTestFramework):108def set_test_params(self):109self.num_nodes = 2110# whitelist peers to speed up tx relay / mempool sync111self.noban_tx_relay = True112self.zmq_port_base = p2p_port(self.num_nodes + 1)114def skip_test_if_missing_module(self):115self.skip_if_no_py3_zmq()116self.skip_if_no_bitcoind_zmq()118def run_test(self):119self.wallet = MiniWallet(self.nodes[0])120self.ctx = zmq.Context()121try:122self.test_basic()123if test_unix_socket():124self.test_basic(unix=True)125else:126self.log.info("Skipping ipc test, because UNIX sockets are not supported.")127self.test_sequence()128self.test_mempool_sync()129self.test_reorg()130self.test_multiple_interfaces()131self.test_ipv6()132finally:133# Destroy the ZMQ context.134self.log.debug("Destroying ZMQ context")135self.ctx.destroy(linger=None)137# Restart node with the specified zmq notifications enabled, subscribe to138# all of them and return the corresponding ZMQSubscriber objects.139def setup_zmq_test(self, services, *, recv_timeout=60, sync_blocks=True, ipv6=False):140subscribers = []141for topic, address in services:142socket = self.ctx.socket(zmq.SUB)143if ipv6:144socket.setsockopt(zmq.IPV6, 1)145subscribers.append(ZMQSubscriber(socket, topic.encode()))147self.restart_node(0, [f"-zmqpub{topic}={address.replace('ipc://', 'unix:')}" for topic, address in services])149for i, sub in enumerate(subscribers):150sub.socket.connect(services[i][1])152# Ensure that all zmq publisher notification interfaces are ready by153# running the following "sync up" procedure:154# 1. Generate a block on the node155# 2. Try to receive the corresponding notification on all subscribers156# 3. If all subscribers get the message within the timeout (1 second),157# we are done, otherwise repeat starting from step 1158for sub in subscribers:159sub.socket.set(zmq.RCVTIMEO, 1000)160while True:161test_block = ZMQTestSetupBlock(self, self.nodes[0])162recv_failed = False163for sub in subscribers:164try:165while not test_block.caused_notification(sub.receive().hex()):166self.log.debug("Ignoring sync-up notification for previously generated block.")167except zmq.error.Again:168self.log.debug("Didn't receive sync-up notification, trying again.")169recv_failed = True170if not recv_failed:171self.log.debug("ZMQ sync-up completed, all subscribers are ready.")172break174# set subscriber's desired timeout for the test175for sub in subscribers:176sub.socket.set(zmq.RCVTIMEO, int(recv_timeout * self.options.timeout_factor * 1000))178self.connect_nodes(0, 1)179if sync_blocks:180self.sync_blocks()182return subscribers184def test_basic(self, unix = False):185self.log.info(f"Running basic test with {'ipc' if unix else 'tcp'} protocol")187# Invalid zmq arguments don't take down the node, see #17185.188self.restart_node(0, ["-zmqpubrawtx=foo", "-zmqpubhashtx=bar"])190address = f"tcp://127.0.0.1:{self.zmq_port_base}"192if unix:193# Use the shortest temp path possible since paths may have as little as 92-char limit194socket_path = tempfile.NamedTemporaryFile().name195address = f"ipc://{socket_path}"197subs = self.setup_zmq_test([(topic, address) for topic in ["hashblock", "hashtx", "rawblock", "rawtx"]])199hashblock = subs[0]200hashtx = subs[1]201rawblock = subs[2]202rawtx = subs[3]204num_blocks = 5205self.log.info(f"Generate {num_blocks} blocks (and {num_blocks} coinbase txes)")206genhashes = self.generatetoaddress(self.nodes[0], num_blocks, ADDRESS_BCRT1_UNSPENDABLE)208for x in range(num_blocks):209# Should receive the coinbase txid.210txid = hashtx.receive()212# Should receive the coinbase raw transaction.213tx = tx_from_hex(rawtx.receive().hex())214assert_equal(tx.txid_hex, txid.hex())216# Should receive the generated raw block.217hex = rawblock.receive()218block = CBlock()219block.deserialize(BytesIO(hex))220assert block.is_valid()221assert_equal(block.vtx[0].txid_hex, tx.txid_hex)222assert_equal(len(block.vtx), 1)223assert_equal(genhashes[x], hash256_reversed(hex[:80]).hex())225# Should receive the generated block hash.226hash = hashblock.receive().hex()227assert_equal(genhashes[x], hash)228# The block should only have the coinbase txid.229assert_equal([txid.hex()], self.nodes[1].getblock(hash)["tx"])232self.log.info("Wait for tx from second node")233payment_tx = self.wallet.send_self_transfer(from_node=self.nodes[1])234payment_txid = payment_tx['txid']235self.sync_all()236# Should receive the broadcasted txid.237txid = hashtx.receive()238assert_equal(payment_txid, txid.hex())240# Should receive the broadcasted raw transaction.241hex = rawtx.receive()242assert_equal(payment_tx['wtxid'], hash256_reversed(hex).hex())244# Mining the block with this tx should result in second notification245# after coinbase tx notification246self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)247hashtx.receive()248txid = hashtx.receive()249assert_equal(payment_txid, txid.hex())
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.