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.
252self.log.info(253"hook into the utxocache:add and utxocache:spent tracepoints")254ctx = USDT(pid=self.nodes[0].process.pid)255ctx.enable_probe(probe="utxocache:add", fn_name="trace_utxocache_add")256ctx.enable_probe(probe="utxocache:spent",257fn_name="trace_utxocache_spent")258bpf = BPF(text=utxocache_changes_program, usdt_contexts=[ctx], debug=0, cflags=bpf_cflags())260# The handle_* function is a ctypes callback function called from C. When261# we assert in the handle_* function, the AssertError doesn't propagate262# back to Python. The exception is ignored. We manually count and assert263# that the handle_* functions succeeded.264EXPECTED_HANDLE_ADD_SUCCESS = 2265EXPECTED_HANDLE_SPENT_SUCCESS = 1267expected_utxocache_adds = []268expected_utxocache_spents = []270actual_utxocache_adds = []271actual_utxocache_spents = []273def handle_utxocache_add(_, data, __):274event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents275self.log.info(f"handle_utxocache_add(): {event}")276actual_utxocache_adds.append(event)278def handle_utxocache_spent(_, data, __):279event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents280self.log.info(f"handle_utxocache_spent(): {event}")281actual_utxocache_spents.append(event)283bpf["utxocache_add"].open_perf_buffer(handle_utxocache_add)284bpf["utxocache_spent"].open_perf_buffer(handle_utxocache_spent)286# We trigger a block re-connection. This causes changes (add/spent)287# to the UTXO-cache which in turn triggers the tracepoints.288self.log.info("reconsider the previously invalidated block")289self.nodes[0].reconsiderblock(block_hash)291block = self.nodes[0].getblock(block_hash, 2)292for (block_index, tx) in enumerate(block["tx"]):293for vin in tx["vin"]:294if "coinbase" not in vin:295prevout_tx = self.nodes[0].getrawtransaction(296vin["txid"], True)297prevout_tx_block = self.nodes[0].getblockheader(298prevout_tx["blockhash"])299spends_coinbase = "coinbase" in prevout_tx["vin"][0]300expected_utxocache_spents.append({301"txid": vin["txid"],302"index": vin["vout"],303"height": prevout_tx_block["height"],304"value": int(prevout_tx["vout"][vin["vout"]]["value"] * COIN),305"is_coinbase": spends_coinbase,306})307for (i, vout) in enumerate(tx["vout"]):308if vout["scriptPubKey"]["type"] != "nulldata":309expected_utxocache_adds.append({310"txid": tx["txid"],311"index": i,312"height": block["height"],313"value": int(vout["value"] * COIN),314"is_coinbase": block_index == 0,315})317bpf.perf_buffer_poll(timeout=200)319assert_equal(EXPECTED_HANDLE_ADD_SUCCESS, len(expected_utxocache_adds), len(actual_utxocache_adds))320assert_equal(EXPECTED_HANDLE_SPENT_SUCCESS, len(expected_utxocache_spents), len(actual_utxocache_spents))322self.log.info(323f"check that we successfully traced {EXPECTED_HANDLE_ADD_SUCCESS} adds and {EXPECTED_HANDLE_SPENT_SUCCESS} spent")325# Check that all expected tracepoints are received, but not the order they were received in.326# Tracepoint ordering is not strictly guaranteed, so this comparison avoids intermittent failures in the test.327def cache_event_to_key(event):328return (329bytes(event.txid[::-1]).hex(),330event.index,331event.height,332event.value,333event.is_coinbase334)336expected_add_keys = {(e["txid"], e["index"], e["height"], e["value"], e["is_coinbase"])337for e in expected_utxocache_adds}338expected_spent_keys = {(e["txid"], e["index"], e["height"], e["value"], e["is_coinbase"])339for e in expected_utxocache_spents}341actual_add_keys = {cache_event_to_key(e) for e in actual_utxocache_adds}342actual_spent_keys = {cache_event_to_key(e) for e in actual_utxocache_spents}344assert_equal(expected_add_keys, actual_add_keys)345assert_equal(expected_spent_keys, actual_spent_keys)347bpf.cleanup()349def test_flush(self):350""" Tests the utxocache:flush tracepoint API.351See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheflush"""353self.log.info("test the utxocache:flush tracepoint API")354self.log.info("hook into the utxocache:flush tracepoint")355ctx = USDT(pid=self.nodes[0].process.pid)356ctx.enable_probe(probe="utxocache:flush",357fn_name="trace_utxocache_flush")358bpf = BPF(text=utxocache_flushes_program, usdt_contexts=[ctx], debug=0, cflags=bpf_cflags())360# The handle_* function is a ctypes callback function called from C. When361# we assert in the handle_* function, the AssertError doesn't propagate362# back to Python. The exception is ignored. We manually count and assert363# that the handle_* functions succeeded.364EXPECTED_HANDLE_FLUSH_SUCCESS = 3365handle_flush_succeeds = 0366expected_flushes = list()368def handle_utxocache_flush(_, data, __):369nonlocal handle_flush_succeeds370event = ctypes.cast(data, ctypes.POINTER(UTXOCacheFlush)).contents371self.log.info(f"handle_utxocache_flush(): {event}")372expected_flushes.remove({373"mode": FLUSHMODE_NAME[event.mode],374"for_prune": event.for_prune,375"size": event.size376})377# sanity checks only378try:379assert event.memory > 0380assert event.duration > 0381except AssertionError:382self.log.exception("Assertion error")383else:384handle_flush_succeeds += 1386bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush)388self.log.info("stop the node to flush the UTXO cache")389UTXOS_IN_CACHE = 3 # might need to be changed if the earlier tests are modified390# A node shutdown causes two flushes. One that flushes UTXOS_IN_CACHE391# UTXOs and one that flushes 0 UTXOs. Normally the 0-UTXO-flush is the392# second flush, however it can happen that the order changes.393expected_flushes.append({"mode": "FORCE_FLUSH", "for_prune": False, "size": UTXOS_IN_CACHE})394expected_flushes.append({"mode": "FORCE_FLUSH", "for_prune": False, "size": 0})395self.stop_node(0)397bpf.perf_buffer_poll(timeout=200)398bpf.cleanup()400self.log.info("check that we don't expect additional flushes")401assert_equal(0, len(expected_flushes))403self.log.info("restart the node with -prune")404self.start_node(0, ["-fastprune=1", "-prune=1"])406BLOCKS_TO_MINE = 350407self.log.info(f"mine {BLOCKS_TO_MINE} blocks to be able to prune")408self.generate(self.wallet, BLOCKS_TO_MINE)410self.log.info("test the utxocache:flush tracepoint API with pruning")411self.log.info("hook into the utxocache:flush tracepoint")412ctx = USDT(pid=self.nodes[0].process.pid)413ctx.enable_probe(probe="utxocache:flush",414fn_name="trace_utxocache_flush")415bpf = BPF(text=utxocache_flushes_program, usdt_contexts=[ctx], debug=0, cflags=bpf_cflags())416bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush)418self.log.info("prune blockchain to trigger a flush for pruning")419expected_flushes.append({"mode": "NONE", "for_prune": True, "size": BLOCKS_TO_MINE})420self.nodes[0].pruneblockchain(315)422bpf.perf_buffer_poll(timeout=500)423bpf.cleanup()425self.log.info(426"check that we don't expect additional flushes and that the handle_* function succeeded")427assert_equal(0, len(expected_flushes))428assert_equal(EXPECTED_HANDLE_FLUSH_SUCCESS, handle_flush_succeeds)431if __name__ == '__main__':432UTXOCacheTracepointTest(__file__).main()
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.