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) 2022-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.6""" Example logging Bitcoin Core mempool events using the mempool:added,7mempool:removed, mempool:replaced, and mempool:rejected tracepoints. """9import curses10import sys11from datetime import datetime, timezone13from bcc import BPF, USDT15# BCC: The C program to be compiled to an eBPF program (by BCC) and loaded into16# a sandboxed Linux kernel VM.17PROGRAM = """18# include <uapi/linux/ptrace.h>20// The longest rejection reason is 114 chars and is generated in case of SCRIPT_ERR_EVAL_FALSE by21// strprintf("block-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))22#define MAX_REJECT_REASON_LENGTH 11423// The longest string returned by RemovalReasonToString() is 'sizelimit'24#define MAX_REMOVAL_REASON_LENGTH 925#define HASH_LENGTH 3227struct added_event28{29u8 hash[HASH_LENGTH];30s32 vsize;31s64 fee;32};34struct removed_event35{36u8 hash[HASH_LENGTH];37char reason[MAX_REMOVAL_REASON_LENGTH];38s32 vsize;39s64 fee;40u64 entry_time;41};43struct rejected_event44{45u8 hash[HASH_LENGTH];46char reason[MAX_REJECT_REASON_LENGTH];47};49struct replaced_event50{51u8 replaced_hash[HASH_LENGTH];52s32 replaced_vsize;53s64 replaced_fee;54u64 replaced_entry_time;55u8 replacement_hash[HASH_LENGTH];56s32 replacement_vsize;57s64 replacement_fee;58};60// BPF perf buffer to push the data to user space.61BPF_PERF_OUTPUT(added_events);62BPF_PERF_OUTPUT(removed_events);63BPF_PERF_OUTPUT(rejected_events);64BPF_PERF_OUTPUT(replaced_events);66int trace_added(struct pt_regs *ctx) {67struct added_event added = {};68void *phash = NULL;69bpf_usdt_readarg(1, ctx, &phash);70bpf_probe_read_user(&added.hash, sizeof(added.hash), phash);71bpf_usdt_readarg(2, ctx, &added.vsize);72bpf_usdt_readarg(3, ctx, &added.fee);74added_events.perf_submit(ctx, &added, sizeof(added));75return 0;76}78int trace_removed(struct pt_regs *ctx) {79struct removed_event removed = {};80void *phash = NULL, *preason = NULL;81bpf_usdt_readarg(1, ctx, &phash);82bpf_probe_read_user(&removed.hash, sizeof(removed.hash), phash);83bpf_usdt_readarg(2, ctx, &preason);84bpf_probe_read_user_str(&removed.reason, sizeof(removed.reason), preason);85bpf_usdt_readarg(3, ctx, &removed.vsize);86bpf_usdt_readarg(4, ctx, &removed.fee);87bpf_usdt_readarg(5, ctx, &removed.entry_time);89removed_events.perf_submit(ctx, &removed, sizeof(removed));90return 0;91}93int trace_rejected(struct pt_regs *ctx) {94struct rejected_event rejected = {};95void *phash = NULL, *preason = NULL;96bpf_usdt_readarg(1, ctx, &phash);97bpf_probe_read_user(&rejected.hash, sizeof(rejected.hash), phash);98bpf_usdt_readarg(2, ctx, &preason);99bpf_probe_read_user_str(&rejected.reason, sizeof(rejected.reason), preason);100rejected_events.perf_submit(ctx, &rejected, sizeof(rejected));101return 0;102}104int trace_replaced(struct pt_regs *ctx) {105struct replaced_event replaced = {};106void *phash_replaced = NULL, *phash_replacement = NULL;107bpf_usdt_readarg(1, ctx, &phash_replaced);108bpf_probe_read_user(&replaced.replaced_hash, sizeof(replaced.replaced_hash), phash_replaced);109bpf_usdt_readarg(2, ctx, &replaced.replaced_vsize);110bpf_usdt_readarg(3, ctx, &replaced.replaced_fee);111bpf_usdt_readarg(4, ctx, &replaced.replaced_entry_time);112bpf_usdt_readarg(5, ctx, &phash_replacement);113bpf_probe_read_user(&replaced.replacement_hash, sizeof(replaced.replacement_hash), phash_replacement);114bpf_usdt_readarg(6, ctx, &replaced.replacement_vsize);115bpf_usdt_readarg(7, ctx, &replaced.replacement_fee);117replaced_events.perf_submit(ctx, &replaced, sizeof(replaced));118return 0;119}120"""123def main(pid):124print(f"Hooking into bitcoind with pid {pid}")125bitcoind_with_usdts = USDT(pid=int(pid))127# attaching the trace functions defined in the BPF program128# to the tracepoints129bitcoind_with_usdts.enable_probe(probe="mempool:added", fn_name="trace_added")130bitcoind_with_usdts.enable_probe(probe="mempool:removed", fn_name="trace_removed")131bitcoind_with_usdts.enable_probe(probe="mempool:replaced", fn_name="trace_replaced")132bitcoind_with_usdts.enable_probe(probe="mempool:rejected", fn_name="trace_rejected")133bpf = BPF(text=PROGRAM, usdt_contexts=[bitcoind_with_usdts])135events = []137def get_timestamp():138return datetime.now(timezone.utc)140def handle_added(_, data, size):141event = bpf["added_events"].event(data)142events.append((get_timestamp(), "added", event))144def handle_removed(_, data, size):145event = bpf["removed_events"].event(data)146events.append((get_timestamp(), "removed", event))148def handle_rejected(_, data, size):149event = bpf["rejected_events"].event(data)150events.append((get_timestamp(), "rejected", event))152def handle_replaced(_, data, size):153event = bpf["replaced_events"].event(data)154events.append((get_timestamp(), "replaced", event))156bpf["added_events"].open_perf_buffer(handle_added)157# By default, open_perf_buffer uses eight pages for a buffer, making for a total158# buffer size of 32k on most machines. In practice, this size is insufficient:159# Each `mempool:removed` event takes up 57 bytes in the buffer (32 bytes for txid,160# 9 bytes for removal reason, and 8 bytes each for vsize and fee). Full blocks161# contain around 2k transactions, requiring a buffer size of around 114kB. To cover162# this amount, 32 4k pages are required.163bpf["removed_events"].open_perf_buffer(handle_removed, page_cnt=32)164bpf["rejected_events"].open_perf_buffer(handle_rejected)165bpf["replaced_events"].open_perf_buffer(handle_replaced)167curses.wrapper(loop, bpf, events)170def loop(screen, bpf, events):171dashboard = Dashboard(screen)172while True:173try:174bpf.perf_buffer_poll(timeout=50)175dashboard.render(events)176except KeyboardInterrupt:177exit()180class Dashboard:181"""Visualization of mempool state using ncurses."""183INFO_WIN_HEIGHT = 2184EVENT_WIN_HEIGHT = 7186def __init__(self, screen):187screen.nodelay(True)188curses.curs_set(False)189self._screen = screen190self._time_started = datetime.now(timezone.utc)191self._timestamps = {"added": [], "removed": [], "rejected": [], "replaced": []}192self._event_history = {"added": 0, "removed": 0, "rejected": 0, "replaced": 0}193self._init_windows()195def _init_windows(self):196"""Initialize all windows."""197self._init_info_win()198self._init_event_count_win()199self._init_event_rate_win()200self._init_event_log_win()202@staticmethod203def create_win(x, y, height, width, title=None):204"""Helper function to create generic windows and decorate them with box and title if requested."""205win = curses.newwin(height, width, x, y)206if title:207win.box()208win.addstr(0, 2, title, curses.A_BOLD)209return win211def _init_info_win(self):212"""Create and populate the info window."""213self._info_win = Dashboard.create_win(214x=0, y=1, height=Dashboard.INFO_WIN_HEIGHT, width=22215)216self._info_win.addstr(0, 0, "Mempool Monitor", curses.A_REVERSE)217self._info_win.addstr(1, 0, "Press CTRL-C to stop.", curses.A_NORMAL)218self._info_win.refresh()220def _init_event_count_win(self):221"""Create and populate the event count window."""222self._event_count_win = Dashboard.create_win(223x=3, y=1, height=Dashboard.EVENT_WIN_HEIGHT, width=37, title="Event count"224)225header = " {:<8} {:>8} {:>7} {:>7} "226self._event_count_win.addstr(2271, 1, header.format("Event", "total", "1 min", "10 min"), curses.A_UNDERLINE228)229self._event_count_win.refresh()231def _init_event_rate_win(self):232"""Create and populate the event rate window."""233self._event_rate_win = Dashboard.create_win(234x=3, y=40, height=Dashboard.EVENT_WIN_HEIGHT, width=42, title="Event rate"235)236header = " {:<8} {:>9} {:>9} {:>9} "237self._event_rate_win.addstr(2381, 1, header.format("Event", "total", "1 min", "10 min"), curses.A_UNDERLINE239)240self._event_rate_win.refresh()242def _init_event_log_win(self):243"""Create windows showing event log. This comprises a dummy boxed window and an244inset window so line breaks don't overwrite box."""245# dummy boxed window246num_rows, num_cols = self._screen.getmaxyx()247space_above = Dashboard.INFO_WIN_HEIGHT + 1 + Dashboard.EVENT_WIN_HEIGHT + 1248box_win_height = num_rows - space_above249box_win_width = num_cols - 2250win_box = Dashboard.create_win(
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.