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) 2020-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"""Parse message capture binary files. To be used in conjunction with -capturemessages."""7import argparse8import os9import shutil10import sys11from io import BytesIO12import json13from pathlib import Path14from typing import Any, Optional16sys.path.append(os.path.join(os.path.dirname(__file__), '../../test/functional'))18from test_framework.messages import ser_uint256 # noqa: E40219from test_framework.p2p import MESSAGEMAP # noqa: E40221TIME_SIZE = 822LENGTH_SIZE = 423MSGTYPE_SIZE = 1225# The test framework classes stores hashes as large ints in many cases.26# These are variables of type uint256 in core.27# There isn't a way to distinguish between a large int and a large int that is actually a blob of bytes.28# As such, they are itemized here.29# Any variables with these names that are of type int are actually uint256 variables.30# (These can be easily found by looking for calls to deser_uint256, deser_uint256_vector, and uint256_from_str in messages.py)31HASH_INTS = [32"blockhash",33"block_hash",34"hash",35"hashMerkleRoot",36"hashPrevBlock",37"hashstop",38"prev_header",39"sha256",40"stop_hash",41]43HASH_INT_VECTORS = [44"hashes",45"headers",46"vHave",47"vHash",48]51class ProgressBar:52def __init__(self, total: float):53self.total = total54self.running = 056def set_progress(self, progress: float):57cols = shutil.get_terminal_size()[0]58if cols <= 12:59return60max_blocks = cols - 961num_blocks = int(max_blocks * progress)62print('\r[ {}{} ] {:3.0f}%'63.format('#' * num_blocks,64' ' * (max_blocks - num_blocks),65progress * 100),66end ='')68def update(self, more: float):69self.running += more70self.set_progress(self.running / self.total)73def to_jsonable(obj: Any) -> Any:74if hasattr(obj, "__dict__"):75return obj.__dict__76elif hasattr(obj, "__slots__"):77ret = {} # type: Any78for slot in obj.__slots__:79val = getattr(obj, slot, None)80if slot in HASH_INTS and isinstance(val, int):81ret[slot] = ser_uint256(val).hex()82elif slot in HASH_INT_VECTORS and all(isinstance(a, int) for a in val):83ret[slot] = [ser_uint256(a).hex() for a in val]84else:85ret[slot] = to_jsonable(val)86return ret87elif isinstance(obj, list):88return [to_jsonable(a) for a in obj]89elif isinstance(obj, bytes):90return obj.hex()91else:92return obj95def process_file(path: str, messages: list[Any], recv: bool, progress_bar: Optional[ProgressBar]) -> None:96with open(path, 'rb') as f_in:97if progress_bar:98bytes_read = 0100while True:101if progress_bar:102# Update progress bar103diff = f_in.tell() - bytes_read - 1104progress_bar.update(diff)105bytes_read = f_in.tell() - 1107# Read the Header108tmp_header_raw = f_in.read(TIME_SIZE + LENGTH_SIZE + MSGTYPE_SIZE)109if not tmp_header_raw:110break111tmp_header = BytesIO(tmp_header_raw)112time = int.from_bytes(tmp_header.read(TIME_SIZE), "little") # type: int113msgtype = tmp_header.read(MSGTYPE_SIZE).split(b'\x00', 1)[0] # type: bytes114length = int.from_bytes(tmp_header.read(LENGTH_SIZE), "little") # type: int116# Start converting the message to a dictionary117msg_dict = {}118msg_dict["direction"] = "recv" if recv else "sent"119msg_dict["time"] = time120msg_dict["size"] = length # "size" is less readable here, but more readable in the output122msg_ser = BytesIO(f_in.read(length))124# Determine message type125if msgtype not in MESSAGEMAP:126# Unrecognized message type127try:128msgtype_tmp = msgtype.decode()129if not msgtype_tmp.isprintable():130raise UnicodeDecodeError131msg_dict["msgtype"] = msgtype_tmp132except UnicodeDecodeError:133msg_dict["msgtype"] = "UNREADABLE"134msg_dict["body"] = msg_ser.read().hex()135msg_dict["error"] = "Unrecognized message type."136messages.append(msg_dict)137print(f"WARNING - Unrecognized message type {msgtype} in {path}", file=sys.stderr)138continue140# Deserialize the message141msg = MESSAGEMAP[msgtype]()142msg_dict["msgtype"] = msgtype.decode()144try:145msg.deserialize(msg_ser)146except KeyboardInterrupt:147raise148except Exception:149# Unable to deserialize message body150msg_ser.seek(0, os.SEEK_SET)151msg_dict["body"] = msg_ser.read().hex()152msg_dict["error"] = "Unable to deserialize message."153messages.append(msg_dict)154print(f"WARNING - Unable to deserialize message in {path}", file=sys.stderr)155continue157# Convert body of message into a jsonable object158if length:159msg_dict["body"] = to_jsonable(msg)160messages.append(msg_dict)162if progress_bar:163# Update the progress bar to the end of the current file164# in case we exited the loop early165f_in.seek(0, os.SEEK_END) # Go to end of file166diff = f_in.tell() - bytes_read - 1167progress_bar.update(diff)170def main():171parser = argparse.ArgumentParser(172description=__doc__,173epilog="EXAMPLE \n\t{0} -o out.json <data-dir>/message_capture/**/*.dat".format(sys.argv[0]),174formatter_class=argparse.RawTextHelpFormatter)175parser.add_argument(176"capturepaths",177nargs='+',178help="binary message capture files to parse.")179parser.add_argument(180"-o", "--output",181help="output file. If unset print to stdout")182parser.add_argument(183"-n", "--no-progress-bar",184action='store_true',185help="disable the progress bar. Automatically set if the output is not a terminal")186args = parser.parse_args()187capturepaths = [Path.cwd() / Path(capturepath) for capturepath in args.capturepaths]188output = Path.cwd() / Path(args.output) if args.output else False189use_progress_bar = (not args.no_progress_bar) and sys.stdout.isatty()191messages = [] # type: list[Any]192if use_progress_bar:193total_size = sum(capture.stat().st_size for capture in capturepaths)194progress_bar = ProgressBar(total_size)195else:196progress_bar = None198for capture in capturepaths:199process_file(str(capture), messages, "recv" in capture.stem, progress_bar)201messages.sort(key=lambda msg: msg['time'])203if use_progress_bar:204progress_bar.set_progress(1)206jsonrep = json.dumps(messages)207if output:208with open(str(output), 'w+') as f_out:209f_out.write(jsonrep)210else:211print(jsonrep)213if __name__ == "__main__":214main()
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.