/ test / functional / interface_usdt_validation.py
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()