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""" Tests the utxocache:* tracepoint API interface.7See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-utxocache8"""10import ctypes11# Test will be skipped if we don't have bcc installed12try:13from bcc import BPF, USDT # type: ignore[import]14except ImportError:15pass16from test_framework.messages import COIN17from test_framework.test_framework import BitcoinTestFramework18from test_framework.util import (19assert_equal,20bpf_cflags,21)22from test_framework.wallet import MiniWallet24utxocache_changes_program = """25#include <uapi/linux/ptrace.h>27typedef signed long long i64;29struct utxocache_change30{31char txid[32];32u32 index;33u32 height;34i64 value;35bool is_coinbase;36};38BPF_PERF_OUTPUT(utxocache_add);39int trace_utxocache_add(struct pt_regs *ctx) {40struct utxocache_change add = {};41void *ptxid = NULL;42bpf_usdt_readarg(1, ctx, &ptxid);43bpf_probe_read_user(&add.txid, sizeof(add.txid), ptxid);44bpf_usdt_readarg(2, ctx, &add.index);45bpf_usdt_readarg(3, ctx, &add.height);46bpf_usdt_readarg(4, ctx, &add.value);47bpf_usdt_readarg(5, ctx, &add.is_coinbase);48utxocache_add.perf_submit(ctx, &add, sizeof(add));49return 0;50}52BPF_PERF_OUTPUT(utxocache_spent);53int trace_utxocache_spent(struct pt_regs *ctx) {54struct utxocache_change spent = {};55void *ptxid = NULL;56bpf_usdt_readarg(1, ctx, &ptxid);57bpf_probe_read_user(&spent.txid, sizeof(spent.txid), ptxid);58bpf_usdt_readarg(2, ctx, &spent.index);59bpf_usdt_readarg(3, ctx, &spent.height);60bpf_usdt_readarg(4, ctx, &spent.value);61bpf_usdt_readarg(5, ctx, &spent.is_coinbase);62utxocache_spent.perf_submit(ctx, &spent, sizeof(spent));63return 0;64}66BPF_PERF_OUTPUT(utxocache_uncache);67int trace_utxocache_uncache(struct pt_regs *ctx) {68struct utxocache_change uncache = {};69void *ptxid = NULL;70bpf_usdt_readarg(1, ctx, &ptxid);71bpf_probe_read_user(&uncache.txid, sizeof(uncache.txid), ptxid);72bpf_usdt_readarg(2, ctx, &uncache.index);73bpf_usdt_readarg(3, ctx, &uncache.height);74bpf_usdt_readarg(4, ctx, &uncache.value);75bpf_usdt_readarg(5, ctx, &uncache.is_coinbase);76utxocache_uncache.perf_submit(ctx, &uncache, sizeof(uncache));77return 0;78}79"""81utxocache_flushes_program = """82#include <uapi/linux/ptrace.h>84typedef signed long long i64;86struct utxocache_flush87{88i64 duration;89u32 mode;90u64 size;91u64 memory;92bool for_prune;93};95BPF_PERF_OUTPUT(utxocache_flush);96int trace_utxocache_flush(struct pt_regs *ctx) {97struct utxocache_flush flush = {};98bpf_usdt_readarg(1, ctx, &flush.duration);99bpf_usdt_readarg(2, ctx, &flush.mode);100bpf_usdt_readarg(3, ctx, &flush.size);101bpf_usdt_readarg(4, ctx, &flush.memory);102bpf_usdt_readarg(5, ctx, &flush.for_prune);103utxocache_flush.perf_submit(ctx, &flush, sizeof(flush));104return 0;105}106"""108FLUSHMODE_NAME = {1090: "NONE",1101: "IF_NEEDED",1112: "PERIODIC",1123: "FORCE_FLUSH",1134: "FORCE_SYNC",114}117class UTXOCacheChange(ctypes.Structure):118_fields_ = [119("txid", ctypes.c_ubyte * 32),120("index", ctypes.c_uint32),121("height", ctypes.c_uint32),122("value", ctypes.c_uint64),123("is_coinbase", ctypes.c_bool),124]126def __repr__(self):127return f"UTXOCacheChange(outpoint={bytes(self.txid[::-1]).hex()}:{self.index}, height={self.height}, value={self.value}sat, is_coinbase={self.is_coinbase})"130class UTXOCacheFlush(ctypes.Structure):131_fields_ = [132("duration", ctypes.c_int64),133("mode", ctypes.c_uint32),134("size", ctypes.c_uint64),135("memory", ctypes.c_uint64),136("for_prune", ctypes.c_bool),137]139def __repr__(self):140return f"UTXOCacheFlush(duration={self.duration}, mode={FLUSHMODE_NAME[self.mode]}, size={self.size}, memory={self.memory}, for_prune={self.for_prune})"143class UTXOCacheTracepointTest(BitcoinTestFramework):144def set_test_params(self):145self.setup_clean_chain = False146self.num_nodes = 1147self.extra_args = [["-txindex"]]149def skip_test_if_missing_module(self):150self.skip_if_platform_not_linux()151self.skip_if_no_bitcoind_tracepoints()152self.skip_if_no_python_bcc()153self.skip_if_no_bpf_permissions()154self.skip_if_running_under_valgrind()156def run_test(self):157self.wallet = MiniWallet(self.nodes[0])159self.test_uncache()160self.test_add_spent()161self.test_flush()163def test_uncache(self):164""" Tests the utxocache:uncache tracepoint API.165https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheuncache166"""167# To trigger an UTXO uncache from the cache, we create an invalid transaction168# spending a not-cached, but existing UTXO. During transaction validation, this169# the UTXO is added to the utxo cache, but as the transaction is invalid, it's170# uncached again.171self.log.info("testing the utxocache:uncache tracepoint API")173# Retrieve the txid for the UTXO created in the first block. This UTXO is not174# in our UTXO cache.175EARLY_BLOCK_HEIGHT = 1176block_1_hash = self.nodes[0].getblockhash(EARLY_BLOCK_HEIGHT)177block_1 = self.nodes[0].getblock(block_1_hash)178block_1_coinbase_txid = block_1["tx"][0]180# Create a transaction and invalidate it by changing the txid of the previous181# output to the coinbase txid of the block at height 1.182invalid_tx = self.wallet.create_self_transfer()["tx"]183invalid_tx.vin[0].prevout.hash = int(block_1_coinbase_txid, 16)185self.log.info("hooking into the utxocache:uncache tracepoint")186ctx = USDT(pid=self.nodes[0].process.pid)187ctx.enable_probe(probe="utxocache:uncache",188fn_name="trace_utxocache_uncache")189bpf = BPF(text=utxocache_changes_program, usdt_contexts=[ctx], debug=0, cflags=bpf_cflags())191# The handle_* function is a ctypes callback function called from C. When192# we assert in the handle_* function, the AssertError doesn't propagate193# back to Python. The exception is ignored. We manually count and assert194# that the handle_* functions succeeded.195EXPECTED_HANDLE_UNCACHE_SUCCESS = 1196handle_uncache_succeeds = 0198def handle_utxocache_uncache(_, data, __):199nonlocal handle_uncache_succeeds200event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents201self.log.info(f"handle_utxocache_uncache(): {event}")202try:203assert_equal(block_1_coinbase_txid, bytes(event.txid[::-1]).hex())204assert_equal(0, event.index) # prevout index205assert_equal(EARLY_BLOCK_HEIGHT, event.height)206assert_equal(50 * COIN, event.value)207assert_equal(True, event.is_coinbase)208except AssertionError:209self.log.exception("Assertion failed")210else:211handle_uncache_succeeds += 1213bpf["utxocache_uncache"].open_perf_buffer(handle_utxocache_uncache)215self.log.info(216"testmempoolaccept the invalid transaction to trigger an UTXO-cache uncache")217result = self.nodes[0].testmempoolaccept(218[invalid_tx.serialize().hex()])[0]219assert_equal(result["allowed"], False)221bpf.perf_buffer_poll(timeout=100)222bpf.cleanup()224self.log.info(225f"check that we successfully traced {EXPECTED_HANDLE_UNCACHE_SUCCESS} uncaches")226assert_equal(EXPECTED_HANDLE_UNCACHE_SUCCESS, handle_uncache_succeeds)228def test_add_spent(self):229""" Tests the utxocache:add utxocache:spent tracepoint API230See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheadd231and https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocachespent232"""234self.log.info(235"test the utxocache:add and utxocache:spent tracepoint API")237self.log.info("create an unconfirmed transaction")238self.wallet.send_self_transfer(from_node=self.nodes[0])240# We mine a block to trace changes (add/spent) to the active in-memory cache241# of the UTXO set (see CoinsTip() of CCoinsViewCache). However, in some cases242# temporary clones of the active cache are made. For example, during mining with243# the generate RPC call, the block is first tested in TestBlockValidity(). There,244# a clone of the active cache is modified during a test ConnectBlock() call.245# These are implementation details we don't want to test here. Thus, after246# mining, we invalidate the block, start the tracing, and then trace the cache247# changes to the active utxo cache.248self.log.info("mine and invalidate a block that is later reconsidered")249block_hash = self.generate(self.wallet, 1)[0]250self.nodes[0].invalidateblock(block_hash)
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.