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