interface_usdt_validation.py raw
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()
147