Skip to content

Commit 76c60d7

Browse files
committed
test: validation:block_connected tracepoint test
This adds a test for the validation:block_connected tracepoint.
1 parent 260e28e commit 76c60d7

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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+
# The handle_* function is a ctypes callback function called from C. When
89+
# we assert in the handle_* function, the AssertError doesn't propagate
90+
# back to Python. The exception is ignored. We manually count and assert
91+
# that the handle_* functions succeeded.
92+
BLOCKS_EXPECTED = 2
93+
blocks_checked = 0
94+
expected_blocks = list()
95+
96+
self.log.info("hook into the validation:block_connected tracepoint")
97+
ctx = USDT(path=str(self.options.bitcoind))
98+
ctx.enable_probe(probe="validation:block_connected",
99+
fn_name="trace_block_connected")
100+
bpf = BPF(text=validation_blockconnected_program,
101+
usdt_contexts=[ctx], debug=0)
102+
103+
def handle_blockconnected(_, data, __):
104+
nonlocal expected_blocks, blocks_checked
105+
event = ctypes.cast(data, ctypes.POINTER(Block)).contents
106+
self.log.info(f"handle_blockconnected(): {event}")
107+
block = expected_blocks.pop(0)
108+
assert_equal(block["hash"], bytes(event.hash[::-1]).hex())
109+
assert_equal(block["height"], event.height)
110+
assert_equal(len(block["tx"]), event.transactions)
111+
assert_equal(len([tx["vin"] for tx in block["tx"]]), event.inputs)
112+
assert_equal(0, event.sigops) # no sigops in coinbase tx
113+
# only plausibility checks
114+
assert(event.duration > 0)
115+
116+
blocks_checked += 1
117+
118+
bpf["block_connected"].open_perf_buffer(
119+
handle_blockconnected)
120+
121+
self.log.info(f"mine {BLOCKS_EXPECTED} blocks")
122+
block_hashes = self.generatetoaddress(
123+
self.nodes[0], BLOCKS_EXPECTED, ADDRESS_BCRT1_UNSPENDABLE)
124+
for block_hash in block_hashes:
125+
expected_blocks.append(self.nodes[0].getblock(block_hash, 2))
126+
127+
bpf.perf_buffer_poll(timeout=200)
128+
bpf.cleanup()
129+
130+
self.log.info(f"check that we traced {BLOCKS_EXPECTED} blocks")
131+
assert_equal(BLOCKS_EXPECTED, blocks_checked)
132+
assert_equal(0, len(expected_blocks))
133+
134+
135+
if __name__ == '__main__':
136+
ValidationTracepointTest().main()

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
'interface_rpc.py',
171171
'interface_usdt_net.py',
172172
'interface_usdt_utxocache.py',
173+
'interface_usdt_validation.py',
173174
'rpc_psbt.py --legacy-wallet',
174175
'rpc_psbt.py --descriptors',
175176
'rpc_users.py',

0 commit comments

Comments
 (0)