interface_usdt_validation.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2022-present The Bitcoin Core developers 3 # Distributed under the MIT software license, see the accompanying 4 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 6 """ Tests the validation:* tracepoint API interface. 7 See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-validation 8 """ 9 10 import ctypes 11 import time 12 13 # Test will be skipped if we don't have bcc installed 14 try: 15 from bcc import BPF, USDT # type: ignore[import] 16 except ImportError: 17 pass 18 19 from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE 20 from test_framework.test_framework import BitcoinTestFramework 21 from test_framework.util import ( 22 assert_equal, 23 bpf_cflags, 24 ) 25 26 validation_blockconnected_program = """ 27 #include <uapi/linux/ptrace.h> 28 29 typedef signed long long i64; 30 31 struct connected_block 32 { 33 char hash[32]; 34 int height; 35 i64 transactions; 36 int inputs; 37 i64 sigops; 38 u64 duration; 39 }; 40 41 BPF_PERF_OUTPUT(block_connected); 42 int trace_block_connected(struct pt_regs *ctx) { 43 struct connected_block block = {}; 44 void *phash = NULL; 45 bpf_usdt_readarg(1, ctx, &phash); 46 bpf_probe_read_user(&block.hash, sizeof(block.hash), phash); 47 bpf_usdt_readarg(2, ctx, &block.height); 48 bpf_usdt_readarg(3, ctx, &block.transactions); 49 bpf_usdt_readarg(4, ctx, &block.inputs); 50 bpf_usdt_readarg(5, ctx, &block.sigops); 51 bpf_usdt_readarg(6, ctx, &block.duration); 52 block_connected.perf_submit(ctx, &block, sizeof(block)); 53 return 0; 54 } 55 """ 56 57 58 class ValidationTracepointTest(BitcoinTestFramework): 59 def set_test_params(self): 60 self.num_nodes = 1 61 62 def skip_test_if_missing_module(self): 63 self.skip_if_platform_not_linux() 64 self.skip_if_no_bitcoind_tracepoints() 65 self.skip_if_no_python_bcc() 66 self.skip_if_no_bpf_permissions() 67 self.skip_if_running_under_valgrind() 68 69 def run_test(self): 70 # Tests the validation:block_connected tracepoint by generating blocks 71 # and comparing the values passed in the tracepoint arguments with the 72 # blocks. 73 # See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-validationblock_connected 74 75 class Block(ctypes.Structure): 76 _fields_ = [ 77 ("hash", ctypes.c_ubyte * 32), 78 ("height", ctypes.c_int), 79 ("transactions", ctypes.c_int64), 80 ("inputs", ctypes.c_int), 81 ("sigops", ctypes.c_int64), 82 ("duration", ctypes.c_uint64), 83 ] 84 85 def __repr__(self): 86 return "ConnectedBlock(hash=%s height=%d, transactions=%d, inputs=%d, sigops=%d, duration=%d)" % ( 87 bytes(self.hash[::-1]).hex(), 88 self.height, 89 self.transactions, 90 self.inputs, 91 self.sigops, 92 self.duration) 93 94 BLOCKS_EXPECTED = 2 95 expected_blocks = dict() 96 events = [] 97 98 self.log.info("hook into the validation:block_connected tracepoint") 99 ctx = USDT(pid=self.nodes[0].process.pid) 100 ctx.enable_probe(probe="validation:block_connected", 101 fn_name="trace_block_connected") 102 bpf = BPF(text=validation_blockconnected_program, 103 usdt_contexts=[ctx], debug=0, cflags=bpf_cflags()) 104 105 def handle_blockconnected(_, data, __): 106 event = ctypes.cast(data, ctypes.POINTER(Block)).contents 107 self.log.info(f"handle_blockconnected(): {event}") 108 events.append(event) 109 110 bpf["block_connected"].open_perf_buffer( 111 handle_blockconnected) 112 113 self.log.info(f"mine {BLOCKS_EXPECTED} blocks") 114 generatetoaddress_duration = dict() 115 for _ in range(BLOCKS_EXPECTED): 116 start = time.time() 117 hash = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)[0] 118 generatetoaddress_duration[hash] = (time.time() - start) * 1e9 # in nanoseconds 119 expected_blocks[hash] = self.nodes[0].getblock(hash, 2) 120 121 bpf.perf_buffer_poll(timeout=200) 122 123 self.log.info(f"check that we correctly traced {BLOCKS_EXPECTED} blocks") 124 for event in events: 125 block_hash = bytes(event.hash[::-1]).hex() 126 block = expected_blocks[block_hash] 127 assert_equal(block["hash"], block_hash) 128 assert_equal(block["height"], event.height) 129 assert_equal(len(block["tx"]), event.transactions) 130 assert_equal(len([tx["vin"] for tx in block["tx"]]), event.inputs) 131 assert_equal(0, event.sigops) # no sigops in coinbase tx 132 # only plausibility checks 133 assert event.duration > 0 134 # generatetoaddress (mining and connecting) takes longer than 135 # connecting the block. In case the duration unit is off, we'll 136 # detect it with this assert. 137 assert event.duration < generatetoaddress_duration[block_hash] 138 del expected_blocks[block_hash] 139 assert_equal(BLOCKS_EXPECTED, len(events)) 140 assert_equal(0, len(expected_blocks)) 141 142 bpf.cleanup() 143 144 145 if __name__ == '__main__': 146 ValidationTracepointTest(__file__).main()