mirror of https://github.com/bitcoin/bitcoin
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
235 lines
8.8 KiB
235 lines
8.8 KiB
#!/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 coin_selection:* tracepoint API interface.
|
|
See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-coin_selection
|
|
"""
|
|
|
|
# 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.test_framework import BitcoinTestFramework
|
|
from test_framework.util import (
|
|
assert_equal,
|
|
assert_greater_than,
|
|
assert_raises_rpc_error,
|
|
)
|
|
|
|
coinselection_tracepoints_program = """
|
|
#include <uapi/linux/ptrace.h>
|
|
|
|
#define WALLET_NAME_LENGTH 16
|
|
#define ALGO_NAME_LENGTH 16
|
|
|
|
struct event_data
|
|
{
|
|
u8 type;
|
|
char wallet_name[WALLET_NAME_LENGTH];
|
|
|
|
// selected coins event
|
|
char algo[ALGO_NAME_LENGTH];
|
|
s64 target;
|
|
s64 waste;
|
|
s64 selected_value;
|
|
|
|
// create tx event
|
|
bool success;
|
|
s64 fee;
|
|
s32 change_pos;
|
|
|
|
// aps create tx event
|
|
bool use_aps;
|
|
};
|
|
|
|
BPF_QUEUE(coin_selection_events, struct event_data, 1024);
|
|
|
|
int trace_selected_coins(struct pt_regs *ctx) {
|
|
struct event_data data;
|
|
__builtin_memset(&data, 0, sizeof(data));
|
|
data.type = 1;
|
|
bpf_usdt_readarg_p(1, ctx, &data.wallet_name, WALLET_NAME_LENGTH);
|
|
bpf_usdt_readarg_p(2, ctx, &data.algo, ALGO_NAME_LENGTH);
|
|
bpf_usdt_readarg(3, ctx, &data.target);
|
|
bpf_usdt_readarg(4, ctx, &data.waste);
|
|
bpf_usdt_readarg(5, ctx, &data.selected_value);
|
|
coin_selection_events.push(&data, 0);
|
|
return 0;
|
|
}
|
|
|
|
int trace_normal_create_tx(struct pt_regs *ctx) {
|
|
struct event_data data;
|
|
__builtin_memset(&data, 0, sizeof(data));
|
|
data.type = 2;
|
|
bpf_usdt_readarg_p(1, ctx, &data.wallet_name, WALLET_NAME_LENGTH);
|
|
bpf_usdt_readarg(2, ctx, &data.success);
|
|
bpf_usdt_readarg(3, ctx, &data.fee);
|
|
bpf_usdt_readarg(4, ctx, &data.change_pos);
|
|
coin_selection_events.push(&data, 0);
|
|
return 0;
|
|
}
|
|
|
|
int trace_attempt_aps(struct pt_regs *ctx) {
|
|
struct event_data data;
|
|
__builtin_memset(&data, 0, sizeof(data));
|
|
data.type = 3;
|
|
bpf_usdt_readarg_p(1, ctx, &data.wallet_name, WALLET_NAME_LENGTH);
|
|
coin_selection_events.push(&data, 0);
|
|
return 0;
|
|
}
|
|
|
|
int trace_aps_create_tx(struct pt_regs *ctx) {
|
|
struct event_data data;
|
|
__builtin_memset(&data, 0, sizeof(data));
|
|
data.type = 4;
|
|
bpf_usdt_readarg_p(1, ctx, &data.wallet_name, WALLET_NAME_LENGTH);
|
|
bpf_usdt_readarg(2, ctx, &data.use_aps);
|
|
bpf_usdt_readarg(3, ctx, &data.success);
|
|
bpf_usdt_readarg(4, ctx, &data.fee);
|
|
bpf_usdt_readarg(5, ctx, &data.change_pos);
|
|
coin_selection_events.push(&data, 0);
|
|
return 0;
|
|
}
|
|
"""
|
|
|
|
|
|
class CoinSelectionTracepointTest(BitcoinTestFramework):
|
|
def add_options(self, parser):
|
|
self.add_wallet_options(parser)
|
|
|
|
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()
|
|
self.skip_if_no_wallet()
|
|
|
|
def get_tracepoints(self, expected_types):
|
|
events = []
|
|
try:
|
|
for i in range(0, len(expected_types) + 1):
|
|
event = self.bpf["coin_selection_events"].pop()
|
|
assert_equal(event.wallet_name.decode(), self.default_wallet_name)
|
|
assert_equal(event.type, expected_types[i])
|
|
events.append(event)
|
|
else:
|
|
# If the loop exits successfully instead of throwing a KeyError, then we have had
|
|
# more events than expected. There should be no more than len(expected_types) events.
|
|
assert False
|
|
except KeyError:
|
|
assert_equal(len(events), len(expected_types))
|
|
return events
|
|
|
|
|
|
def determine_selection_from_usdt(self, events):
|
|
success = None
|
|
use_aps = None
|
|
algo = None
|
|
waste = None
|
|
change_pos = None
|
|
|
|
is_aps = False
|
|
sc_events = []
|
|
for event in events:
|
|
if event.type == 1:
|
|
if not is_aps:
|
|
algo = event.algo.decode()
|
|
waste = event.waste
|
|
sc_events.append(event)
|
|
elif event.type == 2:
|
|
success = event.success
|
|
if not is_aps:
|
|
change_pos = event.change_pos
|
|
elif event.type == 3:
|
|
is_aps = True
|
|
elif event.type == 4:
|
|
assert is_aps
|
|
if event.use_aps:
|
|
use_aps = True
|
|
assert_equal(len(sc_events), 2)
|
|
algo = sc_events[1].algo.decode()
|
|
waste = sc_events[1].waste
|
|
change_pos = event.change_pos
|
|
return success, use_aps, algo, waste, change_pos
|
|
|
|
def run_test(self):
|
|
self.log.info("hook into the coin_selection tracepoints")
|
|
ctx = USDT(pid=self.nodes[0].process.pid)
|
|
ctx.enable_probe(probe="coin_selection:selected_coins", fn_name="trace_selected_coins")
|
|
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, cflags=["-Wno-error=implicit-function-declaration"])
|
|
|
|
self.log.info("Prepare wallets")
|
|
self.generate(self.nodes[0], 101)
|
|
wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
|
|
|
|
self.log.info("Sending a transaction should result in all tracepoints")
|
|
# We should have 5 tracepoints in the order:
|
|
# 1. selected_coins (type 1)
|
|
# 2. normal_create_tx_internal (type 2)
|
|
# 3. attempting_aps_create_tx (type 3)
|
|
# 4. selected_coins (type 1)
|
|
# 5. aps_create_tx_internal (type 4)
|
|
wallet.sendtoaddress(wallet.getnewaddress(), 10)
|
|
events = self.get_tracepoints([1, 2, 3, 1, 4])
|
|
success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
|
|
assert_equal(success, True)
|
|
assert_greater_than(change_pos, -1)
|
|
|
|
self.log.info("Failing to fund results in 1 tracepoint")
|
|
# We should have 1 tracepoints in the order
|
|
# 1. normal_create_tx_internal (type 2)
|
|
assert_raises_rpc_error(-6, "Insufficient funds", wallet.sendtoaddress, wallet.getnewaddress(), 102 * 50)
|
|
events = self.get_tracepoints([2])
|
|
success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
|
|
assert_equal(success, False)
|
|
|
|
self.log.info("Explicitly enabling APS results in 2 tracepoints")
|
|
# We should have 2 tracepoints in the order
|
|
# 1. selected_coins (type 1)
|
|
# 2. normal_create_tx_internal (type 2)
|
|
wallet.setwalletflag("avoid_reuse")
|
|
wallet.sendtoaddress(address=wallet.getnewaddress(), amount=10, avoid_reuse=True)
|
|
events = self.get_tracepoints([1, 2])
|
|
success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
|
|
assert_equal(success, True)
|
|
assert_equal(use_aps, None)
|
|
|
|
self.log.info("Change position is -1 if no change is created with APS when APS was initially not used")
|
|
# We should have 2 tracepoints in the order:
|
|
# 1. selected_coins (type 1)
|
|
# 2. normal_create_tx_internal (type 2)
|
|
# 3. attempting_aps_create_tx (type 3)
|
|
# 4. selected_coins (type 1)
|
|
# 5. aps_create_tx_internal (type 4)
|
|
wallet.sendtoaddress(address=wallet.getnewaddress(), amount=wallet.getbalance(), subtractfeefromamount=True, avoid_reuse=False)
|
|
events = self.get_tracepoints([1, 2, 3, 1, 4])
|
|
success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
|
|
assert_equal(success, True)
|
|
assert_equal(change_pos, -1)
|
|
|
|
self.log.info("Change position is -1 if no change is created normally and APS is not used")
|
|
# We should have 2 tracepoints in the order:
|
|
# 1. selected_coins (type 1)
|
|
# 2. normal_create_tx_internal (type 2)
|
|
wallet.sendtoaddress(address=wallet.getnewaddress(), amount=wallet.getbalance(), subtractfeefromamount=True)
|
|
events = self.get_tracepoints([1, 2])
|
|
success, use_aps, _algo, _waste, change_pos = self.determine_selection_from_usdt(events)
|
|
assert_equal(success, True)
|
|
assert_equal(change_pos, -1)
|
|
|
|
self.bpf.cleanup()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
CoinSelectionTracepointTest(__file__).main()
|