diff --git a/test/functional/interface_usdt_coinselection.py b/test/functional/interface_usdt_coinselection.py index 8148e651a2e7..737740a43bfd 100755 --- a/test/functional/interface_usdt_coinselection.py +++ b/test/functional/interface_usdt_coinselection.py @@ -163,7 +163,7 @@ def run_test(self): ctx.enable_probe(probe="coin_selection:normal_create_tx_internal", fn_name="trace_normal_create_tx") ctx.enable_probe(probe="coin_selection:attempting_aps_create_tx", fn_name="trace_attempt_aps") ctx.enable_probe(probe="coin_selection:aps_create_tx_internal", fn_name="trace_aps_create_tx") - self.bpf = BPF(text=coinselection_tracepoints_program, usdt_contexts=[ctx], debug=0) + self.bpf = BPF(text=coinselection_tracepoints_program, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"]) self.log.info("Prepare wallets") self.generate(self.nodes[0], 101) diff --git a/test/functional/interface_usdt_mempool.py b/test/functional/interface_usdt_mempool.py new file mode 100644 index 000000000000..0168d9f916a4 --- /dev/null +++ b/test/functional/interface_usdt_mempool.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +""" Tests the mempool:* tracepoint API interface. + See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-mempool +""" + +from decimal import Decimal + +# Test will be skipped if we don't have bcc installed +try: + from bcc import BPF, USDT # type: ignore[import] +except ImportError: + pass + +from test_framework.blocktools import COINBASE_MATURITY +from test_framework.messages import COIN, DEFAULT_MEMPOOL_EXPIRY_HOURS +from test_framework.p2p import P2PDataStore +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal +from test_framework.wallet import MiniWallet + +MEMPOOL_TRACEPOINTS_PROGRAM = """ +# include + +// The longest rejection reason is 118 chars and is generated in case of SCRIPT_ERR_EVAL_FALSE by +// strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError())) +#define MAX_REJECT_REASON_LENGTH 118 +// The longest string returned by RemovalReasonToString() is 'sizelimit' +#define MAX_REMOVAL_REASON_LENGTH 9 +#define HASH_LENGTH 32 + +struct added_event +{ + u8 hash[HASH_LENGTH]; + s32 vsize; + s64 fee; +}; + +struct removed_event +{ + u8 hash[HASH_LENGTH]; + char reason[MAX_REMOVAL_REASON_LENGTH]; + s32 vsize; + s64 fee; + u64 entry_time; +}; + +struct rejected_event +{ + u8 hash[HASH_LENGTH]; + char reason[MAX_REJECT_REASON_LENGTH]; +}; + +struct replaced_event +{ + u8 replaced_hash[HASH_LENGTH]; + s32 replaced_vsize; + s64 replaced_fee; + u64 replaced_entry_time; + u8 replacement_hash[HASH_LENGTH]; + s32 replacement_vsize; + s64 replacement_fee; +}; + +// BPF perf buffer to push the data to user space. +BPF_PERF_OUTPUT(added_events); +BPF_PERF_OUTPUT(removed_events); +BPF_PERF_OUTPUT(rejected_events); +BPF_PERF_OUTPUT(replaced_events); + +int trace_added(struct pt_regs *ctx) { + struct added_event added = {}; + + bpf_usdt_readarg_p(1, ctx, &added.hash, HASH_LENGTH); + bpf_usdt_readarg(2, ctx, &added.vsize); + bpf_usdt_readarg(3, ctx, &added.fee); + + added_events.perf_submit(ctx, &added, sizeof(added)); + return 0; +} + +int trace_removed(struct pt_regs *ctx) { + struct removed_event removed = {}; + + bpf_usdt_readarg_p(1, ctx, &removed.hash, HASH_LENGTH); + bpf_usdt_readarg_p(2, ctx, &removed.reason, MAX_REMOVAL_REASON_LENGTH); + bpf_usdt_readarg(3, ctx, &removed.vsize); + bpf_usdt_readarg(4, ctx, &removed.fee); + bpf_usdt_readarg(5, ctx, &removed.entry_time); + + removed_events.perf_submit(ctx, &removed, sizeof(removed)); + return 0; +} + +int trace_rejected(struct pt_regs *ctx) { + struct rejected_event rejected = {}; + + bpf_usdt_readarg_p(1, ctx, &rejected.hash, HASH_LENGTH); + bpf_usdt_readarg_p(2, ctx, &rejected.reason, MAX_REJECT_REASON_LENGTH); + + rejected_events.perf_submit(ctx, &rejected, sizeof(rejected)); + return 0; +} + +int trace_replaced(struct pt_regs *ctx) { + struct replaced_event replaced = {}; + + bpf_usdt_readarg_p(1, ctx, &replaced.replaced_hash, HASH_LENGTH); + bpf_usdt_readarg(2, ctx, &replaced.replaced_vsize); + bpf_usdt_readarg(3, ctx, &replaced.replaced_fee); + bpf_usdt_readarg(4, ctx, &replaced.replaced_entry_time); + bpf_usdt_readarg_p(5, ctx, &replaced.replacement_hash, HASH_LENGTH); + bpf_usdt_readarg(6, ctx, &replaced.replacement_vsize); + bpf_usdt_readarg(7, ctx, &replaced.replacement_fee); + + replaced_events.perf_submit(ctx, &replaced, sizeof(replaced)); + return 0; +} + +""" + + +class MempoolTracepointTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + + def skip_test_if_missing_module(self): + self.skip_if_platform_not_linux() + self.skip_if_no_bitcoind_tracepoints() + self.skip_if_no_python_bcc() + self.skip_if_no_bpf_permissions() + + def added_test(self): + """Add a transaction to the mempool and make sure the tracepoint returns + the expected txid, vsize, and fee.""" + + events = [] + + self.log.info("Hooking into mempool:added tracepoint...") + node = self.nodes[0] + ctx = USDT(pid=node.process.pid) + ctx.enable_probe(probe="mempool:added", fn_name="trace_added") + bpf = BPF(text=MEMPOOL_TRACEPOINTS_PROGRAM, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"]) + + def handle_added_event(_, data, __): + events.append(bpf["added_events"].event(data)) + + bpf["added_events"].open_perf_buffer(handle_added_event) + + self.log.info("Sending transaction...") + fee = Decimal(31200) + tx = self.wallet.send_self_transfer(from_node=node, fee=fee / COIN) + + self.log.info("Polling buffer...") + bpf.perf_buffer_poll(timeout=200) + + self.log.info("Cleaning up mempool...") + self.generate(node, 1) + + self.log.info("Ensuring mempool:added event was handled successfully...") + assert_equal(1, len(events)) + event = events[0] + assert_equal(bytes(event.hash)[::-1].hex(), tx["txid"]) + assert_equal(event.vsize, tx["tx"].get_vsize()) + assert_equal(event.fee, fee) + + bpf.cleanup() + self.generate(self.wallet, 1) + + def removed_test(self): + """Expire a transaction from the mempool and make sure the tracepoint returns + the expected txid, expiry reason, vsize, and fee.""" + + events = [] + + self.log.info("Hooking into mempool:removed tracepoint...") + node = self.nodes[0] + ctx = USDT(pid=node.process.pid) + ctx.enable_probe(probe="mempool:removed", fn_name="trace_removed") + bpf = BPF(text=MEMPOOL_TRACEPOINTS_PROGRAM, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"]) + + def handle_removed_event(_, data, __): + events.append(bpf["removed_events"].event(data)) + + bpf["removed_events"].open_perf_buffer(handle_removed_event) + + self.log.info("Sending transaction...") + fee = Decimal(31200) + tx = self.wallet.send_self_transfer(from_node=node, fee=fee / COIN) + txid = tx["txid"] + + self.log.info("Fast-forwarding time to mempool expiry...") + entry_time = node.getmempoolentry(txid)["time"] + expiry_time = entry_time + 60 * 60 * DEFAULT_MEMPOOL_EXPIRY_HOURS + 5 + node.setmocktime(expiry_time) + + self.log.info("Triggering expiry...") + self.wallet.get_utxo(txid=txid) + self.wallet.send_self_transfer(from_node=node) + + self.log.info("Polling buffer...") + bpf.perf_buffer_poll(timeout=200) + + self.log.info("Ensuring mempool:removed event was handled successfully...") + assert_equal(1, len(events)) + event = events[0] + assert_equal(bytes(event.hash)[::-1].hex(), txid) + assert_equal(event.reason.decode("UTF-8"), "expiry") + assert_equal(event.vsize, tx["tx"].get_vsize()) + assert_equal(event.fee, fee) + assert_equal(event.entry_time, entry_time) + + bpf.cleanup() + self.generate(self.wallet, 1) + + def replaced_test(self): + """Replace one and two transactions in the mempool and make sure the tracepoint + returns the expected txids, vsizes, and fees.""" + + events = [] + + self.log.info("Hooking into mempool:replaced tracepoint...") + node = self.nodes[0] + ctx = USDT(pid=node.process.pid) + ctx.enable_probe(probe="mempool:replaced", fn_name="trace_replaced") + bpf = BPF(text=MEMPOOL_TRACEPOINTS_PROGRAM, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"]) + + def handle_replaced_event(_, data, __): + events.append(bpf["replaced_events"].event(data)) + + bpf["replaced_events"].open_perf_buffer(handle_replaced_event) + + self.log.info("Sending RBF transaction...") + utxo = self.wallet.get_utxo(mark_as_spent=True) + original_fee = Decimal(40000) + original_tx = self.wallet.send_self_transfer( + from_node=node, utxo_to_spend=utxo, fee=original_fee / COIN + ) + entry_time = node.getmempoolentry(original_tx["txid"])["time"] + + self.log.info("Sending replacement transaction...") + replacement_fee = Decimal(45000) + replacement_tx = self.wallet.send_self_transfer( + from_node=node, utxo_to_spend=utxo, fee=replacement_fee / COIN + ) + + self.log.info("Polling buffer...") + bpf.perf_buffer_poll(timeout=200) + + self.log.info("Ensuring mempool:replaced event was handled successfully...") + assert_equal(1, len(events)) + event = events[0] + assert_equal(bytes(event.replaced_hash)[::-1].hex(), original_tx["txid"]) + assert_equal(event.replaced_vsize, original_tx["tx"].get_vsize()) + assert_equal(event.replaced_fee, original_fee) + assert_equal(event.replaced_entry_time, entry_time) + assert_equal(bytes(event.replacement_hash)[::-1].hex(), replacement_tx["txid"]) + assert_equal(event.replacement_vsize, replacement_tx["tx"].get_vsize()) + assert_equal(event.replacement_fee, replacement_fee) + + bpf.cleanup() + self.generate(self.wallet, 1) + + def rejected_test(self): + """Create an invalid transaction and make sure the tracepoint returns + the expected txid, rejection reason, peer id, and peer address.""" + + events = [] + + self.log.info("Adding P2P connection...") + node = self.nodes[0] + node.add_p2p_connection(P2PDataStore()) + + self.log.info("Hooking into mempool:rejected tracepoint...") + ctx = USDT(pid=node.process.pid) + ctx.enable_probe(probe="mempool:rejected", fn_name="trace_rejected") + bpf = BPF(text=MEMPOOL_TRACEPOINTS_PROGRAM, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"]) + + def handle_rejected_event(_, data, __): + events.append(bpf["rejected_events"].event(data)) + + bpf["rejected_events"].open_perf_buffer(handle_rejected_event) + + self.log.info("Sending invalid transaction...") + tx = self.wallet.create_self_transfer(fee_rate=Decimal(0)) + node.p2ps[0].send_txs_and_test([tx["tx"]], node, success=False) + + self.log.info("Polling buffer...") + bpf.perf_buffer_poll(timeout=200) + + self.log.info("Ensuring mempool:rejected event was handled successfully...") + assert_equal(1, len(events)) + event = events[0] + assert_equal(bytes(event.hash)[::-1].hex(), tx["tx"].hash) + # The next test is already known to fail, so disable it to avoid + # wasting CPU time and developer time. See + # https://github.com/bitcoin/bitcoin/issues/27380 + #assert_equal(event.reason.decode("UTF-8"), "min relay fee not met") + + bpf.cleanup() + self.generate(self.wallet, 1) + + def run_test(self): + """Tests the mempool:added, mempool:removed, mempool:replaced, + and mempool:rejected tracepoints.""" + + # Create some coinbase transactions and mature them so they can be spent + node = self.nodes[0] + self.wallet = MiniWallet(node) + self.generate(self.wallet, 4) + self.generate(node, COINBASE_MATURITY) + + # Test individual tracepoints + self.added_test() + self.removed_test() + self.replaced_test() + self.rejected_test() + + +if __name__ == "__main__": + MempoolTracepointTest().main() diff --git a/test/functional/interface_usdt_net.py b/test/functional/interface_usdt_net.py index d446c0e5b7a1..5d7c8c23041f 100755 --- a/test/functional/interface_usdt_net.py +++ b/test/functional/interface_usdt_net.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ Tests the net:* tracepoint API interface. - See https://github.com/dashpay/dash/blob/develop/doc/tracing.md#context-net + See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net """ import ctypes @@ -92,7 +92,7 @@ def skip_test_if_missing_module(self): def run_test(self): # Tests the net:inbound_message and net:outbound_message tracepoints - # See https://github.com/dashpay/dash/blob/develop/doc/tracing.md#context-net + # See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net class P2PMessage(ctypes.Structure): _fields_ = [ @@ -109,7 +109,7 @@ def __repr__(self): self.log.info( "hook into the net:inbound_message and net:outbound_message tracepoints") - ctx = USDT(path=str(self.options.bitcoind)) + ctx = USDT(pid=self.nodes[0].process.pid) ctx.enable_probe(probe="net:inbound_message", fn_name="trace_inbound_message") ctx.enable_probe(probe="net:outbound_message", @@ -121,11 +121,11 @@ def __repr__(self): checked_outbound_version_msg = 0 events = [] - def check_p2p_message(event, inbound): + def check_p2p_message(event, is_inbound): nonlocal checked_inbound_version_msg, checked_outbound_version_msg if event.msg_type.decode("utf-8") == "version": self.log.info( - f"check_p2p_message(): {'inbound' if inbound else 'outbound'} {event}") + f"check_p2p_message(): {'inbound' if is_inbound else 'outbound'} {event}") peer = self.nodes[0].getpeerinfo()[0] msg = msg_version() msg.deserialize(BytesIO(bytes(event.msg[:event.msg_size]))) @@ -133,13 +133,12 @@ def check_p2p_message(event, inbound): assert_equal(peer["addr"], event.peer_addr.decode("utf-8")) assert_equal(peer["connection_type"], event.peer_conn_type.decode("utf-8")) - if inbound: + if is_inbound: checked_inbound_version_msg += 1 else: checked_outbound_version_msg += 1 def handle_inbound(_, data, __): - nonlocal events event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents events.append((event, True)) @@ -150,15 +149,15 @@ def handle_outbound(_, data, __): bpf["inbound_messages"].open_perf_buffer(handle_inbound) bpf["outbound_messages"].open_perf_buffer(handle_outbound) - self.log.info("connect a P2P test node to our dashd node") + self.log.info("connect a P2P test node to our bitcoind node") test_node = P2PInterface() self.nodes[0].add_p2p_connection(test_node) bpf.perf_buffer_poll(timeout=200) self.log.info( "check receipt and content of in- and outbound version messages") - for event, inbound in events: - check_p2p_message(event, inbound) + for event, is_inbound in events: + check_p2p_message(event, is_inbound) assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG, checked_inbound_version_msg) assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG, diff --git a/test/functional/interface_usdt_utxocache.py b/test/functional/interface_usdt_utxocache.py index 76bddc392ac9..06cdcd10a04b 100755 --- a/test/functional/interface_usdt_utxocache.py +++ b/test/functional/interface_usdt_utxocache.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ Tests the utxocache:* tracepoint API interface. - See https://github.com/dashpay/dash/blob/develop/doc/tracing.md#context-utxocache + See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-utxocache """ import ctypes @@ -14,7 +14,6 @@ except ImportError: pass from test_framework.messages import COIN -from test_framework.governance import EXPECTED_STDERR_NO_GOV_PRUNE from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal from test_framework.wallet import MiniWallet @@ -145,7 +144,6 @@ def skip_test_if_missing_module(self): def run_test(self): self.wallet = MiniWallet(self.nodes[0]) - self.generate(self.wallet, 101) self.test_uncache() self.test_add_spent() @@ -153,7 +151,7 @@ def run_test(self): def test_uncache(self): """ Tests the utxocache:uncache tracepoint API. - https://github.com/dashpay/dash/blob/develop/doc/tracing.md#tracepoint-utxocacheuncache + https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheuncache """ # To trigger an UTXO uncache from the cache, we create an invalid transaction # spending a not-cached, but existing UTXO. During transaction validation, this @@ -174,7 +172,7 @@ def test_uncache(self): invalid_tx.vin[0].prevout.hash = int(block_1_coinbase_txid, 16) self.log.info("hooking into the utxocache:uncache tracepoint") - ctx = USDT(path=str(self.options.bitcoind)) + ctx = USDT(pid=self.nodes[0].process.pid) ctx.enable_probe(probe="utxocache:uncache", fn_name="trace_utxocache_uncache") bpf = BPF(text=utxocache_changes_program, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"]) @@ -218,8 +216,8 @@ def handle_utxocache_uncache(_, data, __): def test_add_spent(self): """ Tests the utxocache:add utxocache:spent tracepoint API - See https://github.com/dashpay/dash/blob/develop/doc/tracing.md#tracepoint-utxocacheadd - and https://github.com/dashpay/dash/blob/develop/doc/tracing.md#tracepoint-utxocachespent + See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheadd + and https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocachespent """ self.log.info( @@ -242,7 +240,7 @@ def test_add_spent(self): self.log.info( "hook into the utxocache:add and utxocache:spent tracepoints") - ctx = USDT(path=str(self.options.bitcoind)) + ctx = USDT(pid=self.nodes[0].process.pid) ctx.enable_probe(probe="utxocache:add", fn_name="trace_utxocache_add") ctx.enable_probe(probe="utxocache:spent", fn_name="trace_utxocache_spent") @@ -254,43 +252,30 @@ def test_add_spent(self): # that the handle_* functions succeeded. EXPECTED_HANDLE_ADD_SUCCESS = 2 EXPECTED_HANDLE_SPENT_SUCCESS = 1 - handle_add_succeeds = 0 - handle_spent_succeeds = 0 - expected_utxocache_spents = [] expected_utxocache_adds = [] + expected_utxocache_spents = [] + + actual_utxocache_adds = [] + actual_utxocache_spents = [] + + def compare_utxo_with_event(utxo, event): + """Compare a utxo dict to the event produced by BPF""" + assert_equal(utxo["txid"], bytes(event.txid[::-1]).hex()) + assert_equal(utxo["index"], event.index) + assert_equal(utxo["height"], event.height) + assert_equal(utxo["value"], event.value) + assert_equal(utxo["is_coinbase"], event.is_coinbase) def handle_utxocache_add(_, data, __): - nonlocal handle_add_succeeds event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents self.log.info(f"handle_utxocache_add(): {event}") - add = expected_utxocache_adds.pop(0) - try: - assert_equal(add["txid"], bytes(event.txid[::-1]).hex()) - assert_equal(add["index"], event.index) - assert_equal(add["height"], event.height) - assert_equal(add["value"], event.value) - assert_equal(add["is_coinbase"], event.is_coinbase) - except AssertionError: - self.log.exception("Assertion failed") - else: - handle_add_succeeds += 1 + actual_utxocache_adds.append(event) def handle_utxocache_spent(_, data, __): - nonlocal handle_spent_succeeds event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents self.log.info(f"handle_utxocache_spent(): {event}") - spent = expected_utxocache_spents.pop(0) - try: - assert_equal(spent["txid"], bytes(event.txid[::-1]).hex()) - assert_equal(spent["index"], event.index) - assert_equal(spent["height"], event.height) - assert_equal(spent["value"], event.value) - assert_equal(spent["is_coinbase"], event.is_coinbase) - except AssertionError: - self.log.exception("Assertion failed") - else: - handle_spent_succeeds += 1 + actual_utxocache_spents.append(event) bpf["utxocache_add"].open_perf_buffer(handle_utxocache_add) bpf["utxocache_spent"].open_perf_buffer(handle_utxocache_spent) @@ -326,27 +311,26 @@ def handle_utxocache_spent(_, data, __): "is_coinbase": block_index == 0, }) - assert_equal(EXPECTED_HANDLE_ADD_SUCCESS, len(expected_utxocache_adds)) - assert_equal(EXPECTED_HANDLE_SPENT_SUCCESS, - len(expected_utxocache_spents)) - bpf.perf_buffer_poll(timeout=200) - bpf.cleanup() + + assert_equal(EXPECTED_HANDLE_ADD_SUCCESS, len(expected_utxocache_adds), len(actual_utxocache_adds)) + assert_equal(EXPECTED_HANDLE_SPENT_SUCCESS, len(expected_utxocache_spents), len(actual_utxocache_spents)) self.log.info( f"check that we successfully traced {EXPECTED_HANDLE_ADD_SUCCESS} adds and {EXPECTED_HANDLE_SPENT_SUCCESS} spent") - assert_equal(0, len(expected_utxocache_adds)) - assert_equal(0, len(expected_utxocache_spents)) - assert_equal(EXPECTED_HANDLE_ADD_SUCCESS, handle_add_succeeds) - assert_equal(EXPECTED_HANDLE_SPENT_SUCCESS, handle_spent_succeeds) + for expected_utxo, actual_event in zip(expected_utxocache_adds + expected_utxocache_spents, + actual_utxocache_adds + actual_utxocache_spents): + compare_utxo_with_event(expected_utxo, actual_event) + + bpf.cleanup() def test_flush(self): """ Tests the utxocache:flush tracepoint API. - See https://github.com/dashpay/dash/blob/develop/doc/tracing.md#tracepoint-utxocacheflush""" + See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheflush""" self.log.info("test the utxocache:flush tracepoint API") self.log.info("hook into the utxocache:flush tracepoint") - ctx = USDT(path=str(self.options.bitcoind)) + ctx = USDT(pid=self.nodes[0].process.pid) ctx.enable_probe(probe="utxocache:flush", fn_name="trace_utxocache_flush") bpf = BPF(text=utxocache_flushes_program, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"]) @@ -357,52 +341,61 @@ def test_flush(self): # that the handle_* functions succeeded. EXPECTED_HANDLE_FLUSH_SUCCESS = 3 handle_flush_succeeds = 0 - possible_cache_sizes = set() - expected_flushes = [] + expected_flushes = list() def handle_utxocache_flush(_, data, __): nonlocal handle_flush_succeeds event = ctypes.cast(data, ctypes.POINTER(UTXOCacheFlush)).contents self.log.info(f"handle_utxocache_flush(): {event}") - expected = expected_flushes.pop(0) - assert_equal(expected["mode"], FLUSHMODE_NAME[event.mode]) - possible_cache_sizes.remove(event.size) # fails if size not in set + expected_flushes.remove({ + "mode": FLUSHMODE_NAME[event.mode], + "for_prune": event.for_prune, + "size": event.size + }) # sanity checks only - assert(event.memory > 0) - assert(event.duration > 0) - handle_flush_succeeds += 1 + try: + assert event.memory > 0 + assert event.duration > 0 + except AssertionError: + self.log.exception("Assertion error") + else: + handle_flush_succeeds += 1 bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush) self.log.info("stop the node to flush the UTXO cache") - UTXOS_IN_CACHE = 104 # might need to be changed if the earlier tests are modified + UTXOS_IN_CACHE = 2 # might need to be changed if the earlier tests are modified # A node shutdown causes two flushes. One that flushes UTXOS_IN_CACHE # UTXOs and one that flushes 0 UTXOs. Normally the 0-UTXO-flush is the # second flush, however it can happen that the order changes. - possible_cache_sizes = {UTXOS_IN_CACHE, 0} - flush_for_shutdown = {"mode": "ALWAYS", "for_prune": False} - expected_flushes.extend([flush_for_shutdown, flush_for_shutdown]) + expected_flushes.append({"mode": "ALWAYS", "for_prune": False, "size": UTXOS_IN_CACHE}) + expected_flushes.append({"mode": "ALWAYS", "for_prune": False, "size": 0}) self.stop_node(0) bpf.perf_buffer_poll(timeout=200) + bpf.cleanup() self.log.info("check that we don't expect additional flushes") assert_equal(0, len(expected_flushes)) - assert_equal(0, len(possible_cache_sizes)) self.log.info("restart the node with -prune") self.start_node(0, ["-fastprune=1", "-prune=1"]) - BLOCKS_TO_MINE = 450 + BLOCKS_TO_MINE = 350 self.log.info(f"mine {BLOCKS_TO_MINE} blocks to be able to prune") self.generate(self.wallet, BLOCKS_TO_MINE) - # we added BLOCKS_TO_MINE coinbase UTXOs to the cache - possible_cache_sizes = {BLOCKS_TO_MINE} - expected_flushes.append( - {"mode": "NONE", "for_prune": True, "size_fn": lambda x: x == BLOCKS_TO_MINE}) + + self.log.info("test the utxocache:flush tracepoint API with pruning") + self.log.info("hook into the utxocache:flush tracepoint") + ctx = USDT(pid=self.nodes[0].process.pid) + ctx.enable_probe(probe="utxocache:flush", + fn_name="trace_utxocache_flush") + bpf = BPF(text=utxocache_flushes_program, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"]) + bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush) self.log.info(f"prune blockchain to trigger a flush for pruning") - self.nodes[0].pruneblockchain(415) + expected_flushes.append({"mode": "NONE", "for_prune": True, "size": 0}) + self.nodes[0].pruneblockchain(315) bpf.perf_buffer_poll(timeout=500) bpf.cleanup() @@ -410,10 +403,8 @@ def handle_utxocache_flush(_, data, __): self.log.info( f"check that we don't expect additional flushes and that the handle_* function succeeded") assert_equal(0, len(expected_flushes)) - assert_equal(0, len(possible_cache_sizes)) assert_equal(EXPECTED_HANDLE_FLUSH_SUCCESS, handle_flush_succeeds) - self.stop_node(0, expected_stderr=EXPECTED_STDERR_NO_GOV_PRUNE) if __name__ == '__main__': UTXOCacheTracepointTest().main() diff --git a/test/functional/interface_usdt_validation.py b/test/functional/interface_usdt_validation.py index 0fe56abe0d51..30982393d85b 100755 --- a/test/functional/interface_usdt_validation.py +++ b/test/functional/interface_usdt_validation.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ Tests the validation:* tracepoint API interface. - See https://github.com/dashpay/dash/blob/develop/doc/tracing.md#context-validation + See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-validation """ import ctypes @@ -64,7 +64,7 @@ def run_test(self): # Tests the validation:block_connected tracepoint by generating blocks # and comparing the values passed in the tracepoint arguments with the # blocks. - # See https://github.com/dashpay/dash/blob/develop/doc/tracing.md#tracepoint-validationblock_connected + # See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-validationblock_connected class Block(ctypes.Structure): _fields_ = [ @@ -86,39 +86,27 @@ def __repr__(self): self.duration) BLOCKS_EXPECTED = 2 - blocks_checked = 0 expected_blocks = dict() events = [] self.log.info("hook into the validation:block_connected tracepoint") - ctx = USDT(path=str(self.options.bitcoind)) + ctx = USDT(pid=self.nodes[0].process.pid) ctx.enable_probe(probe="validation:block_connected", fn_name="trace_block_connected") bpf = BPF(text=validation_blockconnected_program, usdt_contexts=[ctx], debug=0, cflags=["-Wno-error=implicit-function-declaration"]) def handle_blockconnected(_, data, __): - nonlocal events, blocks_checked event = ctypes.cast(data, ctypes.POINTER(Block)).contents self.log.info(f"handle_blockconnected(): {event}") - block_hash = bytes(event.hash[::-1]).hex() - block = expected_blocks[block_hash] - assert_equal(block["hash"], block_hash) - assert_equal(block["height"], event.height) - assert_equal(len(block["tx"]), event.transactions) - assert_equal(len([tx["vin"] for tx in block["tx"]]), event.inputs) - assert_equal(0, event.sigops) # no sigops in coinbase tx - # only plausibility checks - assert(event.duration > 0) - del expected_blocks[block_hash] - blocks_checked += 1 + events.append(event) bpf["block_connected"].open_perf_buffer( handle_blockconnected) self.log.info(f"mine {BLOCKS_EXPECTED} blocks") - block_hashes = self.generatetoaddress(self.nodes[0], - BLOCKS_EXPECTED, ADDRESS_BCRT1_UNSPENDABLE) + block_hashes = self.generatetoaddress( + self.nodes[0], BLOCKS_EXPECTED, ADDRESS_BCRT1_UNSPENDABLE) for block_hash in block_hashes: expected_blocks[block_hash] = self.nodes[0].getblock(block_hash, 2) @@ -136,7 +124,7 @@ def handle_blockconnected(_, data, __): # only plausibility checks assert event.duration > 0 del expected_blocks[block_hash] - assert_equal(BLOCKS_EXPECTED, blocks_checked) + assert_equal(BLOCKS_EXPECTED, len(events)) assert_equal(0, len(expected_blocks)) bpf.cleanup()