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 68 def run_test(self): 69 # Tests the validation:block_connected tracepoint by generating blocks 70 # and comparing the values passed in the tracepoint arguments with the 71 # blocks. 72 # See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-validationblock_connected 73 74 class Block(ctypes.Structure): 75 _fields_ = [ 76 ("hash", ctypes.c_ubyte * 32), 77 ("height", ctypes.c_int), 78 ("transactions", ctypes.c_int64), 79 ("inputs", ctypes.c_int), 80 ("sigops", ctypes.c_int64), 81 ("duration", ctypes.c_uint64), 82 ] 83 84 def __repr__(self): 85 return "ConnectedBlock(hash=%s height=%d, transactions=%d, inputs=%d, sigops=%d, duration=%d)" % ( 86 bytes(self.hash[::-1]).hex(), 87 self.height, 88 self.transactions, 89 self.inputs, 90 self.sigops, 91 self.duration) 92 93 BLOCKS_EXPECTED = 2 94 expected_blocks = dict() 95 events = [] 96 97 self.log.info("hook into the validation:block_connected tracepoint") 98 ctx = USDT(pid=self.nodes[0].process.pid) 99 ctx.enable_probe(probe="validation:block_connected", 100 fn_name="trace_block_connected") 101 bpf = BPF(text=validation_blockconnected_program, 102 usdt_contexts=[ctx], debug=0, cflags=bpf_cflags()) 103 104 def handle_blockconnected(_, data, __): 105 event = ctypes.cast(data, ctypes.POINTER(Block)).contents 106 self.log.info(f"handle_blockconnected(): {event}") 107 events.append(event) 108 109 bpf["block_connected"].open_perf_buffer( 110 handle_blockconnected) 111 112 self.log.info(f"mine {BLOCKS_EXPECTED} blocks") 113 generatetoaddress_duration = dict() 114 for _ in range(BLOCKS_EXPECTED): 115 start = time.time() 116 hash = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)[0] 117 generatetoaddress_duration[hash] = (time.time() - start) * 1e9 # in nanoseconds 118 expected_blocks[hash] = self.nodes[0].getblock(hash, 2) 119 120 bpf.perf_buffer_poll(timeout=200) 121 122 self.log.info(f"check that we correctly traced {BLOCKS_EXPECTED} blocks") 123 for event in events: 124 block_hash = bytes(event.hash[::-1]).hex() 125 block = expected_blocks[block_hash] 126 assert_equal(block["hash"], block_hash) 127 assert_equal(block["height"], event.height) 128 assert_equal(len(block["tx"]), event.transactions) 129 assert_equal(len([tx["vin"] for tx in block["tx"]]), event.inputs) 130 assert_equal(0, event.sigops) # no sigops in coinbase tx 131 # only plausibility checks 132 assert event.duration > 0 133 # generatetoaddress (mining and connecting) takes longer than 134 # connecting the block. In case the duration unit is off, we'll 135 # detect it with this assert. 136 assert event.duration < generatetoaddress_duration[block_hash] 137 del expected_blocks[block_hash] 138 assert_equal(BLOCKS_EXPECTED, len(events)) 139 assert_equal(0, len(expected_blocks)) 140 141 bpf.cleanup() 142 143 144 if __name__ == '__main__': 145 ValidationTracepointTest(__file__).main()