aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build_msvc/test_bitcoin/test_bitcoin.vcxproj1
-rw-r--r--contrib/tracing/README.md241
-rwxr-xr-xcontrib/tracing/connectblock_benchmark.bt150
-rwxr-xr-xcontrib/tracing/log_p2p_traffic.bt28
-rwxr-xr-xcontrib/tracing/log_raw_p2p_msgs.py180
-rwxr-xr-xcontrib/tracing/p2p_monitor.py250
-rwxr-xr-xcontrib/verifybinaries/verify.py2
-rw-r--r--doc/build-unix.md9
-rw-r--r--doc/dependencies.md2
-rw-r--r--doc/tracing.md266
-rw-r--r--src/Makefile.test.include3
-rw-r--r--src/index/coinstatsindex.cpp187
-rw-r--r--src/index/coinstatsindex.h16
-rw-r--r--src/logging.cpp26
-rw-r--r--src/logging.h4
-rw-r--r--src/net.cpp12
-rw-r--r--src/net_processing.cpp94
-rw-r--r--src/node/coinstats.h26
-rw-r--r--src/node/transaction.cpp38
-rw-r--r--src/node/transaction.h20
-rw-r--r--src/rpc/blockchain.cpp47
-rw-r--r--src/rpc/rawtransaction.cpp4
-rw-r--r--src/script/interpreter.h4
-rw-r--r--src/test/fuzz/banman.cpp4
-rw-r--r--src/test/fuzz/prevector.cpp6
-rw-r--r--src/test/fuzz/rolling_bloom_filter.cpp17
-rw-r--r--src/test/fuzz/tx_pool.cpp12
-rw-r--r--src/test/txvalidationcache_tests.cpp11
-rw-r--r--src/txorphanage.h7
-rw-r--r--src/validation.cpp44
-rw-r--r--src/validation.h15
-rw-r--r--src/wallet/test/spend_tests.cpp61
-rw-r--r--src/wallet/test/util.cpp38
-rw-r--r--src/wallet/test/util.h19
-rw-r--r--src/wallet/test/wallet_tests.cpp16
-rwxr-xr-xtest/functional/feature_cltv.py3
-rwxr-xr-xtest/functional/feature_coinstatsindex.py14
-rwxr-xr-xtest/functional/feature_csv_activation.py2
-rwxr-xr-xtest/functional/mempool_reorg.py2
-rwxr-xr-xtest/functional/p2p_permissions.py2
-rwxr-xr-xtest/functional/rpc_misc.py16
-rwxr-xr-xtest/functional/rpc_signrawtransaction.py15
-rw-r--r--test/functional/test_framework/blocktools.py4
-rw-r--r--test/functional/test_framework/util.py11
-rwxr-xr-xtest/functional/test_runner.py4
-rwxr-xr-xtest/functional/wallet_listtransactions.py35
-rwxr-xr-xtest/get_previous_releases.py6
-rwxr-xr-xtest/lint/lint-circular-dependencies.sh1
48 files changed, 1671 insertions, 304 deletions
diff --git a/build_msvc/test_bitcoin/test_bitcoin.vcxproj b/build_msvc/test_bitcoin/test_bitcoin.vcxproj
index 5c4b777d51..bb1a780bfa 100644
--- a/build_msvc/test_bitcoin/test_bitcoin.vcxproj
+++ b/build_msvc/test_bitcoin/test_bitcoin.vcxproj
@@ -16,6 +16,7 @@
<ClCompile Include="..\..\src\test\util\*.cpp" />
<ClCompile Include="..\..\src\wallet\test\*_fixture.cpp" />
<ClCompile Include="..\..\src\wallet\test\*_tests.cpp" />
+ <ClCompile Include="..\..\src\wallet\test\util.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\libbitcoinconsensus\libbitcoinconsensus.vcxproj">
diff --git a/contrib/tracing/README.md b/contrib/tracing/README.md
new file mode 100644
index 0000000000..047354cda1
--- /dev/null
+++ b/contrib/tracing/README.md
@@ -0,0 +1,241 @@
+Example scripts for User-space, Statically Defined Tracing (USDT)
+=================================================================
+
+This directory contains scripts showcasing User-space, Statically Defined
+Tracing (USDT) support for Bitcoin Core on Linux using. For more information on
+USDT support in Bitcoin Core see the [USDT documentation].
+
+[USDT documentation]: ../../doc/tracing.md
+
+
+Examples for the two main eBPF front-ends, [bpftrace] and
+[BPF Compiler Collection (BCC)], with support for USDT, are listed. BCC is used
+for complex tools and daemons and `bpftrace` is preferred for one-liners and
+shorter scripts.
+
+[bpftrace]: https://github.com/iovisor/bpftrace
+[BPF Compiler Collection (BCC)]: https://github.com/iovisor/bcc
+
+
+To develop and run bpftrace and BCC scripts you need to install the
+corresponding packages. See [installing bpftrace] and [installing BCC] for more
+information. For development there exist a [bpftrace Reference Guide], a
+[BCC Reference Guide], and a [bcc Python Developer Tutorial].
+
+[installing bpftrace]: https://github.com/iovisor/bpftrace/blob/master/INSTALL.md
+[installing BCC]: https://github.com/iovisor/bcc/blob/master/INSTALL.md
+[bpftrace Reference Guide]: https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md
+[BCC Reference Guide]: https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md
+[bcc Python Developer Tutorial]: https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md
+
+## Examples
+
+The bpftrace examples contain a relative path to the `bitcoind` binary. By
+default, the scripts should be run from the repository-root and assume a
+self-compiled `bitcoind` binary. The paths in the examples can be changed, for
+example, to point to release builds if needed. See the
+[Bitcoin Core USDT documentation] on how to list available tracepoints in your
+`bitcoind` binary.
+
+[Bitcoin Core USDT documentation]: ../../doc/tracing.md#listing-available-tracepoints
+
+**WARNING: eBPF programs require root privileges to be loaded into a Linux
+kernel VM. This means the bpftrace and BCC examples must be executed with root
+privileges. Make sure to carefully review any scripts that you run with root
+privileges first!**
+
+### log_p2p_traffic.bt
+
+A bpftrace script logging information about inbound and outbound P2P network
+messages. Based on the `net:inbound_message` and `net:outbound_message`
+tracepoints.
+
+By default, `bpftrace` limits strings to 64 bytes due to the limited stack size
+in the eBPF VM. For example, Tor v3 addresses exceed the string size limit which
+results in the port being cut off during logging. The string size limit can be
+increased with the `BPFTRACE_STRLEN` environment variable (`BPFTRACE_STRLEN=70`
+works fine).
+
+```
+$ bpftrace contrib/tracing/log_p2p_traffic.bt
+```
+
+Output
+```
+outbound 'ping' msg to peer 11 (outbound-full-relay, [2a02:b10c:f747:1:ef:fake:ipv6:addr]:8333) with 8 bytes
+inbound 'pong' msg from peer 11 (outbound-full-relay, [2a02:b10c:f747:1:ef:fake:ipv6:addr]:8333) with 8 bytes
+inbound 'inv' msg from peer 16 (outbound-full-relay, XX.XX.XXX.121:8333) with 37 bytes
+outbound 'getdata' msg to peer 16 (outbound-full-relay, XX.XX.XXX.121:8333) with 37 bytes
+inbound 'tx' msg from peer 16 (outbound-full-relay, XX.XX.XXX.121:8333) with 222 bytes
+outbound 'inv' msg to peer 9 (outbound-full-relay, faketorv3addressa2ufa6odvoi3s77j4uegey0xb10csyfyve2t33curbyd.onion:8333) with 37 bytes
+outbound 'inv' msg to peer 7 (outbound-full-relay, XX.XX.XXX.242:8333) with 37 bytes
+…
+```
+
+### p2p_monitor.py
+
+A BCC Python script using curses for an interactive P2P message monitor. Based
+on the `net:inbound_message` and `net:outbound_message` tracepoints.
+
+Inbound and outbound traffic is listed for each peer together with information
+about the connection. Peers can be selected individually to view recent P2P
+messages.
+
+```
+$ python3 contrib/tracing/p2p_monitor.py ./src/bitcoind
+```
+
+Lists selectable peers and traffic and connection information.
+```
+ P2P Message Monitor
+ Navigate with UP/DOWN or J/K and select a peer with ENTER or SPACE to see individual P2P messages
+
+ PEER OUTBOUND INBOUND TYPE ADDR
+ 0 46 398 byte 61 1407590 byte block-relay-only XX.XX.XXX.196:8333
+ 11 1156 253570 byte 3431 2394924 byte outbound-full-relay XXX.X.XX.179:8333
+ 13 3425 1809620 byte 1236 305458 byte inbound XXX.X.X.X:60380
+ 16 1046 241633 byte 1589 1199220 byte outbound-full-relay 4faketorv2pbfu7x.onion:8333
+ 19 577 181679 byte 390 148951 byte outbound-full-relay kfake4vctorjv2o2.onion:8333
+ 20 11 1248 byte 13 1283 byte block-relay-only [2600:fake:64d9:b10c:4436:aaaa:fe:bb]:8333
+ 21 11 1248 byte 13 1299 byte block-relay-only XX.XXX.X.155:8333
+ 22 5 103 byte 1 102 byte feeler XX.XX.XXX.173:8333
+ 23 11 1248 byte 12 1255 byte block-relay-only XX.XXX.XXX.220:8333
+ 24 3 103 byte 1 102 byte feeler XXX.XXX.XXX.64:8333
+…
+```
+
+Showing recent P2P messages between our node and a selected peer.
+
+```
+ ----------------------------------------------------------------------
+ | PEER 16 (4faketorv2pbfu7x.onion:8333) |
+ | OUR NODE outbound-full-relay PEER |
+ | <--- sendcmpct (9 bytes) |
+ | inv (37 byte) ---> |
+ | <--- ping (8 bytes) |
+ | pong (8 byte) ---> |
+ | inv (37 byte) ---> |
+ | <--- addr (31 bytes) |
+ | inv (37 byte) ---> |
+ | <--- getheaders (1029 bytes) |
+ | headers (1 byte) ---> |
+ | <--- feefilter (8 bytes) |
+ | <--- pong (8 bytes) |
+ | <--- headers (82 bytes) |
+ | <--- addr (30003 bytes) |
+ | inv (1261 byte) ---> |
+ | … |
+
+```
+
+### log_raw_p2p_msgs.py
+
+A BCC Python script showcasing eBPF and USDT limitations when passing data
+larger than about 32kb. Based on the `net:inbound_message` and
+`net:outbound_message` tracepoints.
+
+Bitcoin P2P messages can be larger than 32kb (e.g. `tx`, `block`, ...). The
+eBPF VM's stack is limited to 512 bytes, and we can't allocate more than about
+32kb for a P2P message in the eBPF VM. The **message data is cut off** when the
+message is larger than MAX_MSG_DATA_LENGTH (see script). This can be detected
+in user-space by comparing the data length to the message length variable. The
+message is cut off when the data length is smaller than the message length.
+A warning is included with the printed message data.
+
+Data is submitted to user-space (i.e. to this script) via a ring buffer. The
+throughput of the ring buffer is limited. Each p2p_message is about 32kb in
+size. In- or outbound messages submitted to the ring buffer in rapid
+succession fill the ring buffer faster than it can be read. Some messages are
+lost. BCC prints: `Possibly lost 2 samples` on lost messages.
+
+
+```
+$ python3 contrib/tracing/log_raw_p2p_msgs.py ./src/bitcoind
+```
+
+```
+Logging raw P2P messages.
+Messages larger that about 32kb will be cut off!
+Some messages might be lost!
+ outbound msg 'inv' from peer 4 (outbound-full-relay, XX.XXX.XX.4:8333) with 253 bytes: 0705000000be2245c8f844c9f763748e1a7…
+…
+Warning: incomplete message (only 32568 out of 53552 bytes)! inbound msg 'tx' from peer 32 (outbound-full-relay, XX.XXX.XXX.43:8333) with 53552 bytes: 020000000001fd3c01939c85ad6756ed9fc…
+…
+Possibly lost 2 samples
+```
+
+### connectblock_benchmark.bt
+
+A `bpftrace` script to benchmark the `ConnectBlock()` function during, for
+example, a blockchain re-index. Based on the `validation:block_connected` USDT
+tracepoint.
+
+The script takes three positional arguments. The first two arguments, the start,
+and end height indicate between which blocks the benchmark should be run. The
+third acts as a duration threshold in milliseconds. When the `ConnectBlock()`
+function takes longer than the threshold, information about the block, is
+printed. For more details, see the header comment in the script.
+
+By default, `bpftrace` limits strings to 64 bytes due to the limited stack size
+in the kernel VM. Block hashes as zero-terminated hex strings are 65 bytes which
+exceed the string limit. The string size limit can be set to 65 bytes with the
+environment variable `BPFTRACE_STRLEN`.
+
+The following command can be used to benchmark, for example, `ConnectBlock()`
+between height 20000 and 38000 on SigNet while logging all blocks that take
+longer than 25ms to connect.
+
+```
+$ BPFTRACE_STRLEN=65 bpftrace contrib/tracing/connectblock_benchmark.bt 20000 38000 25
+```
+
+In a different terminal, starting Bitcoin Core in SigNet mode and with
+re-indexing enabled.
+
+```
+$ ./src/bitcoind -signet -reindex
+```
+
+This produces the following output.
+```
+Attaching 5 probes...
+ConnectBlock Benchmark between height 20000 and 38000 inclusive
+Logging blocks taking longer than 25 ms to connect.
+Starting Connect Block Benchmark between height 20000 and 38000.
+BENCH 39 blk/s 59 tx/s 59 inputs/s 20 sigops/s (height 20038)
+Block 20492 (000000f555653bb05e2f3c6e79925e01a20dd57033f4dc7c354b46e34735d32b) 20 tx 2319 ins 2318 sigops took 38 ms
+BENCH 1840 blk/s 2117 tx/s 4478 inputs/s 2471 sigops/s (height 21879)
+BENCH 1816 blk/s 4972 tx/s 4982 inputs/s 125 sigops/s (height 23695)
+BENCH 2095 blk/s 2890 tx/s 2910 inputs/s 152 sigops/s (height 25790)
+BENCH 1684 blk/s 3979 tx/s 4053 inputs/s 288 sigops/s (height 27474)
+BENCH 1155 blk/s 3216 tx/s 3252 inputs/s 115 sigops/s (height 28629)
+BENCH 1797 blk/s 2488 tx/s 2503 inputs/s 111 sigops/s (height 30426)
+BENCH 1849 blk/s 6318 tx/s 6569 inputs/s 12189 sigops/s (height 32275)
+BENCH 946 blk/s 20209 tx/s 20775 inputs/s 83809 sigops/s (height 33221)
+Block 33406 (0000002adfe4a15cfcd53bd890a89bbae836e5bb7f38bac566f61ad4548c87f6) 25 tx 2045 ins 2090 sigops took 29 ms
+Block 33687 (00000073231307a9828e5607ceb8156b402efe56747271a4442e75eb5b77cd36) 52 tx 1797 ins 1826 sigops took 26 ms
+BENCH 582 blk/s 21581 tx/s 27673 inputs/s 60345 sigops/s (height 33803)
+BENCH 1035 blk/s 19735 tx/s 19776 inputs/s 51355 sigops/s (height 34838)
+Block 35625 (0000006b00b347390c4768ea9df2655e9ff4b120f29d78594a2a702f8a02c997) 20 tx 3374 ins 3371 sigops took 49 ms
+BENCH 887 blk/s 17857 tx/s 22191 inputs/s 24404 sigops/s (height 35725)
+Block 35937 (000000d816d13d6e39b471cd4368db60463a764ba1f29168606b04a22b81ea57) 75 tx 3943 ins 3940 sigops took 61 ms
+BENCH 823 blk/s 16298 tx/s 21031 inputs/s 18440 sigops/s (height 36548)
+Block 36583 (000000c3e260556dbf42968aae3f904dba8b8c1ff96a6f6e3aa5365d2e3ad317) 24 tx 2198 ins 2194 sigops took 34 ms
+Block 36700 (000000b3b173de9e65a3cfa738d976af6347aaf83fa17ab3f2a4d2ede3ddfac4) 73 tx 1615 ins 1611 sigops took 31 ms
+Block 36832 (0000007859578c02c1ac37dabd1b9ec19b98f350b56935f5dd3a41e9f79f836e) 34 tx 1440 ins 1436 sigops took 26 ms
+BENCH 613 blk/s 16718 tx/s 25074 inputs/s 23022 sigops/s (height 37161)
+Block 37870 (000000f5c1086291ba2d943fb0c3bc82e71c5ee341ee117681d1456fbf6c6c38) 25 tx 1517 ins 1514 sigops took 29 ms
+BENCH 811 blk/s 16031 tx/s 20921 inputs/s 18696 sigops/s (height 37972)
+
+Took 14055 ms to connect the blocks between height 20000 and 38000.
+
+Histogram of block connection times in milliseconds (ms).
+@durations:
+[0] 16838 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
+[1] 882 |@@ |
+[2, 4) 236 | |
+[4, 8) 23 | |
+[8, 16) 9 | |
+[16, 32) 9 | |
+[32, 64) 4 | |
+```
diff --git a/contrib/tracing/connectblock_benchmark.bt b/contrib/tracing/connectblock_benchmark.bt
new file mode 100755
index 0000000000..d268eff7f8
--- /dev/null
+++ b/contrib/tracing/connectblock_benchmark.bt
@@ -0,0 +1,150 @@
+#!/usr/bin/env bpftrace
+
+/*
+
+ USAGE:
+
+ BPFTRACE_STRLEN=65 bpftrace contrib/tracing/connectblock_benchmark.bt <start height> <end height> <logging threshold in ms>
+
+ - The environment variable BPFTRACE_STRLEN needs to be set to 65 chars as
+ strings are limited to 64 chars by default. Hex strings with Bitcoin block
+ hashes are 64 hex chars + 1 null-termination char.
+ - <start height> sets the height at which the benchmark should start. Setting
+ the start height to 0 starts the benchmark immediately, even before the
+ first block is connected.
+ - <end height> sets the height after which the benchmark should end. Setting
+ the end height to 0 disables the benchmark. The script only logs blocks
+ over <logging threshold in ms>.
+ - Threshold <logging threshold in ms>
+
+ This script requires a 'bitcoind' binary compiled with eBPF support and the
+ 'validation:block_connected' USDT. By default, it's assumed that 'bitcoind' is
+ located in './src/bitcoind'. This can be modified in the script below.
+
+ EXAMPLES:
+
+ BPFTRACE_STRLEN=65 bpftrace contrib/tracing/connectblock_benchmark.bt 300000 680000 1000
+
+ When run together 'bitcoind -reindex', this benchmarks the time it takes to
+ connect the blocks between height 300.000 and 680.000 (inclusive) and prints
+ details about all blocks that take longer than 1000ms to connect. Prints a
+ histogram with block connection times when the benchmark is finished.
+
+
+ BPFTRACE_STRLEN=65 bpftrace contrib/tracing/connectblock_benchmark.bt 0 0 500
+
+ When running together 'bitcoind', all newly connected blocks that
+ take longer than 500ms to connect are logged. A histogram with block
+ connection times is shown when the script is terminated.
+
+*/
+
+BEGIN
+{
+ $start_height = $1;
+ $end_height = $2;
+ $logging_threshold_ms = $3;
+
+ if ($end_height < $start_height) {
+ printf("Error: start height (%d) larger than end height (%d)!\n", $start_height, $end_height);
+ exit();
+ }
+
+ if ($end_height > 0) {
+ printf("ConnectBlock benchmark between height %d and %d inclusive\n", $start_height, $end_height);
+ } else {
+ printf("ConnectBlock logging starting at height %d\n", $start_height);
+ }
+
+ if ($logging_threshold_ms > 0) {
+ printf("Logging blocks taking longer than %d ms to connect.\n", $3);
+ }
+
+ if ($start_height == 0) {
+ @start = nsecs;
+ }
+}
+
+/*
+ Attaches to the 'validation:block_connected' USDT and collects stats when the
+ connected block is between the start and end height (or the end height is
+ unset).
+*/
+usdt:./src/bitcoind:validation:block_connected /arg1 >= $1 && (arg1 <= $2 || $2 == 0 )/
+{
+ $height = arg1;
+ $transactions = arg2;
+ $inputs = arg3;
+ $sigops = arg4;
+ $duration = (uint64) arg5;
+
+ @height = $height;
+
+ @blocks = @blocks + 1;
+ @transactions = @transactions + $transactions;
+ @inputs = @inputs + $inputs;
+ @sigops = @sigops + $sigops;
+
+ @durations = hist($duration / 1000);
+
+ if ($height == $1 && $height != 0) {
+ @start = nsecs;
+ printf("Starting Connect Block Benchmark between height %d and %d.\n", $1, $2);
+ }
+
+ if ($2 > 0 && $height >= $2) {
+ @end = nsecs;
+ $duration = @end - @start;
+ printf("\nTook %d ms to connect the blocks between height %d and %d.\n", $duration / 1000000, $1, $2);
+ exit();
+ }
+}
+
+/*
+ Attaches to the 'validation:block_connected' USDT and logs information about
+ blocks where the time it took to connect the block is above the
+ <logging threshold in ms>.
+*/
+usdt:./src/bitcoind:validation:block_connected / (uint64) arg5 / 1000> $3 /
+{
+ $hash_str = str(arg0);
+ $height = (int32) arg1;
+ $transactions = (uint64) arg2;
+ $inputs = (int32) arg3;
+ $sigops = (int64) arg4;
+ $duration = (int64) arg5;
+
+ printf("Block %d (%s) %4d tx %5d ins %5d sigops took %4d ms\n", $height, $hash_str, $transactions, $inputs, $sigops, (uint64) $duration / 1000);
+}
+
+
+/*
+ Prints stats about the blocks, transactions, inputs, and sigops processed in
+ the last second (if any).
+*/
+interval:s:1 {
+ if (@blocks > 0) {
+ printf("BENCH %4d blk/s %6d tx/s %7d inputs/s %8d sigops/s (height %d)\n", @blocks, @transactions, @inputs, @sigops, @height);
+
+ zero(@blocks);
+ zero(@transactions);
+ zero(@inputs);
+ zero(@sigops);
+ }
+}
+
+END
+{
+ printf("\nHistogram of block connection times in milliseconds (ms).\n");
+ print(@durations);
+
+ clear(@durations);
+ clear(@blocks);
+ clear(@transactions);
+ clear(@inputs);
+ clear(@sigops);
+ clear(@height);
+ clear(@start);
+ clear(@end);
+}
+
diff --git a/contrib/tracing/log_p2p_traffic.bt b/contrib/tracing/log_p2p_traffic.bt
new file mode 100755
index 0000000000..f62956aa5e
--- /dev/null
+++ b/contrib/tracing/log_p2p_traffic.bt
@@ -0,0 +1,28 @@
+#!/usr/bin/env bpftrace
+
+BEGIN
+{
+ printf("Logging P2P traffic\n")
+}
+
+usdt:./src/bitcoind:net:inbound_message
+{
+ $peer_id = (int64) arg0;
+ $peer_addr = str(arg1);
+ $peer_type = str(arg2);
+ $msg_type = str(arg3);
+ $msg_len = arg4;
+ printf("inbound '%s' msg from peer %d (%s, %s) with %d bytes\n", $msg_type, $peer_id, $peer_type, $peer_addr, $msg_len);
+}
+
+usdt:./src/bitcoind:net:outbound_message
+{
+ $peer_id = (int64) arg0;
+ $peer_addr = str(arg1);
+ $peer_type = str(arg2);
+ $msg_type = str(arg3);
+ $msg_len = arg4;
+
+ printf("outbound '%s' msg to peer %d (%s, %s) with %d bytes\n", $msg_type, $peer_id, $peer_type, $peer_addr, $msg_len);
+}
+
diff --git a/contrib/tracing/log_raw_p2p_msgs.py b/contrib/tracing/log_raw_p2p_msgs.py
new file mode 100755
index 0000000000..b5b5755632
--- /dev/null
+++ b/contrib/tracing/log_raw_p2p_msgs.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python3
+
+""" Demonstration of eBPF limitations and the effect on USDT with the
+ net:inbound_message and net:outbound_message tracepoints. """
+
+# This script shows a limitation of eBPF when data larger than 32kb is passed to
+# user-space. It uses BCC (https://github.com/iovisor/bcc) to load a sandboxed
+# eBPF program into the Linux kernel (root privileges are required). The eBPF
+# program attaches to two statically defined tracepoints. The tracepoint
+# 'net:inbound_message' is called when a new P2P message is received, and
+# 'net:outbound_message' is called on outbound P2P messages. The eBPF program
+# submits the P2P messages to this script via a BPF ring buffer. The submitted
+# messages are printed.
+
+# eBPF Limitations:
+#
+# Bitcoin P2P messages can be larger than 32kb (e.g. tx, block, ...). The eBPF
+# VM's stack is limited to 512 bytes, and we can't allocate more than about 32kb
+# for a P2P message in the eBPF VM. The message data is cut off when the message
+# is larger than MAX_MSG_DATA_LENGTH (see definition below). This can be detected
+# in user-space by comparing the data length to the message length variable. The
+# message is cut off when the data length is smaller than the message length.
+# A warning is included with the printed message data.
+#
+# Data is submitted to user-space (i.e. to this script) via a ring buffer. The
+# throughput of the ring buffer is limited. Each p2p_message is about 32kb in
+# size. In- or outbound messages submitted to the ring buffer in rapid
+# succession fill the ring buffer faster than it can be read. Some messages are
+# lost.
+#
+# BCC prints: "Possibly lost 2 samples" on lost messages.
+
+import sys
+from bcc import BPF, USDT
+
+# BCC: The C program to be compiled to an eBPF program (by BCC) and loaded into
+# a sandboxed Linux kernel VM.
+program = """
+#include <uapi/linux/ptrace.h>
+
+#define MIN(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; })
+
+// Maximum possible allocation size
+// from include/linux/percpu.h in the Linux kernel
+#define PCPU_MIN_UNIT_SIZE (32 << 10)
+
+// Tor v3 addresses are 62 chars + 6 chars for the port (':12345').
+#define MAX_PEER_ADDR_LENGTH 62 + 6
+#define MAX_PEER_CONN_TYPE_LENGTH 20
+#define MAX_MSG_TYPE_LENGTH 20
+#define MAX_MSG_DATA_LENGTH PCPU_MIN_UNIT_SIZE - 200
+
+struct p2p_message
+{
+ u64 peer_id;
+ char peer_addr[MAX_PEER_ADDR_LENGTH];
+ char peer_conn_type[MAX_PEER_CONN_TYPE_LENGTH];
+ char msg_type[MAX_MSG_TYPE_LENGTH];
+ u64 msg_size;
+ u8 msg[MAX_MSG_DATA_LENGTH];
+};
+
+// We can't store the p2p_message struct on the eBPF stack as it is limited to
+// 512 bytes and P2P message can be bigger than 512 bytes. However, we can use
+// an BPF-array with a length of 1 to allocate up to 32768 bytes (this is
+// defined by PCPU_MIN_UNIT_SIZE in include/linux/percpu.h in the Linux kernel).
+// Also see https://github.com/iovisor/bcc/issues/2306
+BPF_ARRAY(msg_arr, struct p2p_message, 1);
+
+// Two BPF perf buffers for pushing data (here P2P messages) to user-space.
+BPF_PERF_OUTPUT(inbound_messages);
+BPF_PERF_OUTPUT(outbound_messages);
+
+int trace_inbound_message(struct pt_regs *ctx) {
+ int idx = 0;
+ struct p2p_message *msg = msg_arr.lookup(&idx);
+
+ // lookup() does not return a NULL pointer. However, the BPF verifier
+ // requires an explicit check that that the `msg` pointer isn't a NULL
+ // pointer. See https://github.com/iovisor/bcc/issues/2595
+ if (msg == NULL) return 1;
+
+ bpf_usdt_readarg(1, ctx, &msg->peer_id);
+ bpf_usdt_readarg_p(2, ctx, &msg->peer_addr, MAX_PEER_ADDR_LENGTH);
+ bpf_usdt_readarg_p(3, ctx, &msg->peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
+ bpf_usdt_readarg_p(4, ctx, &msg->msg_type, MAX_MSG_TYPE_LENGTH);
+ bpf_usdt_readarg(5, ctx, &msg->msg_size);
+ bpf_usdt_readarg_p(6, ctx, &msg->msg, MIN(msg->msg_size, MAX_MSG_DATA_LENGTH));
+
+ inbound_messages.perf_submit(ctx, msg, sizeof(*msg));
+ return 0;
+};
+
+int trace_outbound_message(struct pt_regs *ctx) {
+ int idx = 0;
+ struct p2p_message *msg = msg_arr.lookup(&idx);
+
+ // lookup() does not return a NULL pointer. However, the BPF verifier
+ // requires an explicit check that that the `msg` pointer isn't a NULL
+ // pointer. See https://github.com/iovisor/bcc/issues/2595
+ if (msg == NULL) return 1;
+
+ bpf_usdt_readarg(1, ctx, &msg->peer_id);
+ bpf_usdt_readarg_p(2, ctx, &msg->peer_addr, MAX_PEER_ADDR_LENGTH);
+ bpf_usdt_readarg_p(3, ctx, &msg->peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
+ bpf_usdt_readarg_p(4, ctx, &msg->msg_type, MAX_MSG_TYPE_LENGTH);
+ bpf_usdt_readarg(5, ctx, &msg->msg_size);
+ bpf_usdt_readarg_p(6, ctx, &msg->msg, MIN(msg->msg_size, MAX_MSG_DATA_LENGTH));
+
+ outbound_messages.perf_submit(ctx, msg, sizeof(*msg));
+ return 0;
+};
+"""
+
+
+def print_message(event, inbound):
+ print(f"%s %s msg '%s' from peer %d (%s, %s) with %d bytes: %s" %
+ (
+ f"Warning: incomplete message (only %d out of %d bytes)!" % (
+ len(event.msg), event.msg_size) if len(event.msg) < event.msg_size else "",
+ "inbound" if inbound else "outbound",
+ event.msg_type.decode("utf-8"),
+ event.peer_id,
+ event.peer_conn_type.decode("utf-8"),
+ event.peer_addr.decode("utf-8"),
+ event.msg_size,
+ bytes(event.msg[:event.msg_size]).hex(),
+ )
+ )
+
+
+def main(bitcoind_path):
+ bitcoind_with_usdts = USDT(path=str(bitcoind_path))
+
+ # attaching the trace functions defined in the BPF program to the tracepoints
+ bitcoind_with_usdts.enable_probe(
+ probe="inbound_message", fn_name="trace_inbound_message")
+ bitcoind_with_usdts.enable_probe(
+ probe="outbound_message", fn_name="trace_outbound_message")
+ bpf = BPF(text=program, usdt_contexts=[bitcoind_with_usdts])
+
+ # BCC: perf buffer handle function for inbound_messages
+ def handle_inbound(_, data, size):
+ """ Inbound message handler.
+
+ Called each time a message is submitted to the inbound_messages BPF table."""
+
+ event = bpf["inbound_messages"].event(data)
+ print_message(event, True)
+
+ # BCC: perf buffer handle function for outbound_messages
+
+ def handle_outbound(_, data, size):
+ """ Outbound message handler.
+
+ Called each time a message is submitted to the outbound_messages BPF table."""
+
+ event = bpf["outbound_messages"].event(data)
+ print_message(event, False)
+
+ # BCC: add handlers to the inbound and outbound perf buffers
+ bpf["inbound_messages"].open_perf_buffer(handle_inbound)
+ bpf["outbound_messages"].open_perf_buffer(handle_outbound)
+
+ print("Logging raw P2P messages.")
+ print("Messages larger that about 32kb will be cut off!")
+ print("Some messages might be lost!")
+ while True:
+ try:
+ bpf.perf_buffer_poll()
+ except KeyboardInterrupt:
+ exit()
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ print("USAGE:", sys.argv[0], "path/to/bitcoind")
+ exit()
+ path = sys.argv[1]
+ main(path)
diff --git a/contrib/tracing/p2p_monitor.py b/contrib/tracing/p2p_monitor.py
new file mode 100755
index 0000000000..14e3e3a801
--- /dev/null
+++ b/contrib/tracing/p2p_monitor.py
@@ -0,0 +1,250 @@
+#!/usr/bin/env python3
+
+""" Interactive bitcoind P2P network traffic monitor utilizing USDT and the
+ net:inbound_message and net:outbound_message tracepoints. """
+
+# This script demonstrates what USDT for Bitcoin Core can enable. It uses BCC
+# (https://github.com/iovisor/bcc) to load a sandboxed eBPF program into the
+# Linux kernel (root privileges are required). The eBPF program attaches to two
+# statically defined tracepoints. The tracepoint 'net:inbound_message' is called
+# when a new P2P message is received, and 'net:outbound_message' is called on
+# outbound P2P messages. The eBPF program submits the P2P messages to
+# this script via a BPF ring buffer.
+
+import sys
+import curses
+from curses import wrapper, panel
+from bcc import BPF, USDT
+
+# BCC: The C program to be compiled to an eBPF program (by BCC) and loaded into
+# a sandboxed Linux kernel VM.
+program = """
+#include <uapi/linux/ptrace.h>
+
+// Tor v3 addresses are 62 chars + 6 chars for the port (':12345').
+// I2P addresses are 60 chars + 6 chars for the port (':12345').
+#define MAX_PEER_ADDR_LENGTH 62 + 6
+#define MAX_PEER_CONN_TYPE_LENGTH 20
+#define MAX_MSG_TYPE_LENGTH 20
+
+struct p2p_message
+{
+ u64 peer_id;
+ char peer_addr[MAX_PEER_ADDR_LENGTH];
+ char peer_conn_type[MAX_PEER_CONN_TYPE_LENGTH];
+ char msg_type[MAX_MSG_TYPE_LENGTH];
+ u64 msg_size;
+};
+
+
+// Two BPF perf buffers for pushing data (here P2P messages) to user space.
+BPF_PERF_OUTPUT(inbound_messages);
+BPF_PERF_OUTPUT(outbound_messages);
+
+int trace_inbound_message(struct pt_regs *ctx) {
+ struct p2p_message msg = {};
+
+ bpf_usdt_readarg(1, ctx, &msg.peer_id);
+ bpf_usdt_readarg_p(2, ctx, &msg.peer_addr, MAX_PEER_ADDR_LENGTH);
+ bpf_usdt_readarg_p(3, ctx, &msg.peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
+ bpf_usdt_readarg_p(4, ctx, &msg.msg_type, MAX_MSG_TYPE_LENGTH);
+ bpf_usdt_readarg(5, ctx, &msg.msg_size);
+
+ inbound_messages.perf_submit(ctx, &msg, sizeof(msg));
+ return 0;
+};
+
+int trace_outbound_message(struct pt_regs *ctx) {
+ struct p2p_message msg = {};
+
+ bpf_usdt_readarg(1, ctx, &msg.peer_id);
+ bpf_usdt_readarg_p(2, ctx, &msg.peer_addr, MAX_PEER_ADDR_LENGTH);
+ bpf_usdt_readarg_p(3, ctx, &msg.peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH);
+ bpf_usdt_readarg_p(4, ctx, &msg.msg_type, MAX_MSG_TYPE_LENGTH);
+ bpf_usdt_readarg(5, ctx, &msg.msg_size);
+
+ outbound_messages.perf_submit(ctx, &msg, sizeof(msg));
+ return 0;
+};
+"""
+
+
+class Message:
+ """ A P2P network message. """
+ msg_type = ""
+ size = 0
+ data = bytes()
+ inbound = False
+
+ def __init__(self, msg_type, size, inbound):
+ self.msg_type = msg_type
+ self.size = size
+ self.inbound = inbound
+
+
+class Peer:
+ """ A P2P network peer. """
+ id = 0
+ address = ""
+ connection_type = ""
+ last_messages = list()
+
+ total_inbound_msgs = 0
+ total_inbound_bytes = 0
+ total_outbound_msgs = 0
+ total_outbound_bytes = 0
+
+ def __init__(self, id, address, connection_type):
+ self.id = id
+ self.address = address
+ self.connection_type = connection_type
+ self.last_messages = list()
+
+ def add_message(self, message):
+ self.last_messages.append(message)
+ if len(self.last_messages) > 25:
+ self.last_messages.pop(0)
+ if message.inbound:
+ self.total_inbound_bytes += message.size
+ self.total_inbound_msgs += 1
+ else:
+ self.total_outbound_bytes += message.size
+ self.total_outbound_msgs += 1
+
+
+def main(bitcoind_path):
+ peers = dict()
+
+ bitcoind_with_usdts = USDT(path=str(bitcoind_path))
+
+ # attaching the trace functions defined in the BPF program to the tracepoints
+ bitcoind_with_usdts.enable_probe(
+ probe="inbound_message", fn_name="trace_inbound_message")
+ bitcoind_with_usdts.enable_probe(
+ probe="outbound_message", fn_name="trace_outbound_message")
+ bpf = BPF(text=program, usdt_contexts=[bitcoind_with_usdts])
+
+ # BCC: perf buffer handle function for inbound_messages
+ def handle_inbound(_, data, size):
+ """ Inbound message handler.
+
+ Called each time a message is submitted to the inbound_messages BPF table."""
+ event = bpf["inbound_messages"].event(data)
+ if event.peer_id not in peers:
+ peer = Peer(event.peer_id, event.peer_addr.decode(
+ "utf-8"), event.peer_conn_type.decode("utf-8"))
+ peers[peer.id] = peer
+ peers[event.peer_id].add_message(
+ Message(event.msg_type.decode("utf-8"), event.msg_size, True))
+
+ # BCC: perf buffer handle function for outbound_messages
+ def handle_outbound(_, data, size):
+ """ Outbound message handler.
+
+ Called each time a message is submitted to the outbound_messages BPF table."""
+ event = bpf["outbound_messages"].event(data)
+ if event.peer_id not in peers:
+ peer = Peer(event.peer_id, event.peer_addr.decode(
+ "utf-8"), event.peer_conn_type.decode("utf-8"))
+ peers[peer.id] = peer
+ peers[event.peer_id].add_message(
+ Message(event.msg_type.decode("utf-8"), event.msg_size, False))
+
+ # BCC: add handlers to the inbound and outbound perf buffers
+ bpf["inbound_messages"].open_perf_buffer(handle_inbound)
+ bpf["outbound_messages"].open_perf_buffer(handle_outbound)
+
+ wrapper(loop, bpf, peers)
+
+
+def loop(screen, bpf, peers):
+ screen.nodelay(1)
+ cur_list_pos = 0
+ win = curses.newwin(30, 70, 2, 7)
+ win.erase()
+ win.border(ord("|"), ord("|"), ord("-"), ord("-"),
+ ord("-"), ord("-"), ord("-"), ord("-"))
+ info_panel = panel.new_panel(win)
+ info_panel.hide()
+
+ ROWS_AVALIABLE_FOR_LIST = curses.LINES - 5
+ scroll = 0
+
+ while True:
+ try:
+ # BCC: poll the perf buffers for new events or timeout after 50ms
+ bpf.perf_buffer_poll(timeout=50)
+
+ ch = screen.getch()
+ if (ch == curses.KEY_DOWN or ch == ord("j")) and cur_list_pos < len(
+ peers.keys()) -1 and info_panel.hidden():
+ cur_list_pos += 1
+ if cur_list_pos >= ROWS_AVALIABLE_FOR_LIST:
+ scroll += 1
+ if (ch == curses.KEY_UP or ch == ord("k")) and cur_list_pos > 0 and info_panel.hidden():
+ cur_list_pos -= 1
+ if scroll > 0:
+ scroll -= 1
+ if ch == ord('\n') or ch == ord(' '):
+ if info_panel.hidden():
+ info_panel.show()
+ else:
+ info_panel.hide()
+ screen.erase()
+ render(screen, peers, cur_list_pos, scroll, ROWS_AVALIABLE_FOR_LIST, info_panel)
+ curses.panel.update_panels()
+ screen.refresh()
+ except KeyboardInterrupt:
+ exit()
+
+
+def render(screen, peers, cur_list_pos, scroll, ROWS_AVALIABLE_FOR_LIST, info_panel):
+ """ renders the list of peers and details panel
+
+ This code is unrelated to USDT, BCC and BPF.
+ """
+ header_format = "%6s %-20s %-20s %-22s %-67s"
+ row_format = "%6s %-5d %9d byte %-5d %9d byte %-22s %-67s"
+
+ screen.addstr(0, 1, (" P2P Message Monitor "), curses.A_REVERSE)
+ screen.addstr(
+ 1, 0, (" Navigate with UP/DOWN or J/K and select a peer with ENTER or SPACE to see individual P2P messages"), curses.A_NORMAL)
+ screen.addstr(3, 0,
+ header_format % ("PEER", "OUTBOUND", "INBOUND", "TYPE", "ADDR"), curses.A_BOLD | curses.A_UNDERLINE)
+ peer_list = sorted(peers.keys())[scroll:ROWS_AVALIABLE_FOR_LIST+scroll]
+ for i, peer_id in enumerate(peer_list):
+ peer = peers[peer_id]
+ screen.addstr(i + 4, 0,
+ row_format % (peer.id, peer.total_outbound_msgs, peer.total_outbound_bytes,
+ peer.total_inbound_msgs, peer.total_inbound_bytes,
+ peer.connection_type, peer.address),
+ curses.A_REVERSE if i + scroll == cur_list_pos else curses.A_NORMAL)
+ if i + scroll == cur_list_pos:
+ info_window = info_panel.window()
+ info_window.erase()
+ info_window.border(
+ ord("|"), ord("|"), ord("-"), ord("-"),
+ ord("-"), ord("-"), ord("-"), ord("-"))
+
+ info_window.addstr(
+ 1, 1, f"PEER {peer.id} ({peer.address})".center(68), curses.A_REVERSE | curses.A_BOLD)
+ info_window.addstr(
+ 2, 1, f" OUR NODE{peer.connection_type:^54}PEER ",
+ curses.A_BOLD)
+ for i, msg in enumerate(peer.last_messages):
+ if msg.inbound:
+ info_window.addstr(
+ i + 3, 1, "%68s" %
+ (f"<--- {msg.msg_type} ({msg.size} bytes) "), curses.A_NORMAL)
+ else:
+ info_window.addstr(
+ i + 3, 1, " %s (%d byte) --->" %
+ (msg.msg_type, msg.size), curses.A_NORMAL)
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ print("USAGE:", sys.argv[0], "path/to/bitcoind")
+ exit()
+ path = sys.argv[1]
+ main(path)
diff --git a/contrib/verifybinaries/verify.py b/contrib/verifybinaries/verify.py
index 6cbaf2dc0c..51c151add8 100755
--- a/contrib/verifybinaries/verify.py
+++ b/contrib/verifybinaries/verify.py
@@ -2,7 +2,7 @@
# Copyright (c) 2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-"""Script for verifying Bitoin Core release binaries
+"""Script for verifying Bitcoin Core release binaries
This script attempts to download the signature file SHA256SUMS.asc from
bitcoincore.org and bitcoin.org and compares them.
diff --git a/doc/build-unix.md b/doc/build-unix.md
index 73c0bf8779..4a56114109 100644
--- a/doc/build-unix.md
+++ b/doc/build-unix.md
@@ -48,6 +48,7 @@ Optional dependencies:
univalue | Utility | JSON parsing and encoding (bundled version will be used unless --with-system-univalue passed to configure)
libzmq3 | ZMQ notification | Optional, allows generating ZMQ notifications (requires ZMQ version >= 4.0.0)
sqlite3 | SQLite DB | Optional, wallet storage (only needed when wallet enabled)
+ systemtap | Tracing (USDT) | Optional, statically defined tracepoints
For the versions used, see [dependencies.md](dependencies.md)
@@ -107,6 +108,10 @@ ZMQ dependencies (provides ZMQ API):
sudo apt-get install libzmq3-dev
+User-Space, Statically Defined Tracing (USDT) dependencies:
+
+ sudo apt install systemtap-sdt-dev
+
GUI dependencies:
If you want to build bitcoin-qt, make sure that the required packages for Qt development
@@ -162,6 +167,10 @@ ZMQ dependencies (provides ZMQ API):
sudo dnf install zeromq-devel
+User-Space, Statically Defined Tracing (USDT) dependencies:
+
+ sudo dnf install systemtap
+
GUI dependencies:
If you want to build bitcoin-qt, make sure that the required packages for Qt development
diff --git a/doc/dependencies.md b/doc/dependencies.md
index 66c5a76b3b..b7634718e8 100644
--- a/doc/dependencies.md
+++ b/doc/dependencies.md
@@ -24,6 +24,7 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct
| Qt | [5.12.11](https://download.qt.io/official_releases/qt/) | [5.9.5](https://github.com/bitcoin/bitcoin/issues/20104) | No | | |
| SQLite | [3.32.1](https://sqlite.org/download.html) | [3.7.17](https://github.com/bitcoin/bitcoin/pull/19077) | | | |
| XCB | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Linux only) |
+| systemtap ([tracing](tracing.md))| | | | | |
| xkbcommon | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Linux only) |
| ZeroMQ | [4.3.1](https://github.com/zeromq/libzmq/releases) | 4.0.0 | No | | |
| zlib | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) |
@@ -41,6 +42,7 @@ Some dependencies are not needed in all configurations. The following are some f
* SQLite is not needed with `--disable-wallet` or `--without-sqlite`.
* Qt is not needed with `--without-gui`.
* If the qrencode dependency is absent, QR support won't be added. To force an error when that happens, pass `--with-qrencode`.
+* If the systemtap dependency is absent, USDT support won't compiled in.
* ZeroMQ is needed only with the `--with-zmq` option.
#### Other
diff --git a/doc/tracing.md b/doc/tracing.md
new file mode 100644
index 0000000000..1242a0d250
--- /dev/null
+++ b/doc/tracing.md
@@ -0,0 +1,266 @@
+# User-space, Statically Defined Tracing (USDT) for Bitcoin Core
+
+Bitcoin Core includes statically defined tracepoints to allow for more
+observability during development, debugging, code review, and production usage.
+These tracepoints make it possible to keep track of custom statistics and
+enable detailed monitoring of otherwise hidden internals. They have
+little to no performance impact when unused.
+
+```
+eBPF and USDT Overview
+======================
+
+ ┌──────────────────┐ ┌──────────────┐
+ │ tracing script │ │ bitcoind │
+ │==================│ 2. │==============│
+ │ eBPF │ tracing │ hooks │ │
+ │ code │ logic │ into┌─┤►tracepoint 1─┼───┐ 3.
+ └────┬───┴──▲──────┘ ├─┤►tracepoint 2 │ │ pass args
+ 1. │ │ 4. │ │ ... │ │ to eBPF
+ User compiles │ │ pass data to │ └──────────────┘ │ program
+ Space & loads │ │ tracing script │ │
+ ─────────────────┼──────┼─────────────────┼────────────────────┼───
+ Kernel │ │ │ │
+ Space ┌──┬─▼──────┴─────────────────┴────────────┐ │
+ │ │ eBPF program │◄──────┘
+ │ └───────────────────────────────────────┤
+ │ eBPF kernel Virtual Machine (sandboxed) │
+ └──────────────────────────────────────────┘
+
+1. The tracing script compiles the eBPF code and loads the eBPF program into a kernel VM
+2. The eBPF program hooks into one or more tracepoints
+3. When the tracepoint is called, the arguments are passed to the eBPF program
+4. The eBPF program processes the arguments and returns data to the tracing script
+```
+
+The Linux kernel can hook into the tracepoints during runtime and pass data to
+sandboxed [eBPF] programs running in the kernel. These eBPF programs can, for
+example, collect statistics or pass data back to user-space scripts for further
+processing.
+
+[eBPF]: https://ebpf.io/
+
+The two main eBPF front-ends with support for USDT are [bpftrace] and
+[BPF Compiler Collection (BCC)]. BCC is used for complex tools and daemons and
+`bpftrace` is preferred for one-liners and shorter scripts. Examples for both can
+be found in [contrib/tracing].
+
+[bpftrace]: https://github.com/iovisor/bpftrace
+[BPF Compiler Collection (BCC)]: https://github.com/iovisor/bcc
+[contrib/tracing]: ../contrib/tracing/
+
+## Tracepoint documentation
+
+The currently available tracepoints are listed here.
+
+### Context `net`
+
+#### Tracepoint `net:inbound_message`
+
+Is called when a message is received from a peer over the P2P network. Passes
+information about our peer, the connection and the message as arguments.
+
+Arguments passed:
+1. Peer ID as `int64`
+2. Peer Address and Port (IPv4, IPv6, Tor v3, I2P, ...) as `pointer to C-style String` (max. length 68 characters)
+3. Connection Type (inbound, feeler, outbound-full-relay, ...) as `pointer to C-style String` (max. length 20 characters)
+4. Message Type (inv, ping, getdata, addrv2, ...) as `pointer to C-style String` (max. length 20 characters)
+5. Message Size in bytes as `uint64`
+6. Message Bytes as `pointer to unsigned chars` (i.e. bytes)
+
+Note: The message is passed to the tracepoint in full, however, due to space
+limitations in the eBPF kernel VM it might not be possible to pass the message
+to user-space in full. Messages longer than a 32kb might be cut off. This can
+be detected in tracing scripts by comparing the message size to the length of
+the passed message.
+
+#### Tracepoint `net:outbound_message`
+
+Is called when a message is send to a peer over the P2P network. Passes
+information about our peer, the connection and the message as arguments.
+
+Arguments passed:
+1. Peer ID as `int64`
+2. Peer Address and Port (IPv4, IPv6, Tor v3, I2P, ...) as `pointer to C-style String` (max. length 68 characters)
+3. Connection Type (inbound, feeler, outbound-full-relay, ...) as `pointer to C-style String` (max. length 20 characters)
+4. Message Type (inv, ping, getdata, addrv2, ...) as `pointer to C-style String` (max. length 20 characters)
+5. Message Size in bytes as `uint64`
+6. Message Bytes as `pointer to unsigned chars` (i.e. bytes)
+
+Note: The message is passed to the tracepoint in full, however, due to space
+limitations in the eBPF kernel VM it might not be possible to pass the message
+to user-space in full. Messages longer than a 32kb might be cut off. This can
+be detected in tracing scripts by comparing the message size to the length of
+the passed message.
+
+### Context `validation`
+
+#### Tracepoint `validation:block_connected`
+
+Is called *after* a block is connected to the chain. Can, for example, be used
+to benchmark block connections together with `-reindex`.
+
+Arguments passed:
+1. Block Header Hash as `pointer to C-style String` (64 characters)
+2. Block Height as `int32`
+3. Transactions in the Block as `uint64`
+4. Inputs spend in the Block as `int32`
+5. SigOps in the Block (excluding coinbase SigOps) `uint64`
+6. Time it took to connect the Block in microseconds (µs) as `uint64`
+7. Block Header Hash as `pointer to unsigned chars` (i.e. 32 bytes in little-endian)
+
+Note: The 7th argument can't be accessed by bpftrace and is purposefully chosen
+to be the block header hash as bytes. See [bpftrace argument limit] for more
+details.
+
+[bpftrace argument limit]: #bpftrace-argument-limit
+
+## Adding tracepoints to Bitcoin Core
+
+To add a new tracepoint, `#include <util/trace.h>` in the compilation unit where
+the tracepoint is inserted. Use one of the `TRACEx` macros listed below
+depending on the number of arguments passed to the tracepoint. Up to 12
+arguments can be provided. The `context` and `event` specify the names by which
+the tracepoint is referred to. Please use `snake_case` and try to make sure that
+the tracepoint names make sense even without detailed knowledge of the
+implementation details. Do not forget to update the tracepoint list in this
+document.
+
+```c
+#define TRACE(context, event)
+#define TRACE1(context, event, a)
+#define TRACE2(context, event, a, b)
+#define TRACE3(context, event, a, b, c)
+#define TRACE4(context, event, a, b, c, d)
+#define TRACE5(context, event, a, b, c, d, e)
+#define TRACE6(context, event, a, b, c, d, e, f)
+#define TRACE7(context, event, a, b, c, d, e, f, g)
+#define TRACE8(context, event, a, b, c, d, e, f, g, h)
+#define TRACE9(context, event, a, b, c, d, e, f, g, h, i)
+#define TRACE10(context, event, a, b, c, d, e, f, g, h, i, j)
+#define TRACE11(context, event, a, b, c, d, e, f, g, h, i, j, k)
+#define TRACE12(context, event, a, b, c, d, e, f, g, h, i, j, k, l)
+```
+
+For example:
+
+```C++
+TRACE6(net, inbound_message,
+ pnode->GetId(),
+ pnode->GetAddrName().c_str(),
+ pnode->ConnectionTypeAsString().c_str(),
+ sanitizedType.c_str(),
+ msg.data.size(),
+ msg.data.data()
+);
+```
+
+### Guidelines and best practices
+
+#### Clear motivation and use-case
+Tracepoints need a clear motivation and use-case. The motivation should
+outweigh the impact on, for example, code readability. There is no point in
+adding tracepoints that don't end up being used.
+
+#### Provide an example
+When adding a new tracepoint, provide an example. Examples can show the use case
+and help reviewers testing that the tracepoint works as intended. The examples
+can be kept simple but should give others a starting point when working with
+the tracepoint. See existing examples in [contrib/tracing/].
+
+[contrib/tracing/]: ../contrib/tracing/
+
+#### No expensive computations for tracepoints
+Data passed to the tracepoint should be inexpensive to compute. Although the
+tracepoint itself only has overhead when enabled, the code to compute arguments
+is always run - even if the tracepoint is not used. For example, avoid
+serialization and parsing.
+
+#### Semi-stable API
+Tracepoints should have a semi-stable API. Users should be able to rely on the
+tracepoints for scripting. This means tracepoints need to be documented, and the
+argument order ideally should not change. If there is an important reason to
+change argument order, make sure to document the change and update the examples
+using the tracepoint.
+
+#### eBPF Virtual Machine limits
+Keep the eBPF Virtual Machine limits in mind. eBPF programs receiving data from
+the tracepoints run in a sandboxed Linux kernel VM. This VM has a limited stack
+size of 512 bytes. Check if it makes sense to pass larger amounts of data, for
+example, with a tracing script that can handle the passed data.
+
+#### `bpftrace` argument limit
+While tracepoints can have up to 12 arguments, bpftrace scripts currently only
+support reading from the first six arguments (`arg0` till `arg5`) on `x86_64`.
+bpftrace currently lacks real support for handling and printing binary data,
+like block header hashes and txids. When a tracepoint passes more than six
+arguments, then string and integer arguments should preferably be placed in the
+first six argument fields. Binary data can be placed in later arguments. The BCC
+supports reading from all 12 arguments.
+
+#### Strings as C-style String
+Generally, strings should be passed into the `TRACEx` macros as pointers to
+C-style strings (a null-terminated sequence of characters). For C++
+`std::strings`, [`c_str()`] can be used. It's recommended to document the
+maximum expected string size if known.
+
+
+[`c_str()`]: https://www.cplusplus.com/reference/string/string/c_str/
+
+
+## Listing available tracepoints
+
+Multiple tools can list the available tracepoints in a `bitcoind` binary with
+USDT support.
+
+### GDB - GNU Project Debugger
+
+To list probes in Bitcoin Core, use `info probes` in `gdb`:
+
+```
+$ gdb ./src/bitcoind
+…
+(gdb) info probes
+Type Provider Name Where Semaphore Object
+stap net inbound_message 0x000000000014419e /src/bitcoind
+stap net outbound_message 0x0000000000107c05 /src/bitcoind
+stap validation block_connected 0x00000000002fb10c /src/bitcoind
+…
+```
+
+### With `readelf`
+
+The `readelf` tool can be used to display the USDT tracepoints in Bitcoin Core.
+Look for the notes with the description `NT_STAPSDT`.
+
+```
+$ readelf -n ./src/bitcoind | grep NT_STAPSDT -A 4 -B 2
+Displaying notes found in: .note.stapsdt
+ Owner Data size Description
+ stapsdt 0x0000005d NT_STAPSDT (SystemTap probe descriptors)
+ Provider: net
+ Name: outbound_message
+ Location: 0x0000000000107c05, Base: 0x0000000000579c90, Semaphore: 0x0000000000000000
+ Arguments: -8@%r12 8@%rbx 8@%rdi 8@192(%rsp) 8@%rax 8@%rdx
+…
+```
+
+### With `tplist`
+
+The `tplist` tool is provided by BCC (see [Installing BCC]). It displays kernel
+tracepoints or USDT probes and their formats (for more information, see the
+[`tplist` usage demonstration]). There are slight binary naming differences
+between distributions. For example, on
+[Ubuntu the binary is called `tplist-bpfcc`][ubuntu binary].
+
+[Installing BCC]: https://github.com/iovisor/bcc/blob/master/INSTALL.md
+[`tplist` usage demonstration]: https://github.com/iovisor/bcc/blob/master/tools/tplist_example.txt
+[ubuntu binary]: https://github.com/iovisor/bcc/blob/master/INSTALL.md#ubuntu---binary
+
+```
+$ tplist -l ./src/bitcoind -v
+b'net':b'outbound_message' [sema 0x0]
+ 1 location(s)
+ 6 argument(s)
+…
+```
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index fc2fd80166..a07a1bb002 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -152,6 +152,7 @@ BITCOIN_TESTS =\
if ENABLE_WALLET
BITCOIN_TESTS += \
wallet/test/psbt_wallet_tests.cpp \
+ wallet/test/spend_tests.cpp \
wallet/test/wallet_tests.cpp \
wallet/test/walletdb_tests.cpp \
wallet/test/wallet_crypto_tests.cpp \
@@ -170,6 +171,8 @@ endif
BITCOIN_TEST_SUITE += \
+ wallet/test/util.cpp \
+ wallet/test/util.h \
wallet/test/wallet_test_fixture.cpp \
wallet/test/wallet_test_fixture.h \
wallet/test/init_test_fixture.cpp \
diff --git a/src/index/coinstatsindex.cpp b/src/index/coinstatsindex.cpp
index 8c6046489b..9ab9209ca4 100644
--- a/src/index/coinstatsindex.cpp
+++ b/src/index/coinstatsindex.cpp
@@ -24,14 +24,14 @@ struct DBVal {
uint64_t bogo_size;
CAmount total_amount;
CAmount total_subsidy;
- CAmount block_unspendable_amount;
- CAmount block_prevout_spent_amount;
- CAmount block_new_outputs_ex_coinbase_amount;
- CAmount block_coinbase_amount;
- CAmount unspendables_genesis_block;
- CAmount unspendables_bip30;
- CAmount unspendables_scripts;
- CAmount unspendables_unclaimed_rewards;
+ CAmount total_unspendable_amount;
+ CAmount total_prevout_spent_amount;
+ CAmount total_new_outputs_ex_coinbase_amount;
+ CAmount total_coinbase_amount;
+ CAmount total_unspendables_genesis_block;
+ CAmount total_unspendables_bip30;
+ CAmount total_unspendables_scripts;
+ CAmount total_unspendables_unclaimed_rewards;
SERIALIZE_METHODS(DBVal, obj)
{
@@ -40,14 +40,14 @@ struct DBVal {
READWRITE(obj.bogo_size);
READWRITE(obj.total_amount);
READWRITE(obj.total_subsidy);
- READWRITE(obj.block_unspendable_amount);
- READWRITE(obj.block_prevout_spent_amount);
- READWRITE(obj.block_new_outputs_ex_coinbase_amount);
- READWRITE(obj.block_coinbase_amount);
- READWRITE(obj.unspendables_genesis_block);
- READWRITE(obj.unspendables_bip30);
- READWRITE(obj.unspendables_scripts);
- READWRITE(obj.unspendables_unclaimed_rewards);
+ READWRITE(obj.total_unspendable_amount);
+ READWRITE(obj.total_prevout_spent_amount);
+ READWRITE(obj.total_new_outputs_ex_coinbase_amount);
+ READWRITE(obj.total_coinbase_amount);
+ READWRITE(obj.total_unspendables_genesis_block);
+ READWRITE(obj.total_unspendables_bip30);
+ READWRITE(obj.total_unspendables_scripts);
+ READWRITE(obj.total_unspendables_unclaimed_rewards);
}
};
@@ -122,9 +122,12 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
uint256 expected_block_hash{pindex->pprev->GetBlockHash()};
if (read_out.first != expected_block_hash) {
+ LogPrintf("WARNING: previous block header belongs to unexpected block %s; expected %s\n",
+ read_out.first.ToString(), expected_block_hash.ToString());
+
if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
- return error("%s: previous block header belongs to unexpected block %s; expected %s",
- __func__, read_out.first.ToString(), expected_block_hash.ToString());
+ return error("%s: previous block header not found; expected %s",
+ __func__, expected_block_hash.ToString());
}
}
@@ -138,29 +141,29 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
// Skip duplicate txid coinbase transactions (BIP30).
if (is_bip30_block && tx->IsCoinBase()) {
- m_block_unspendable_amount += block_subsidy;
- m_unspendables_bip30 += block_subsidy;
+ m_total_unspendable_amount += block_subsidy;
+ m_total_unspendables_bip30 += block_subsidy;
continue;
}
- for (size_t j = 0; j < tx->vout.size(); ++j) {
+ for (uint32_t j = 0; j < tx->vout.size(); ++j) {
const CTxOut& out{tx->vout[j]};
Coin coin{out, pindex->nHeight, tx->IsCoinBase()};
- COutPoint outpoint{tx->GetHash(), static_cast<uint32_t>(j)};
+ COutPoint outpoint{tx->GetHash(), j};
// Skip unspendable coins
if (coin.out.scriptPubKey.IsUnspendable()) {
- m_block_unspendable_amount += coin.out.nValue;
- m_unspendables_scripts += coin.out.nValue;
+ m_total_unspendable_amount += coin.out.nValue;
+ m_total_unspendables_scripts += coin.out.nValue;
continue;
}
m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
if (tx->IsCoinBase()) {
- m_block_coinbase_amount += coin.out.nValue;
+ m_total_coinbase_amount += coin.out.nValue;
} else {
- m_block_new_outputs_ex_coinbase_amount += coin.out.nValue;
+ m_total_new_outputs_ex_coinbase_amount += coin.out.nValue;
}
++m_transaction_output_count;
@@ -178,7 +181,7 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));
- m_block_prevout_spent_amount += coin.out.nValue;
+ m_total_prevout_spent_amount += coin.out.nValue;
--m_transaction_output_count;
m_total_amount -= coin.out.nValue;
@@ -188,17 +191,17 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
}
} else {
// genesis block
- m_block_unspendable_amount += block_subsidy;
- m_unspendables_genesis_block += block_subsidy;
+ m_total_unspendable_amount += block_subsidy;
+ m_total_unspendables_genesis_block += block_subsidy;
}
// If spent prevouts + block subsidy are still a higher amount than
// new outputs + coinbase + current unspendable amount this means
// the miner did not claim the full block reward. Unclaimed block
// rewards are also unspendable.
- const CAmount unclaimed_rewards{(m_block_prevout_spent_amount + m_total_subsidy) - (m_block_new_outputs_ex_coinbase_amount + m_block_coinbase_amount + m_block_unspendable_amount)};
- m_block_unspendable_amount += unclaimed_rewards;
- m_unspendables_unclaimed_rewards += unclaimed_rewards;
+ const CAmount unclaimed_rewards{(m_total_prevout_spent_amount + m_total_subsidy) - (m_total_new_outputs_ex_coinbase_amount + m_total_coinbase_amount + m_total_unspendable_amount)};
+ m_total_unspendable_amount += unclaimed_rewards;
+ m_total_unspendables_unclaimed_rewards += unclaimed_rewards;
std::pair<uint256, DBVal> value;
value.first = pindex->GetBlockHash();
@@ -206,20 +209,23 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
value.second.bogo_size = m_bogo_size;
value.second.total_amount = m_total_amount;
value.second.total_subsidy = m_total_subsidy;
- value.second.block_unspendable_amount = m_block_unspendable_amount;
- value.second.block_prevout_spent_amount = m_block_prevout_spent_amount;
- value.second.block_new_outputs_ex_coinbase_amount = m_block_new_outputs_ex_coinbase_amount;
- value.second.block_coinbase_amount = m_block_coinbase_amount;
- value.second.unspendables_genesis_block = m_unspendables_genesis_block;
- value.second.unspendables_bip30 = m_unspendables_bip30;
- value.second.unspendables_scripts = m_unspendables_scripts;
- value.second.unspendables_unclaimed_rewards = m_unspendables_unclaimed_rewards;
+ value.second.total_unspendable_amount = m_total_unspendable_amount;
+ value.second.total_prevout_spent_amount = m_total_prevout_spent_amount;
+ value.second.total_new_outputs_ex_coinbase_amount = m_total_new_outputs_ex_coinbase_amount;
+ value.second.total_coinbase_amount = m_total_coinbase_amount;
+ value.second.total_unspendables_genesis_block = m_total_unspendables_genesis_block;
+ value.second.total_unspendables_bip30 = m_total_unspendables_bip30;
+ value.second.total_unspendables_scripts = m_total_unspendables_scripts;
+ value.second.total_unspendables_unclaimed_rewards = m_total_unspendables_unclaimed_rewards;
uint256 out;
m_muhash.Finalize(out);
value.second.muhash = out;
- return m_db->Write(DBHeightKey(pindex->nHeight), value) && m_db->Write(DB_MUHASH, m_muhash);
+ CDBBatch batch(*m_db);
+ batch.Write(DBHeightKey(pindex->nHeight), value);
+ batch.Write(DB_MUHASH, m_muhash);
+ return m_db->WriteBatch(batch);
}
static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch,
@@ -317,14 +323,14 @@ bool CoinStatsIndex::LookUpStats(const CBlockIndex* block_index, CCoinsStats& co
coins_stats.nBogoSize = entry.bogo_size;
coins_stats.nTotalAmount = entry.total_amount;
coins_stats.total_subsidy = entry.total_subsidy;
- coins_stats.block_unspendable_amount = entry.block_unspendable_amount;
- coins_stats.block_prevout_spent_amount = entry.block_prevout_spent_amount;
- coins_stats.block_new_outputs_ex_coinbase_amount = entry.block_new_outputs_ex_coinbase_amount;
- coins_stats.block_coinbase_amount = entry.block_coinbase_amount;
- coins_stats.unspendables_genesis_block = entry.unspendables_genesis_block;
- coins_stats.unspendables_bip30 = entry.unspendables_bip30;
- coins_stats.unspendables_scripts = entry.unspendables_scripts;
- coins_stats.unspendables_unclaimed_rewards = entry.unspendables_unclaimed_rewards;
+ coins_stats.total_unspendable_amount = entry.total_unspendable_amount;
+ coins_stats.total_prevout_spent_amount = entry.total_prevout_spent_amount;
+ coins_stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
+ coins_stats.total_coinbase_amount = entry.total_coinbase_amount;
+ coins_stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
+ coins_stats.total_unspendables_bip30 = entry.total_unspendables_bip30;
+ coins_stats.total_unspendables_scripts = entry.total_unspendables_scripts;
+ coins_stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
return true;
}
@@ -341,33 +347,31 @@ bool CoinStatsIndex::Init()
}
}
- if (BaseIndex::Init()) {
- const CBlockIndex* pindex{CurrentIndex()};
+ if (!BaseIndex::Init()) return false;
- if (pindex) {
- DBVal entry;
- if (!LookUpOne(*m_db, pindex, entry)) {
- return false;
- }
+ const CBlockIndex* pindex{CurrentIndex()};
- m_transaction_output_count = entry.transaction_output_count;
- m_bogo_size = entry.bogo_size;
- m_total_amount = entry.total_amount;
- m_total_subsidy = entry.total_subsidy;
- m_block_unspendable_amount = entry.block_unspendable_amount;
- m_block_prevout_spent_amount = entry.block_prevout_spent_amount;
- m_block_new_outputs_ex_coinbase_amount = entry.block_new_outputs_ex_coinbase_amount;
- m_block_coinbase_amount = entry.block_coinbase_amount;
- m_unspendables_genesis_block = entry.unspendables_genesis_block;
- m_unspendables_bip30 = entry.unspendables_bip30;
- m_unspendables_scripts = entry.unspendables_scripts;
- m_unspendables_unclaimed_rewards = entry.unspendables_unclaimed_rewards;
+ if (pindex) {
+ DBVal entry;
+ if (!LookUpOne(*m_db, pindex, entry)) {
+ return false;
}
- return true;
+ m_transaction_output_count = entry.transaction_output_count;
+ m_bogo_size = entry.bogo_size;
+ m_total_amount = entry.total_amount;
+ m_total_subsidy = entry.total_subsidy;
+ m_total_unspendable_amount = entry.total_unspendable_amount;
+ m_total_prevout_spent_amount = entry.total_prevout_spent_amount;
+ m_total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
+ m_total_coinbase_amount = entry.total_coinbase_amount;
+ m_total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
+ m_total_unspendables_bip30 = entry.total_unspendables_bip30;
+ m_total_unspendables_scripts = entry.total_unspendables_scripts;
+ m_total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
}
- return false;
+ return true;
}
// Reverse a single block as part of a reorg
@@ -391,9 +395,12 @@ bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex
uint256 expected_block_hash{pindex->pprev->GetBlockHash()};
if (read_out.first != expected_block_hash) {
+ LogPrintf("WARNING: previous block header belongs to unexpected block %s; expected %s\n",
+ read_out.first.ToString(), expected_block_hash.ToString());
+
if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
- return error("%s: previous block header belongs to unexpected block %s; expected %s",
- __func__, read_out.first.ToString(), expected_block_hash.ToString());
+ return error("%s: previous block header not found; expected %s",
+ __func__, expected_block_hash.ToString());
}
}
}
@@ -402,24 +409,24 @@ bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex
for (size_t i = 0; i < block.vtx.size(); ++i) {
const auto& tx{block.vtx.at(i)};
- for (size_t j = 0; j < tx->vout.size(); ++j) {
+ for (uint32_t j = 0; j < tx->vout.size(); ++j) {
const CTxOut& out{tx->vout[j]};
- COutPoint outpoint{tx->GetHash(), static_cast<uint32_t>(j)};
+ COutPoint outpoint{tx->GetHash(), j};
Coin coin{out, pindex->nHeight, tx->IsCoinBase()};
// Skip unspendable coins
if (coin.out.scriptPubKey.IsUnspendable()) {
- m_block_unspendable_amount -= coin.out.nValue;
- m_unspendables_scripts -= coin.out.nValue;
+ m_total_unspendable_amount -= coin.out.nValue;
+ m_total_unspendables_scripts -= coin.out.nValue;
continue;
}
m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));
if (tx->IsCoinBase()) {
- m_block_coinbase_amount -= coin.out.nValue;
+ m_total_coinbase_amount -= coin.out.nValue;
} else {
- m_block_new_outputs_ex_coinbase_amount -= coin.out.nValue;
+ m_total_new_outputs_ex_coinbase_amount -= coin.out.nValue;
}
--m_transaction_output_count;
@@ -437,7 +444,7 @@ bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex
m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
- m_block_prevout_spent_amount -= coin.out.nValue;
+ m_total_prevout_spent_amount -= coin.out.nValue;
m_transaction_output_count++;
m_total_amount += coin.out.nValue;
@@ -446,9 +453,9 @@ bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex
}
}
- const CAmount unclaimed_rewards{(m_block_new_outputs_ex_coinbase_amount + m_block_coinbase_amount + m_block_unspendable_amount) - (m_block_prevout_spent_amount + m_total_subsidy)};
- m_block_unspendable_amount -= unclaimed_rewards;
- m_unspendables_unclaimed_rewards -= unclaimed_rewards;
+ const CAmount unclaimed_rewards{(m_total_new_outputs_ex_coinbase_amount + m_total_coinbase_amount + m_total_unspendable_amount) - (m_total_prevout_spent_amount + m_total_subsidy)};
+ m_total_unspendable_amount -= unclaimed_rewards;
+ m_total_unspendables_unclaimed_rewards -= unclaimed_rewards;
// Check that the rolled back internal values are consistent with the DB read out
uint256 out;
@@ -459,14 +466,14 @@ bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex
Assert(m_total_amount == read_out.second.total_amount);
Assert(m_bogo_size == read_out.second.bogo_size);
Assert(m_total_subsidy == read_out.second.total_subsidy);
- Assert(m_block_unspendable_amount == read_out.second.block_unspendable_amount);
- Assert(m_block_prevout_spent_amount == read_out.second.block_prevout_spent_amount);
- Assert(m_block_new_outputs_ex_coinbase_amount == read_out.second.block_new_outputs_ex_coinbase_amount);
- Assert(m_block_coinbase_amount == read_out.second.block_coinbase_amount);
- Assert(m_unspendables_genesis_block == read_out.second.unspendables_genesis_block);
- Assert(m_unspendables_bip30 == read_out.second.unspendables_bip30);
- Assert(m_unspendables_scripts == read_out.second.unspendables_scripts);
- Assert(m_unspendables_unclaimed_rewards == read_out.second.unspendables_unclaimed_rewards);
+ Assert(m_total_unspendable_amount == read_out.second.total_unspendable_amount);
+ Assert(m_total_prevout_spent_amount == read_out.second.total_prevout_spent_amount);
+ Assert(m_total_new_outputs_ex_coinbase_amount == read_out.second.total_new_outputs_ex_coinbase_amount);
+ Assert(m_total_coinbase_amount == read_out.second.total_coinbase_amount);
+ Assert(m_total_unspendables_genesis_block == read_out.second.total_unspendables_genesis_block);
+ Assert(m_total_unspendables_bip30 == read_out.second.total_unspendables_bip30);
+ Assert(m_total_unspendables_scripts == read_out.second.total_unspendables_scripts);
+ Assert(m_total_unspendables_unclaimed_rewards == read_out.second.total_unspendables_unclaimed_rewards);
return m_db->Write(DB_MUHASH, m_muhash);
}
diff --git a/src/index/coinstatsindex.h b/src/index/coinstatsindex.h
index 6149f9b4b3..a575b37c7c 100644
--- a/src/index/coinstatsindex.h
+++ b/src/index/coinstatsindex.h
@@ -25,14 +25,14 @@ private:
uint64_t m_bogo_size{0};
CAmount m_total_amount{0};
CAmount m_total_subsidy{0};
- CAmount m_block_unspendable_amount{0};
- CAmount m_block_prevout_spent_amount{0};
- CAmount m_block_new_outputs_ex_coinbase_amount{0};
- CAmount m_block_coinbase_amount{0};
- CAmount m_unspendables_genesis_block{0};
- CAmount m_unspendables_bip30{0};
- CAmount m_unspendables_scripts{0};
- CAmount m_unspendables_unclaimed_rewards{0};
+ CAmount m_total_unspendable_amount{0};
+ CAmount m_total_prevout_spent_amount{0};
+ CAmount m_total_new_outputs_ex_coinbase_amount{0};
+ CAmount m_total_coinbase_amount{0};
+ CAmount m_total_unspendables_genesis_block{0};
+ CAmount m_total_unspendables_bip30{0};
+ CAmount m_total_unspendables_scripts{0};
+ CAmount m_total_unspendables_unclaimed_rewards{0};
bool ReverseBlock(const CBlock& block, const CBlockIndex* pindex);
diff --git a/src/logging.cpp b/src/logging.cpp
index e5187fd596..b456108b61 100644
--- a/src/logging.cpp
+++ b/src/logging.cpp
@@ -8,6 +8,8 @@
#include <util/string.h>
#include <util/time.h>
+#include <algorithm>
+#include <array>
#include <mutex>
const char * const DEFAULT_DEBUGLOGFILE = "debug.log";
@@ -124,8 +126,7 @@ bool BCLog::Logger::DefaultShrinkDebugFile() const
return m_categories == BCLog::NONE;
}
-struct CLogCategoryDesc
-{
+struct CLogCategoryDesc {
BCLog::LogFlags flag;
std::string category;
};
@@ -179,15 +180,18 @@ bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str)
std::vector<LogCategory> BCLog::Logger::LogCategoriesList() const
{
+ // Sort log categories by alphabetical order.
+ std::array<CLogCategoryDesc, std::size(LogCategories)> categories;
+ std::copy(std::begin(LogCategories), std::end(LogCategories), categories.begin());
+ std::sort(categories.begin(), categories.end(), [](auto a, auto b) { return a.category < b.category; });
+
std::vector<LogCategory> ret;
- for (const CLogCategoryDesc& category_desc : LogCategories) {
- // Omit the special cases.
- if (category_desc.flag != BCLog::NONE && category_desc.flag != BCLog::ALL) {
- LogCategory catActive;
- catActive.category = category_desc.category;
- catActive.active = WillLogCategory(category_desc.flag);
- ret.push_back(catActive);
- }
+ for (const CLogCategoryDesc& category_desc : categories) {
+ if (category_desc.flag == BCLog::NONE || category_desc.flag == BCLog::ALL) continue;
+ LogCategory catActive;
+ catActive.category = category_desc.category;
+ catActive.active = WillLogCategory(category_desc.flag);
+ ret.push_back(catActive);
}
return ret;
}
@@ -237,7 +241,7 @@ namespace BCLog {
}
return ret;
}
-}
+} // namespace BCLog
void BCLog::Logger::LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line)
{
diff --git a/src/logging.h b/src/logging.h
index d04bc99268..38d73863e7 100644
--- a/src/logging.h
+++ b/src/logging.h
@@ -138,9 +138,9 @@ namespace BCLog {
bool DisableCategory(const std::string& str);
bool WillLogCategory(LogFlags category) const;
- /** Returns a vector of the log categories */
+ /** Returns a vector of the log categories in alphabetical order. */
std::vector<LogCategory> LogCategoriesList() const;
- /** Returns a string with the log categories */
+ /** Returns a string with the log categories in alphabetical order. */
std::string LogCategoriesString() const
{
return Join(LogCategoriesList(), ", ", [&](const LogCategory& i) { return i.category; });
diff --git a/src/net.cpp b/src/net.cpp
index 3a1bb138ab..8ef770ede2 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -25,6 +25,7 @@
#include <util/sock.h>
#include <util/strencodings.h>
#include <util/thread.h>
+#include <util/trace.h>
#include <util/translation.h>
#ifdef WIN32
@@ -3017,11 +3018,20 @@ bool CConnman::NodeFullyConnected(const CNode* pnode)
void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg)
{
size_t nMessageSize = msg.data.size();
- LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n", SanitizeString(msg.m_type), nMessageSize, pnode->GetId());
+ LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n", msg.m_type, nMessageSize, pnode->GetId());
if (gArgs.GetBoolArg("-capturemessages", false)) {
CaptureMessage(pnode->addr, msg.m_type, msg.data, /* incoming */ false);
}
+ TRACE6(net, outbound_message,
+ pnode->GetId(),
+ pnode->GetAddrName().c_str(),
+ pnode->ConnectionTypeAsString().c_str(),
+ msg.m_type.c_str(),
+ msg.data.size(),
+ msg.data.data()
+ );
+
// make sure we use the appropriate network transport format
std::vector<unsigned char> serializedHeader;
pnode->m_serializer->prepareForTransport(msg, serializedHeader);
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index a0c346b99f..2538904ade 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -34,6 +34,7 @@
#include <util/check.h> // For NDEBUG compile time check
#include <util/strencodings.h>
#include <util/system.h>
+#include <util/trace.h>
#include <validation.h>
#include <algorithm>
@@ -396,7 +397,8 @@ private:
/** The height of the best chain */
std::atomic<int> m_best_height{-1};
- int64_t m_stale_tip_check_time; //!< Next time to check for stale tip
+ /** Next time to check for stale tip */
+ int64_t m_stale_tip_check_time{0};
/** Whether this node is running in blocks only mode */
const bool m_ignore_incoming_txs;
@@ -469,16 +471,26 @@ private:
*
* Memory used: 1.3 MB
*/
- std::unique_ptr<CRollingBloomFilter> recentRejects GUARDED_BY(cs_main);
+ CRollingBloomFilter m_recent_rejects GUARDED_BY(::cs_main){120'000, 0.000'001};
uint256 hashRecentRejectsChainTip GUARDED_BY(cs_main);
/*
* Filter for transactions that have been recently confirmed.
* We use this to avoid requesting transactions that have already been
* confirnmed.
+ *
+ * Blocks don't typically have more than 4000 transactions, so this should
+ * be at least six blocks (~1 hr) worth of transactions that we can store,
+ * inserting both a txid and wtxid for every observed transaction.
+ * If the number of transactions appearing in a block goes up, or if we are
+ * seeing getdata requests more than an hour after initial announcement, we
+ * can increase this number.
+ * The false positive rate of 1/1M should come out to less than 1
+ * transaction per day that would be inadvertently ignored (which is the
+ * same probability that we have in the reject filter).
*/
Mutex m_recent_confirmed_transactions_mutex;
- std::unique_ptr<CRollingBloomFilter> m_recent_confirmed_transactions GUARDED_BY(m_recent_confirmed_transactions_mutex);
+ CRollingBloomFilter m_recent_confirmed_transactions GUARDED_BY(m_recent_confirmed_transactions_mutex){48'000, 0.000'001};
/** Have we requested this block from a peer */
bool IsBlockRequested(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@@ -1194,6 +1206,7 @@ void PeerManagerImpl::FinalizeNode(const CNode& node)
assert(m_outbound_peers_with_protect_from_disconnect == 0);
assert(m_wtxid_relay_peers == 0);
assert(m_txrequest.Size() == 0);
+ assert(m_orphanage.Size() == 0);
}
} // cs_main
if (node.fSuccessfullyConnected && misbehavior == 0 &&
@@ -1280,14 +1293,20 @@ void PeerManagerImpl::Misbehaving(const NodeId pnode, const int howmuch, const s
if (peer == nullptr) return;
LOCK(peer->m_misbehavior_mutex);
+ const int score_before{peer->m_misbehavior_score};
peer->m_misbehavior_score += howmuch;
+ const int score_now{peer->m_misbehavior_score};
+
const std::string message_prefixed = message.empty() ? "" : (": " + message);
- if (peer->m_misbehavior_score >= DISCOURAGEMENT_THRESHOLD && peer->m_misbehavior_score - howmuch < DISCOURAGEMENT_THRESHOLD) {
- LogPrint(BCLog::NET, "Misbehaving: peer=%d (%d -> %d) DISCOURAGE THRESHOLD EXCEEDED%s\n", pnode, peer->m_misbehavior_score - howmuch, peer->m_misbehavior_score, message_prefixed);
+ std::string warning;
+
+ if (score_now >= DISCOURAGEMENT_THRESHOLD && score_before < DISCOURAGEMENT_THRESHOLD) {
+ warning = " DISCOURAGE THRESHOLD EXCEEDED";
peer->m_should_discourage = true;
- } else {
- LogPrint(BCLog::NET, "Misbehaving: peer=%d (%d -> %d)%s\n", pnode, peer->m_misbehavior_score - howmuch, peer->m_misbehavior_score, message_prefixed);
}
+
+ LogPrint(BCLog::NET, "Misbehaving: peer=%d (%d -> %d)%s%s\n",
+ pnode, score_before, score_now, warning, message_prefixed);
}
bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state,
@@ -1392,23 +1411,8 @@ PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& conn
m_banman(banman),
m_chainman(chainman),
m_mempool(pool),
- m_stale_tip_check_time(0),
m_ignore_incoming_txs(ignore_incoming_txs)
{
- // Initialize global variables that cannot be constructed at startup.
- recentRejects.reset(new CRollingBloomFilter(120000, 0.000001));
-
- // Blocks don't typically have more than 4000 transactions, so this should
- // be at least six blocks (~1 hr) worth of transactions that we can store,
- // inserting both a txid and wtxid for every observed transaction.
- // If the number of transactions appearing in a block goes up, or if we are
- // seeing getdata requests more than an hour after initial announcement, we
- // can increase this number.
- // The false positive rate of 1/1M should come out to less than 1
- // transaction per day that would be inadvertently ignored (which is the
- // same probability that we have in the reject filter).
- m_recent_confirmed_transactions.reset(new CRollingBloomFilter(48000, 0.000001));
-
// Stale tip checking and peer eviction are on two different timers, but we
// don't want them to get out of sync due to drift in the scheduler, so we
// combine them in one function and schedule at the quicker (peer-eviction)
@@ -1434,9 +1438,9 @@ void PeerManagerImpl::BlockConnected(const std::shared_ptr<const CBlock>& pblock
{
LOCK(m_recent_confirmed_transactions_mutex);
for (const auto& ptx : pblock->vtx) {
- m_recent_confirmed_transactions->insert(ptx->GetHash());
+ m_recent_confirmed_transactions.insert(ptx->GetHash());
if (ptx->GetHash() != ptx->GetWitnessHash()) {
- m_recent_confirmed_transactions->insert(ptx->GetWitnessHash());
+ m_recent_confirmed_transactions.insert(ptx->GetWitnessHash());
}
}
}
@@ -1460,7 +1464,7 @@ void PeerManagerImpl::BlockDisconnected(const std::shared_ptr<const CBlock> &blo
// presumably the most common case of relaying a confirmed transaction
// should be just after a new block containing it is found.
LOCK(m_recent_confirmed_transactions_mutex);
- m_recent_confirmed_transactions->reset();
+ m_recent_confirmed_transactions.reset();
}
// All of the following cache a recent block, and are protected by cs_most_recent_block
@@ -1600,14 +1604,13 @@ void PeerManagerImpl::BlockChecked(const CBlock& block, const BlockValidationSta
bool PeerManagerImpl::AlreadyHaveTx(const GenTxid& gtxid)
{
- assert(recentRejects);
if (m_chainman.ActiveChain().Tip()->GetBlockHash() != hashRecentRejectsChainTip) {
// If the chain tip has changed previously rejected transactions
// might be now valid, e.g. due to a nLockTime'd tx becoming valid,
// or a double-spend. Reset the rejects filter and give those
// txs a second chance.
hashRecentRejectsChainTip = m_chainman.ActiveChain().Tip()->GetBlockHash();
- recentRejects->reset();
+ m_recent_rejects.reset();
}
const uint256& hash = gtxid.GetHash();
@@ -1616,10 +1619,10 @@ bool PeerManagerImpl::AlreadyHaveTx(const GenTxid& gtxid)
{
LOCK(m_recent_confirmed_transactions_mutex);
- if (m_recent_confirmed_transactions->contains(hash)) return true;
+ if (m_recent_confirmed_transactions.contains(hash)) return true;
}
- return recentRejects->contains(hash) || m_mempool.exists(gtxid);
+ return m_recent_rejects.contains(hash) || m_mempool.exists(gtxid);
}
bool PeerManagerImpl::AlreadyHaveBlock(const uint256& block_hash)
@@ -2238,8 +2241,7 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set)
// See also comments in https://github.com/bitcoin/bitcoin/pull/18044#discussion_r443419034
// for concerns around weakening security of unupgraded nodes
// if we start doing this too early.
- assert(recentRejects);
- recentRejects->insert(porphanTx->GetWitnessHash());
+ m_recent_rejects.insert(porphanTx->GetWitnessHash());
// If the transaction failed for TX_INPUTS_NOT_STANDARD,
// then we know that the witness was irrelevant to the policy
// failure, since this check depends only on the txid
@@ -2251,7 +2253,7 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set)
if (state.GetResult() == TxValidationResult::TX_INPUTS_NOT_STANDARD && porphanTx->GetWitnessHash() != porphanTx->GetHash()) {
// We only add the txid if it differs from the wtxid, to
// avoid wasting entries in the rolling bloom filter.
- recentRejects->insert(porphanTx->GetHash());
+ m_recent_rejects.insert(porphanTx->GetHash());
}
}
m_orphanage.EraseTx(orphanHash);
@@ -3252,7 +3254,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
std::sort(unique_parents.begin(), unique_parents.end());
unique_parents.erase(std::unique(unique_parents.begin(), unique_parents.end()), unique_parents.end());
for (const uint256& parent_txid : unique_parents) {
- if (recentRejects->contains(parent_txid)) {
+ if (m_recent_rejects.contains(parent_txid)) {
fRejectedParents = true;
break;
}
@@ -3293,8 +3295,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// regardless of what witness is provided, we will not accept
// this, so we don't need to allow for redownload of this txid
// from any of our non-wtxidrelay peers.
- recentRejects->insert(tx.GetHash());
- recentRejects->insert(tx.GetWitnessHash());
+ m_recent_rejects.insert(tx.GetHash());
+ m_recent_rejects.insert(tx.GetWitnessHash());
m_txrequest.ForgetTxHash(tx.GetHash());
m_txrequest.ForgetTxHash(tx.GetWitnessHash());
}
@@ -3313,8 +3315,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// See also comments in https://github.com/bitcoin/bitcoin/pull/18044#discussion_r443419034
// for concerns around weakening security of unupgraded nodes
// if we start doing this too early.
- assert(recentRejects);
- recentRejects->insert(tx.GetWitnessHash());
+ m_recent_rejects.insert(tx.GetWitnessHash());
m_txrequest.ForgetTxHash(tx.GetWitnessHash());
// If the transaction failed for TX_INPUTS_NOT_STANDARD,
// then we know that the witness was irrelevant to the policy
@@ -3325,7 +3326,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// transactions are later received (resulting in
// parent-fetching by txid via the orphan-handling logic).
if (state.GetResult() == TxValidationResult::TX_INPUTS_NOT_STANDARD && tx.GetWitnessHash() != tx.GetHash()) {
- recentRejects->insert(tx.GetHash());
+ m_recent_rejects.insert(tx.GetHash());
m_txrequest.ForgetTxHash(tx.GetHash());
}
if (RecursiveDynamicUsage(*ptx) < 100000) {
@@ -3334,21 +3335,21 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
}
}
- // If a tx has been detected by recentRejects, we will have reached
+ // If a tx has been detected by m_recent_rejects, we will have reached
// this point and the tx will have been ignored. Because we haven't run
// the tx through AcceptToMemoryPool, we won't have computed a DoS
// score for it or determined exactly why we consider it invalid.
//
// This means we won't penalize any peer subsequently relaying a DoSy
// tx (even if we penalized the first peer who gave it to us) because
- // we have to account for recentRejects showing false positives. In
+ // we have to account for m_recent_rejects showing false positives. In
// other words, we shouldn't penalize a peer if we aren't *sure* they
// submitted a DoSy tx.
//
- // Note that recentRejects doesn't just record DoSy or invalid
+ // Note that m_recent_rejects doesn't just record DoSy or invalid
// transactions, but any tx not accepted by the mempool, which may be
// due to node policy (vs. consensus). So we can't blanket penalize a
- // peer simply for relaying a tx that our recentRejects has caught,
+ // peer simply for relaying a tx that our m_recent_rejects has caught,
// regardless of false positives.
if (state.IsInvalid()) {
@@ -4046,6 +4047,15 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt
}
CNetMessage& msg(msgs.front());
+ TRACE6(net, inbound_message,
+ pfrom->GetId(),
+ pfrom->GetAddrName().c_str(),
+ pfrom->ConnectionTypeAsString().c_str(),
+ msg.m_command.c_str(),
+ msg.m_recv.size(),
+ msg.m_recv.data()
+ );
+
if (gArgs.GetBoolArg("-capturemessages", false)) {
CaptureMessage(pfrom->addr, msg.m_command, MakeUCharSpan(msg.m_recv), /* incoming */ true);
}
diff --git a/src/node/coinstats.h b/src/node/coinstats.h
index 8be256edc9..69e856dd15 100644
--- a/src/node/coinstats.h
+++ b/src/node/coinstats.h
@@ -45,15 +45,25 @@ struct CCoinsStats
bool index_used{false};
// Following values are only available from coinstats index
+
+ //! Total cumulative amount of block subsidies up to and including this block
CAmount total_subsidy{0};
- CAmount block_unspendable_amount{0};
- CAmount block_prevout_spent_amount{0};
- CAmount block_new_outputs_ex_coinbase_amount{0};
- CAmount block_coinbase_amount{0};
- CAmount unspendables_genesis_block{0};
- CAmount unspendables_bip30{0};
- CAmount unspendables_scripts{0};
- CAmount unspendables_unclaimed_rewards{0};
+ //! Total cumulative amount of unspendable coins up to and including this block
+ CAmount total_unspendable_amount{0};
+ //! Total cumulative amount of prevouts spent up to and including this block
+ CAmount total_prevout_spent_amount{0};
+ //! Total cumulative amount of outputs created up to and including this block
+ CAmount total_new_outputs_ex_coinbase_amount{0};
+ //! Total cumulative amount of coinbase outputs up to and including this block
+ CAmount total_coinbase_amount{0};
+ //! The unspendable coinbase amount from the genesis block
+ CAmount total_unspendables_genesis_block{0};
+ //! The two unspendable coinbase outputs total amount caused by BIP30
+ CAmount total_unspendables_bip30{0};
+ //! Total cumulative amount of outputs sent to unspendable scripts (OP_RETURN for example) up to and including this block
+ CAmount total_unspendables_scripts{0};
+ //! Total cumulative amount of coins lost due to unclaimed miner rewards up to and including this block
+ CAmount total_unspendables_unclaimed_rewards{0};
CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {}
};
diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp
index d3bce069b0..1861755aff 100644
--- a/src/node/transaction.cpp
+++ b/src/node/transaction.cpp
@@ -4,9 +4,12 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <consensus/validation.h>
+#include <index/txindex.h>
#include <net.h>
#include <net_processing.h>
+#include <node/blockstorage.h>
#include <node/context.h>
+#include <txmempool.h>
#include <validation.h>
#include <validationinterface.h>
#include <node/transaction.h>
@@ -119,3 +122,38 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
return TransactionError::OK;
}
+
+CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock)
+{
+ LOCK(cs_main);
+
+ if (mempool && !block_index) {
+ CTransactionRef ptx = mempool->get(hash);
+ if (ptx) return ptx;
+ }
+ if (g_txindex) {
+ CTransactionRef tx;
+ uint256 block_hash;
+ if (g_txindex->FindTx(hash, block_hash, tx)) {
+ if (!block_index || block_index->GetBlockHash() == block_hash) {
+ // Don't return the transaction if the provided block hash doesn't match.
+ // The case where a transaction appears in multiple blocks (e.g. reorgs or
+ // BIP30) is handled by the block lookup below.
+ hashBlock = block_hash;
+ return tx;
+ }
+ }
+ }
+ if (block_index) {
+ CBlock block;
+ if (ReadBlockFromDisk(block, block_index, consensusParams)) {
+ for (const auto& tx : block.vtx) {
+ if (tx->GetHash() == hash) {
+ hashBlock = block_index->GetBlockHash();
+ return tx;
+ }
+ }
+ }
+ }
+ return nullptr;
+}
diff --git a/src/node/transaction.h b/src/node/transaction.h
index 0c016ff04e..aed519cf7f 100644
--- a/src/node/transaction.h
+++ b/src/node/transaction.h
@@ -10,7 +10,12 @@
#include <primitives/transaction.h>
#include <util/error.h>
+class CBlockIndex;
+class CTxMemPool;
struct NodeContext;
+namespace Consensus {
+struct Params;
+}
/** Maximum fee rate for sendrawtransaction and testmempoolaccept RPC calls.
* Also used by the GUI when broadcasting a completed PSBT.
@@ -38,4 +43,19 @@ static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN / 10};
*/
[[nodiscard]] TransactionError BroadcastTransaction(NodeContext& node, CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback);
+/**
+ * Return transaction with a given hash.
+ * If mempool is provided and block_index is not provided, check it first for the tx.
+ * If -txindex is available, check it next for the tx.
+ * Finally, if block_index is provided, check for tx by reading entire block from disk.
+ *
+ * @param[in] block_index The block to read from disk, or nullptr
+ * @param[in] mempool If provided, check mempool for tx
+ * @param[in] hash The txid
+ * @param[in] consensusParams The params
+ * @param[out] hashBlock The block hash, if the tx was found via -txindex or block_index
+ * @returns The tx if found, otherwise nullptr
+ */
+CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock);
+
#endif // BITCOIN_NODE_TRANSACTION_H
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index f8a4d4ccab..4956ee39e9 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1120,13 +1120,13 @@ static RPCHelpMan gettxoutsetinfo()
{RPCResult::Type::STR_AMOUNT, "total_unspendable_amount", "The total amount of coins permanently excluded from the UTXO set (only available if coinstatsindex is used)"},
{RPCResult::Type::OBJ, "block_info", "Info on amounts in the block at this block height (only available if coinstatsindex is used)",
{
- {RPCResult::Type::STR_AMOUNT, "prevout_spent", ""},
- {RPCResult::Type::STR_AMOUNT, "coinbase", ""},
- {RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", ""},
- {RPCResult::Type::STR_AMOUNT, "unspendable", ""},
+ {RPCResult::Type::STR_AMOUNT, "prevout_spent", "Total amount of all prevouts spent in this block"},
+ {RPCResult::Type::STR_AMOUNT, "coinbase", "Coinbase subsidy amount of this block"},
+ {RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", "Total amount of new outputs created by this block"},
+ {RPCResult::Type::STR_AMOUNT, "unspendable", "Total amount of unspendable outputs created in this block"},
{RPCResult::Type::OBJ, "unspendables", "Detailed view of the unspendable categories",
{
- {RPCResult::Type::STR_AMOUNT, "genesis_block", ""},
+ {RPCResult::Type::STR_AMOUNT, "genesis_block", "The unspendable amount of the Genesis block subsidy"},
{RPCResult::Type::STR_AMOUNT, "bip30", "Transactions overridden by duplicates (no longer possible with BIP30)"},
{RPCResult::Type::STR_AMOUNT, "scripts", "Amounts sent to scripts that are unspendable (for example OP_RETURN outputs)"},
{RPCResult::Type::STR_AMOUNT, "unclaimed_rewards", "Fee rewards that miners did not claim in their coinbase transaction"},
@@ -1178,6 +1178,18 @@ static RPCHelpMan gettxoutsetinfo()
pindex = ParseHashOrHeight(request.params[1], chainman);
}
+ if (stats.index_requested && g_coin_stats_index) {
+ if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) {
+ const IndexSummary summary{g_coin_stats_index->GetSummary()};
+
+ // If a specific block was requested and the index has already synced past that height, we can return the
+ // data already even though the index is not fully synced yet.
+ if (pindex->nHeight > summary.best_block_height) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to get data because coinstatsindex is still syncing. Current height: %d", summary.best_block_height));
+ }
+ }
+ }
+
if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point, pindex)) {
ret.pushKV("height", (int64_t)stats.nHeight);
ret.pushKV("bestblock", stats.hashBlock.GetHex());
@@ -1194,7 +1206,7 @@ static RPCHelpMan gettxoutsetinfo()
ret.pushKV("transactions", static_cast<int64_t>(stats.nTransactions));
ret.pushKV("disk_size", stats.nDiskSize);
} else {
- ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.block_unspendable_amount));
+ ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.total_unspendable_amount));
CCoinsStats prev_stats{hash_type};
@@ -1203,28 +1215,21 @@ static RPCHelpMan gettxoutsetinfo()
}
UniValue block_info(UniValue::VOBJ);
- block_info.pushKV("prevout_spent", ValueFromAmount(stats.block_prevout_spent_amount - prev_stats.block_prevout_spent_amount));
- block_info.pushKV("coinbase", ValueFromAmount(stats.block_coinbase_amount - prev_stats.block_coinbase_amount));
- block_info.pushKV("new_outputs_ex_coinbase", ValueFromAmount(stats.block_new_outputs_ex_coinbase_amount - prev_stats.block_new_outputs_ex_coinbase_amount));
- block_info.pushKV("unspendable", ValueFromAmount(stats.block_unspendable_amount - prev_stats.block_unspendable_amount));
+ block_info.pushKV("prevout_spent", ValueFromAmount(stats.total_prevout_spent_amount - prev_stats.total_prevout_spent_amount));
+ block_info.pushKV("coinbase", ValueFromAmount(stats.total_coinbase_amount - prev_stats.total_coinbase_amount));
+ block_info.pushKV("new_outputs_ex_coinbase", ValueFromAmount(stats.total_new_outputs_ex_coinbase_amount - prev_stats.total_new_outputs_ex_coinbase_amount));
+ block_info.pushKV("unspendable", ValueFromAmount(stats.total_unspendable_amount - prev_stats.total_unspendable_amount));
UniValue unspendables(UniValue::VOBJ);
- unspendables.pushKV("genesis_block", ValueFromAmount(stats.unspendables_genesis_block - prev_stats.unspendables_genesis_block));
- unspendables.pushKV("bip30", ValueFromAmount(stats.unspendables_bip30 - prev_stats.unspendables_bip30));
- unspendables.pushKV("scripts", ValueFromAmount(stats.unspendables_scripts - prev_stats.unspendables_scripts));
- unspendables.pushKV("unclaimed_rewards", ValueFromAmount(stats.unspendables_unclaimed_rewards - prev_stats.unspendables_unclaimed_rewards));
+ unspendables.pushKV("genesis_block", ValueFromAmount(stats.total_unspendables_genesis_block - prev_stats.total_unspendables_genesis_block));
+ unspendables.pushKV("bip30", ValueFromAmount(stats.total_unspendables_bip30 - prev_stats.total_unspendables_bip30));
+ unspendables.pushKV("scripts", ValueFromAmount(stats.total_unspendables_scripts - prev_stats.total_unspendables_scripts));
+ unspendables.pushKV("unclaimed_rewards", ValueFromAmount(stats.total_unspendables_unclaimed_rewards - prev_stats.total_unspendables_unclaimed_rewards));
block_info.pushKV("unspendables", unspendables);
ret.pushKV("block_info", block_info);
}
} else {
- if (g_coin_stats_index) {
- const IndexSummary summary{g_coin_stats_index->GetSummary()};
-
- if (!summary.synced) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to read UTXO set because coinstatsindex is still syncing. Current height: %d", summary.best_block_height));
- }
- }
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}
return ret;
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 6dfccd9023..c617b0389c 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -76,8 +76,8 @@ static RPCHelpMan getrawtransaction()
"\nBy default, this call only returns a transaction if it is in the mempool. If -txindex is enabled\n"
"and no blockhash argument is passed, it will return the transaction if it is in the mempool or any block.\n"
- "If -txindex is not enabled and a blockhash argument is passed, it will return the transaction if\n"
- "the specified block is available and the transaction is found in that block.\n"
+ "If a blockhash argument is passed, it will return the transaction if\n"
+ "the specified block is available and the transaction is in that block.\n"
"\nHint: Use gettransaction for wallet transactions.\n"
"\nIf verbose is 'true', returns an Object with information about 'txid'.\n"
diff --git a/src/script/interpreter.h b/src/script/interpreter.h
index 37ae713996..93136a0b79 100644
--- a/src/script/interpreter.h
+++ b/src/script/interpreter.h
@@ -139,6 +139,10 @@ enum : uint32_t {
// Making unknown public key versions (in BIP 342 scripts) non-standard
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = (1U << 20),
+
+ // Constants to point to the highest flag in use. Add new flags above this line.
+ //
+ SCRIPT_VERIFY_END_MARKER
};
bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned int flags, ScriptError* serror);
diff --git a/src/test/fuzz/banman.cpp b/src/test/fuzz/banman.cpp
index 182aabc79b..1986b5e4c8 100644
--- a/src/test/fuzz/banman.cpp
+++ b/src/test/fuzz/banman.cpp
@@ -109,7 +109,9 @@ FUZZ_TARGET_INIT(banman, initialize_banman)
BanMan ban_man_read{banlist_file, /* client_interface */ nullptr, /* default_ban_time */ 0};
banmap_t banmap_read;
ban_man_read.GetBanned(banmap_read);
- assert(banmap == banmap_read);
+ // Assert temporarily disabled to allow the remainder of the fuzz test to run while a
+ // fix is being worked on. See https://github.com/bitcoin/bitcoin/pull/22517
+ (void)(banmap == banmap_read);
}
}
fs::remove(banlist_file.string() + ".dat");
diff --git a/src/test/fuzz/prevector.cpp b/src/test/fuzz/prevector.cpp
index 51956bbe9e..447f32ed16 100644
--- a/src/test/fuzz/prevector.cpp
+++ b/src/test/fuzz/prevector.cpp
@@ -206,10 +206,14 @@ public:
FUZZ_TARGET(prevector)
{
+ // Pick an arbitrary upper bound to limit the runtime and avoid timeouts on
+ // inputs.
+ int limit_max_ops{3000};
+
FuzzedDataProvider prov(buffer.data(), buffer.size());
prevector_tester<8, int> test;
- while (prov.remaining_bytes()) {
+ while (--limit_max_ops >= 0 && prov.remaining_bytes()) {
switch (prov.ConsumeIntegralInRange<int>(0, 13 + 3 * (test.size() > 0))) {
case 0:
test.insert(prov.ConsumeIntegralInRange<size_t>(0, test.size()), prov.ConsumeIntegral<int>());
diff --git a/src/test/fuzz/rolling_bloom_filter.cpp b/src/test/fuzz/rolling_bloom_filter.cpp
index 07059cce76..3b33115e72 100644
--- a/src/test/fuzz/rolling_bloom_filter.cpp
+++ b/src/test/fuzz/rolling_bloom_filter.cpp
@@ -16,12 +16,16 @@
FUZZ_TARGET(rolling_bloom_filter)
{
+ // Pick an arbitrary upper bound to limit the runtime and avoid timeouts on
+ // inputs.
+ int limit_max_ops{3000};
+
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
CRollingBloomFilter rolling_bloom_filter{
fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, 1000),
0.999 / fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, std::numeric_limits<unsigned int>::max())};
- while (fuzzed_data_provider.remaining_bytes() > 0) {
+ while (--limit_max_ops >= 0 && fuzzed_data_provider.remaining_bytes() > 0) {
CallOneOf(
fuzzed_data_provider,
[&] {
@@ -32,13 +36,10 @@ FUZZ_TARGET(rolling_bloom_filter)
assert(present);
},
[&] {
- const std::optional<uint256> u256 = ConsumeDeserializable<uint256>(fuzzed_data_provider);
- if (!u256) {
- return;
- }
- (void)rolling_bloom_filter.contains(*u256);
- rolling_bloom_filter.insert(*u256);
- const bool present = rolling_bloom_filter.contains(*u256);
+ const uint256 u256{ConsumeUInt256(fuzzed_data_provider)};
+ (void)rolling_bloom_filter.contains(u256);
+ rolling_bloom_filter.insert(u256);
+ const bool present = rolling_bloom_filter.contains(u256);
assert(present);
},
[&] {
diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp
index bab34ea340..dadf772bc1 100644
--- a/src/test/fuzz/tx_pool.cpp
+++ b/src/test/fuzz/tx_pool.cpp
@@ -112,6 +112,10 @@ void MockTime(FuzzedDataProvider& fuzzed_data_provider, const CChainState& chain
FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
{
+ // Pick an arbitrary upper bound to limit the runtime and avoid timeouts on
+ // inputs.
+ int limit_max_ops{300};
+
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const auto& node = g_setup->m_node;
auto& chainstate = node.chainman->ActiveChainstate();
@@ -142,7 +146,7 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
return c.out.nValue;
};
- while (fuzzed_data_provider.ConsumeBool()) {
+ while (--limit_max_ops >= 0 && fuzzed_data_provider.ConsumeBool()) {
{
// Total supply is the mempool fee + all outpoints
CAmount supply_now{WITH_LOCK(tx_pool.cs, return tx_pool.GetTotalFee())};
@@ -285,6 +289,10 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool)
FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool)
{
+ // Pick an arbitrary upper bound to limit the runtime and avoid timeouts on
+ // inputs.
+ int limit_max_ops{300};
+
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const auto& node = g_setup->m_node;
auto& chainstate = node.chainman->ActiveChainstate();
@@ -305,7 +313,7 @@ FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool)
CTxMemPool tx_pool_{/* estimator */ nullptr, /* check_ratio */ 1};
MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_);
- while (fuzzed_data_provider.ConsumeBool()) {
+ while (--limit_max_ops >= 0 && fuzzed_data_provider.ConsumeBool()) {
const auto mut_tx = ConsumeTransaction(fuzzed_data_provider, txids);
if (fuzzed_data_provider.ConsumeBool()) {
diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp
index 23195c0a26..1924ea55b1 100644
--- a/src/test/txvalidationcache_tests.cpp
+++ b/src/test/txvalidationcache_tests.cpp
@@ -112,10 +112,15 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup)
static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t failing_flags, bool add_to_cache, CCoinsViewCache& active_coins_tip) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
PrecomputedTransactionData txdata;
- // If we add many more flags, this loop can get too expensive, but we can
- // rewrite in the future to randomly pick a set of flags to evaluate.
- for (uint32_t test_flags=0; test_flags < (1U << 16); test_flags += 1) {
+
+ FastRandomContext insecure_rand(true);
+
+ for (int count = 0; count < 10000; ++count) {
TxValidationState state;
+
+ // Randomly selects flag combinations
+ uint32_t test_flags = (uint32_t) insecure_rand.randrange((SCRIPT_VERIFY_END_MARKER - 1) << 1);
+
// Filter out incompatible flag choices
if ((test_flags & SCRIPT_VERIFY_CLEANSTACK)) {
// CLEANSTACK requires P2SH and WITNESS, see VerifyScript() in
diff --git a/src/txorphanage.h b/src/txorphanage.h
index e4266e470a..24c8318f36 100644
--- a/src/txorphanage.h
+++ b/src/txorphanage.h
@@ -47,6 +47,13 @@ public:
* (ie orphans that may have found their final missing parent, and so should be reconsidered for the mempool) */
void AddChildrenToWorkSet(const CTransaction& tx, std::set<uint256>& orphan_work_set) const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans);
+ /** Return how many entries exist in the orphange */
+ size_t Size() LOCKS_EXCLUDED(::g_cs_orphans)
+ {
+ LOCK(::g_cs_orphans);
+ return m_orphans.size();
+ }
+
protected:
struct OrphanTx {
CTransactionRef tx;
diff --git a/src/validation.cpp b/src/validation.cpp
index 4e7bc635da..1b3d00bc6d 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -19,7 +19,6 @@
#include <flatfile.h>
#include <hash.h>
#include <index/blockfilterindex.h>
-#include <index/txindex.h>
#include <logging.h>
#include <logging/timer.h>
#include <node/blockstorage.h>
@@ -48,6 +47,7 @@
#include <util/rbf.h>
#include <util/strencodings.h>
#include <util/system.h>
+#include <util/trace.h>
#include <util/translation.h>
#include <validationinterface.h>
#include <warnings.h>
@@ -1154,38 +1154,6 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx
return result;
}
-CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock)
-{
- LOCK(cs_main);
-
- if (mempool && !block_index) {
- CTransactionRef ptx = mempool->get(hash);
- if (ptx) return ptx;
- }
- if (g_txindex) {
- CTransactionRef tx;
- uint256 block_hash;
- if (g_txindex->FindTx(hash, block_hash, tx)) {
- if (!block_index || block_index->GetBlockHash() == block_hash) {
- hashBlock = block_hash;
- return tx;
- }
- }
- }
- if (block_index) {
- CBlock block;
- if (ReadBlockFromDisk(block, block_index, consensusParams)) {
- for (const auto& tx : block.vtx) {
- if (tx->GetHash() == hash) {
- hashBlock = block_index->GetBlockHash();
- return tx;
- }
- }
- }
- }
- return nullptr;
-}
-
CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams)
{
int halvings = nHeight / consensusParams.nSubsidyHalvingInterval;
@@ -1997,6 +1965,16 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
int64_t nTime6 = GetTimeMicros(); nTimeCallbacks += nTime6 - nTime5;
LogPrint(BCLog::BENCH, " - Callbacks: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime6 - nTime5), nTimeCallbacks * MICRO, nTimeCallbacks * MILLI / nBlocksTotal);
+ TRACE7(validation, block_connected,
+ block.GetHash().ToString().c_str(),
+ pindex->nHeight,
+ block.vtx.size(),
+ nInputs,
+ nSigOpsCost,
+ GetTimeMicros() - nTimeStart, // in microseconds (µs)
+ block.GetHash().data()
+ );
+
return true;
}
diff --git a/src/validation.h b/src/validation.h
index d3e4f3b983..9d8d7c06a9 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -140,20 +140,7 @@ void UnloadBlockIndex(CTxMemPool* mempool, ChainstateManager& chainman);
void StartScriptCheckWorkerThreads(int threads_num);
/** Stop all of the script checking worker threads */
void StopScriptCheckWorkerThreads();
-/**
- * Return transaction with a given hash.
- * If mempool is provided and block_index is not provided, check it first for the tx.
- * If -txindex is available, check it next for the tx.
- * Finally, if block_index is provided, check for tx by reading entire block from disk.
- *
- * @param[in] block_index The block to read from disk, or nullptr
- * @param[in] mempool If provided, check mempool for tx
- * @param[in] hash The txid
- * @param[in] consensusParams The params
- * @param[out] hashBlock The block hash, if the tx was found via -txindex or block_index
- * @returns The tx if found, otherwise nullptr
- */
-CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock);
+
CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams);
bool AbortNode(BlockValidationState& state, const std::string& strMessage, const bilingual_str& userMessage = bilingual_str{});
diff --git a/src/wallet/test/spend_tests.cpp b/src/wallet/test/spend_tests.cpp
new file mode 100644
index 0000000000..66e7de4273
--- /dev/null
+++ b/src/wallet/test/spend_tests.cpp
@@ -0,0 +1,61 @@
+// Copyright (c) 2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <policy/fees.h>
+#include <validation.h>
+#include <wallet/coincontrol.h>
+#include <wallet/test/util.h>
+#include <wallet/test/wallet_test_fixture.h>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(spend_tests, WalletTestingSetup)
+
+BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
+{
+ CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
+ auto wallet = CreateSyncedWallet(*m_node.chain, m_node.chainman->ActiveChain(), coinbaseKey);
+
+ // Check that a subtract-from-recipient transaction slightly less than the
+ // coinbase input amount does not create a change output (because it would
+ // be uneconomical to add and spend the output), and make sure it pays the
+ // leftover input amount which would have been change to the recipient
+ // instead of the miner.
+ auto check_tx = [&wallet](CAmount leftover_input_amount) {
+ CRecipient recipient{GetScriptForRawPubKey({}), 50 * COIN - leftover_input_amount, true /* subtract fee */};
+ CTransactionRef tx;
+ CAmount fee;
+ int change_pos = -1;
+ bilingual_str error;
+ CCoinControl coin_control;
+ coin_control.m_feerate.emplace(10000);
+ coin_control.fOverrideFeeRate = true;
+ FeeCalculation fee_calc;
+ BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, fee, change_pos, error, coin_control, fee_calc));
+ BOOST_CHECK_EQUAL(tx->vout.size(), 1);
+ BOOST_CHECK_EQUAL(tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - fee);
+ BOOST_CHECK_GT(fee, 0);
+ return fee;
+ };
+
+ // Send full input amount to recipient, check that only nonzero fee is
+ // subtracted (to_reduce == fee).
+ const CAmount fee{check_tx(0)};
+
+ // Send slightly less than full input amount to recipient, check leftover
+ // input amount is paid to recipient not the miner (to_reduce == fee - 123)
+ BOOST_CHECK_EQUAL(fee, check_tx(123));
+
+ // Send full input minus fee amount to recipient, check leftover input
+ // amount is paid to recipient not the miner (to_reduce == 0)
+ BOOST_CHECK_EQUAL(fee, check_tx(fee));
+
+ // Send full input minus more than the fee amount to recipient, check
+ // leftover input amount is paid to recipient not the miner (to_reduce ==
+ // -123). This overpays the recipient instead of overpaying the miner more
+ // than double the neccesary fee.
+ BOOST_CHECK_EQUAL(fee, check_tx(fee + 123));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp
new file mode 100644
index 0000000000..c3061b93c0
--- /dev/null
+++ b/src/wallet/test/util.cpp
@@ -0,0 +1,38 @@
+// Copyright (c) 2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <wallet/test/util.h>
+
+#include <chain.h>
+#include <key.h>
+#include <test/util/setup_common.h>
+#include <wallet/wallet.h>
+#include <wallet/walletdb.h>
+
+#include <boost/test/unit_test.hpp>
+
+#include <memory>
+
+std::unique_ptr<CWallet> CreateSyncedWallet(interfaces::Chain& chain, CChain& cchain, const CKey& key)
+{
+ auto wallet = std::make_unique<CWallet>(&chain, "", CreateMockWalletDatabase());
+ {
+ LOCK2(wallet->cs_wallet, ::cs_main);
+ wallet->SetLastBlockProcessed(cchain.Height(), cchain.Tip()->GetBlockHash());
+ }
+ wallet->LoadWallet();
+ {
+ auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan();
+ LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore);
+ spk_man->AddKeyPubKey(key, key.GetPubKey());
+ }
+ WalletRescanReserver reserver(*wallet);
+ reserver.reserve();
+ CWallet::ScanResult result = wallet->ScanForWalletTransactions(cchain.Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, false /* update */);
+ BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
+ BOOST_CHECK_EQUAL(result.last_scanned_block, cchain.Tip()->GetBlockHash());
+ BOOST_CHECK_EQUAL(*result.last_scanned_height, cchain.Height());
+ BOOST_CHECK(result.last_failed_block.IsNull());
+ return wallet;
+}
diff --git a/src/wallet/test/util.h b/src/wallet/test/util.h
new file mode 100644
index 0000000000..288c111571
--- /dev/null
+++ b/src/wallet/test/util.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_WALLET_TEST_UTIL_H
+#define BITCOIN_WALLET_TEST_UTIL_H
+
+#include <memory>
+
+class CChain;
+class CKey;
+class CWallet;
+namespace interfaces {
+class Chain;
+} // namespace interfaces
+
+std::unique_ptr<CWallet> CreateSyncedWallet(interfaces::Chain& chain, CChain& cchain, const CKey& key);
+
+#endif // BITCOIN_WALLET_TEST_UTIL_H
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index a0070b8dd3..75a08b6f74 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -20,6 +20,7 @@
#include <util/translation.h>
#include <validation.h>
#include <wallet/coincontrol.h>
+#include <wallet/test/util.h>
#include <wallet/test/wallet_test_fixture.h>
#include <boost/test/unit_test.hpp>
@@ -480,20 +481,7 @@ public:
ListCoinsTestingSetup()
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
- wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
- {
- LOCK2(wallet->cs_wallet, ::cs_main);
- wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
- }
- wallet->LoadWallet();
- AddKey(*wallet, coinbaseKey);
- WalletRescanReserver reserver(*wallet);
- reserver.reserve();
- CWallet::ScanResult result = wallet->ScanForWalletTransactions(m_node.chainman->ActiveChain().Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, false /* update */);
- BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
- BOOST_CHECK_EQUAL(result.last_scanned_block, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
- BOOST_CHECK_EQUAL(*result.last_scanned_height, m_node.chainman->ActiveChain().Height());
- BOOST_CHECK(result.last_failed_block.IsNull());
+ wallet = CreateSyncedWallet(*m_node.chain, m_node.chainman->ActiveChain(), coinbaseKey);
}
~ListCoinsTestingSetup()
diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py
index 10d2072dba..7c14f5d5a6 100755
--- a/test/functional/feature_cltv.py
+++ b/test/functional/feature_cltv.py
@@ -9,6 +9,7 @@ Test that the CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height
"""
from test_framework.blocktools import (
+ CLTV_HEIGHT,
create_block,
create_coinbase,
)
@@ -31,8 +32,6 @@ from test_framework.wallet import (
MiniWalletMode,
)
-CLTV_HEIGHT = 1351
-
# Helper function to modify a transaction by
# 1) prepending a given script to the scriptSig of vin 0 and
diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py
index 5d8ec2a8da..71d522a245 100755
--- a/test/functional/feature_coinstatsindex.py
+++ b/test/functional/feature_coinstatsindex.py
@@ -32,7 +32,6 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
- try_rpc,
)
class CoinStatsIndexTest(BitcoinTestFramework):
@@ -76,13 +75,11 @@ class CoinStatsIndexTest(BitcoinTestFramework):
self.sync_blocks(timeout=120)
self.log.info("Test that gettxoutsetinfo() output is consistent with or without coinstatsindex option")
- self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", node.gettxoutsetinfo))
res0 = node.gettxoutsetinfo('none')
# The fields 'disk_size' and 'transactions' do not exist on the index
del res0['disk_size'], res0['transactions']
- self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options:
res1 = index_node.gettxoutsetinfo(hash_option)
# The fields 'block_info' and 'total_unspendable_amount' only exist on the index
@@ -97,7 +94,6 @@ class CoinStatsIndexTest(BitcoinTestFramework):
# Generate a new tip
node.generate(5)
- self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options:
# Fetch old stats by height
res2 = index_node.gettxoutsetinfo(hash_option, 102)
@@ -176,7 +172,6 @@ class CoinStatsIndexTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all()
- self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options:
# Check all amounts were registered correctly
res6 = index_node.gettxoutsetinfo(hash_option, 108)
@@ -209,7 +204,6 @@ class CoinStatsIndexTest(BitcoinTestFramework):
self.nodes[0].submitblock(block.serialize().hex())
self.sync_all()
- self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options:
res7 = index_node.gettxoutsetinfo(hash_option, 109)
assert_equal(res7['total_unspendable_amount'], Decimal('80.98999999'))
@@ -235,7 +229,6 @@ class CoinStatsIndexTest(BitcoinTestFramework):
assert_equal(res8, res9)
index_node.generate(1)
- self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res10 = index_node.gettxoutsetinfo('muhash')
assert(res8['txouts'] < res10['txouts'])
@@ -256,14 +249,12 @@ class CoinStatsIndexTest(BitcoinTestFramework):
index_node = self.nodes[1]
reorg_blocks = index_node.generatetoaddress(2, index_node.getnewaddress())
reorg_block = reorg_blocks[1]
- self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res_invalid = index_node.gettxoutsetinfo('muhash')
index_node.invalidateblock(reorg_blocks[0])
assert_equal(index_node.gettxoutsetinfo('muhash')['height'], 110)
# Add two new blocks
block = index_node.generate(2)[1]
- self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=None, use_index=False)
# Test that the result of the reorged block is not returned for its old block height
@@ -285,9 +276,7 @@ class CoinStatsIndexTest(BitcoinTestFramework):
# Ensure that removing and re-adding blocks yields consistent results
block = index_node.getblockhash(99)
index_node.invalidateblock(block)
- self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
index_node.reconsiderblock(block)
- self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res3 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=112)
assert_equal(res2, res3)
@@ -297,8 +286,7 @@ class CoinStatsIndexTest(BitcoinTestFramework):
node.getblock(reorg_block)
self.restart_node(0, ["-coinstatsindex"])
- self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", node.gettxoutsetinfo, 'muhash'))
- assert_raises_rpc_error(-32603, "Unable to read UTXO set", node.gettxoutsetinfo, 'muhash', reorg_block)
+ assert_raises_rpc_error(-32603, "Unable to get data because coinstatsindex is still syncing.", node.gettxoutsetinfo, 'muhash', reorg_block)
def _test_index_rejects_hash_serialized(self):
self.log.info("Test that the rpc raises if the legacy hash is passed with the index")
diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py
index 5081867319..1ac1a0563f 100755
--- a/test/functional/feature_csv_activation.py
+++ b/test/functional/feature_csv_activation.py
@@ -41,6 +41,7 @@ from itertools import product
import time
from test_framework.blocktools import (
+ CSV_ACTIVATION_HEIGHT,
create_block,
create_coinbase,
)
@@ -63,7 +64,6 @@ from test_framework.wallet import (
TESTING_TX_COUNT = 83 # Number of testing transactions: 1 BIP113 tx, 16 BIP68 txs, 66 BIP112 txs (see comments above)
COINBASE_BLOCK_COUNT = TESTING_TX_COUNT # Number of coinbase blocks we need to generate as inputs for our txs
BASE_RELATIVE_LOCKTIME = 10
-CSV_ACTIVATION_HEIGHT = 432
SEQ_DISABLE_FLAG = 1 << 31
SEQ_RANDOM_HIGH_BIT = 1 << 25
SEQ_TYPE_FLAG = 1 << 22
diff --git a/test/functional/mempool_reorg.py b/test/functional/mempool_reorg.py
index bcc6aa7bcc..b5086e1df1 100755
--- a/test/functional/mempool_reorg.py
+++ b/test/functional/mempool_reorg.py
@@ -80,7 +80,7 @@ class MempoolCoinbaseTest(BitcoinTestFramework):
self.log.info("Generate a block")
last_block = self.nodes[0].generate(1)
# Sync blocks, so that peer 1 gets the block before timelock_tx
- # Otherwise, peer 1 would put the timelock_tx in recentRejects
+ # Otherwise, peer 1 would put the timelock_tx in m_recent_rejects
self.sync_all()
self.log.info("The time-locked transaction can now be spent")
diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py
index 594a28d662..8b285907c5 100755
--- a/test/functional/p2p_permissions.py
+++ b/test/functional/p2p_permissions.py
@@ -130,7 +130,7 @@ class P2PPermissionsTests(BitcoinTestFramework):
tx.vout[0].nValue += 1
txid = tx.rehash()
# Send the transaction twice. The first time, it'll be rejected by ATMP because it conflicts
- # with a mempool transaction. The second time, it'll be in the recentRejects filter.
+ # with a mempool transaction. The second time, it'll be in the m_recent_rejects filter.
p2p_rebroadcast_wallet.send_txs_and_test(
[tx],
self.nodes[1],
diff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py
index 52c8fa883d..563f2ea43e 100755
--- a/test/functional/rpc_misc.py
+++ b/test/functional/rpc_misc.py
@@ -54,13 +54,27 @@ class RpcMiscTest(BitcoinTestFramework):
assert_raises_rpc_error(-8, "unknown mode foobar", node.getmemoryinfo, mode="foobar")
- self.log.info("test logging")
+ self.log.info("test logging rpc and help")
+
+ # Test logging RPC returns the expected number of logging categories.
+ assert_equal(len(node.logging()), 24)
+
+ # Test toggling a logging category on/off/on with the logging RPC.
assert_equal(node.logging()['qt'], True)
node.logging(exclude=['qt'])
assert_equal(node.logging()['qt'], False)
node.logging(include=['qt'])
assert_equal(node.logging()['qt'], True)
+ # Test logging RPC returns the logging categories in alphabetical order.
+ sorted_logging_categories = sorted(node.logging())
+ assert_equal(list(node.logging()), sorted_logging_categories)
+
+ # Test logging help returns the logging categories string in alphabetical order.
+ categories = ', '.join(sorted_logging_categories)
+ logging_help = self.nodes[0].help('logging')
+ assert f"valid logging categories are: {categories}" in logging_help
+
self.log.info("test echoipc (testing spawned process in multiprocess build)")
assert_equal(node.echoipc("hello"), "hello")
diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/rpc_signrawtransaction.py
index f3627d1e37..571029155e 100755
--- a/test/functional/rpc_signrawtransaction.py
+++ b/test/functional/rpc_signrawtransaction.py
@@ -4,7 +4,11 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test transaction signing using the signrawtransaction* RPCs."""
-from test_framework.blocktools import COINBASE_MATURITY
+from test_framework.blocktools import (
+ CLTV_HEIGHT,
+ COINBASE_MATURITY,
+ CSV_ACTIVATION_HEIGHT,
+)
from test_framework.address import (
script_to_p2sh,
script_to_p2wsh,
@@ -15,6 +19,7 @@ from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
find_vout_for_address,
+ generate_to_height,
hex_str_to_bytes,
)
from test_framework.messages import (
@@ -270,7 +275,8 @@ class SignRawTransactionsTest(BitcoinTestFramework):
getcontext().prec = 8
# Make sure CSV is active
- self.nodes[0].generate(500)
+ generate_to_height(self.nodes[0], CSV_ACTIVATION_HEIGHT)
+ assert self.nodes[0].getblockchaininfo()['softforks']['csv']['active']
# Create a P2WSH script with CSV
script = CScript([1, OP_CHECKSEQUENCEVERIFY, OP_DROP])
@@ -304,8 +310,9 @@ class SignRawTransactionsTest(BitcoinTestFramework):
self.nodes[0].walletpassphrase("password", 9999)
getcontext().prec = 8
- # Make sure CSV is active
- self.nodes[0].generate(1500)
+ # Make sure CLTV is active
+ generate_to_height(self.nodes[0], CLTV_HEIGHT)
+ assert self.nodes[0].getblockchaininfo()['softforks']['bip65']['active']
# Create a P2WSH script with CLTV
script = CScript([1000, OP_CHECKLOCKTIMEVERIFY, OP_DROP])
diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py
index 833a215993..2ab720aafb 100644
--- a/test/functional/test_framework/blocktools.py
+++ b/test/functional/test_framework/blocktools.py
@@ -55,6 +55,10 @@ TIME_GENESIS_BLOCK = 1296688602
# Coinbase transaction outputs can only be spent after this number of new blocks (network rule)
COINBASE_MATURITY = 100
+# Soft-fork activation heights
+CLTV_HEIGHT = 1351
+CSV_ACTIVATION_HEIGHT = 432
+
# From BIP141
WITNESS_COMMITMENT_HEADER = b"\xaa\x21\xa9\xed"
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 35dbfbba8d..fcaf3b2c29 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -559,6 +559,17 @@ def mine_large_block(node, utxos=None):
node.generate(1)
+def generate_to_height(node, target_height):
+ """Generates blocks until a given target block height has been reached.
+ To prevent timeouts, only up to 200 blocks are generated per RPC call.
+ Can be used to activate certain soft-forks (e.g. CSV, CLTV)."""
+ current_height = node.getblockcount()
+ while current_height < target_height:
+ nblocks = min(200, target_height - current_height)
+ current_height += len(node.generate(nblocks))
+ assert_equal(node.getblockcount(), target_height)
+
+
def find_vout_for_address(node, txid, addr):
"""
Locate the vout index of the given transaction sending to the
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 32da2202db..0a73891f2a 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -109,8 +109,6 @@ BASE_SCRIPTS = [
'p2p_tx_download.py',
'mempool_updatefromblock.py',
'wallet_dump.py --legacy-wallet',
- 'wallet_listtransactions.py --legacy-wallet',
- 'wallet_listtransactions.py --descriptors',
'feature_taproot.py --previous_release',
'feature_taproot.py',
'rpc_signer.py',
@@ -159,6 +157,8 @@ BASE_SCRIPTS = [
'wallet_createwallet.py --legacy-wallet',
'wallet_createwallet.py --usecli',
'wallet_createwallet.py --descriptors',
+ 'wallet_listtransactions.py --legacy-wallet',
+ 'wallet_listtransactions.py --descriptors',
'wallet_watchonly.py --legacy-wallet',
'wallet_watchonly.py --usecli --legacy-wallet',
'wallet_reorgsrestore.py',
diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py
index 8b503f5971..c0386f5d70 100755
--- a/test/functional/wallet_listtransactions.py
+++ b/test/functional/wallet_listtransactions.py
@@ -18,14 +18,15 @@ from test_framework.util import (
class ListTransactionsTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
+ # This test isn't testing txn relay/timing, so set whitelist on the
+ # peers for instant txn relay. This speeds up the test run time 2-3x.
+ self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
- self.nodes[0].generate(1) # Get out of IBD
- self.sync_all()
- # Simple send, 0 to 1:
+ self.log.info("Test simple send from node0 to node1")
txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)
self.sync_all()
assert_array_result(self.nodes[0].listtransactions(),
@@ -34,7 +35,7 @@ class ListTransactionsTest(BitcoinTestFramework):
assert_array_result(self.nodes[1].listtransactions(),
{"txid": txid},
{"category": "receive", "amount": Decimal("0.1"), "confirmations": 0})
- # mine a block, confirmations should change:
+ self.log.info("Test confirmations change after mining a block")
blockhash = self.nodes[0].generate(1)[0]
blockheight = self.nodes[0].getblockheader(blockhash)['height']
self.sync_all()
@@ -45,7 +46,7 @@ class ListTransactionsTest(BitcoinTestFramework):
{"txid": txid},
{"category": "receive", "amount": Decimal("0.1"), "confirmations": 1, "blockhash": blockhash, "blockheight": blockheight})
- # send-to-self:
+ self.log.info("Test send-to-self on node0")
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 0.2)
assert_array_result(self.nodes[0].listtransactions(),
{"txid": txid, "category": "send"},
@@ -54,7 +55,7 @@ class ListTransactionsTest(BitcoinTestFramework):
{"txid": txid, "category": "receive"},
{"amount": Decimal("0.2")})
- # sendmany from node1: twice to self, twice to node2:
+ self.log.info("Test sendmany from node1: twice to self, twice to node0")
send_to = {self.nodes[0].getnewaddress(): 0.11,
self.nodes[1].getnewaddress(): 0.22,
self.nodes[0].getnewaddress(): 0.33,
@@ -88,6 +89,7 @@ class ListTransactionsTest(BitcoinTestFramework):
if not self.options.descriptors:
# include_watchonly is a legacy wallet feature, so don't test it for descriptor wallets
+ self.log.info("Test 'include_watchonly' feature (legacy wallet)")
pubkey = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['pubkey']
multisig = self.nodes[1].createmultisig(1, [pubkey])
self.nodes[0].importaddress(multisig["redeemScript"], "watchonly", False, True)
@@ -103,37 +105,38 @@ class ListTransactionsTest(BitcoinTestFramework):
self.run_rbf_opt_in_test()
- # Check that the opt-in-rbf flag works properly, for sent and received
- # transactions.
+
def run_rbf_opt_in_test(self):
- # Check whether a transaction signals opt-in RBF itself
+ """Test the opt-in-rbf flag for sent and received transactions."""
+
def is_opt_in(node, txid):
+ """Check whether a transaction signals opt-in RBF itself."""
rawtx = node.getrawtransaction(txid, 1)
for x in rawtx["vin"]:
if x["sequence"] < 0xfffffffe:
return True
return False
- # Find an unconfirmed output matching a certain txid
def get_unconfirmed_utxo_entry(node, txid_to_match):
+ """Find an unconfirmed output matching a certain txid."""
utxo = node.listunspent(0, 0)
for i in utxo:
if i["txid"] == txid_to_match:
return i
return None
- # 1. Chain a few transactions that don't opt-in.
+ self.log.info("Test txs w/o opt-in RBF (bip125-replaceable=no)")
+ # Chain a few transactions that don't opt in.
txid_1 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1)
assert not is_opt_in(self.nodes[0], txid_1)
assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_1}, {"bip125-replaceable": "no"})
self.sync_mempools()
assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_1}, {"bip125-replaceable": "no"})
- # Tx2 will build off txid_1, still not opting in to RBF.
+ # Tx2 will build off tx1, still not opting in to RBF.
utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[0], txid_1)
assert_equal(utxo_to_use["safe"], True)
utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_1)
- utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_1)
assert_equal(utxo_to_use["safe"], False)
# Create tx2 using createrawtransaction
@@ -149,6 +152,7 @@ class ListTransactionsTest(BitcoinTestFramework):
self.sync_mempools()
assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_2}, {"bip125-replaceable": "no"})
+ self.log.info("Test txs with opt-in RBF (bip125-replaceable=yes)")
# Tx3 will opt-in to RBF
utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[0], txid_2)
inputs = [{"txid": txid_2, "vout": utxo_to_use["vout"]}]
@@ -179,6 +183,7 @@ class ListTransactionsTest(BitcoinTestFramework):
self.sync_mempools()
assert_array_result(self.nodes[0].listtransactions(), {"txid": txid_4}, {"bip125-replaceable": "yes"})
+ self.log.info("Test tx with unknown RBF state (bip125-replaceable=unknown)")
# Replace tx3, and check that tx4 becomes unknown
tx3_b = tx3_modified
tx3_b.vout[0].nValue -= int(Decimal("0.004") * COIN) # bump the fee
@@ -191,7 +196,7 @@ class ListTransactionsTest(BitcoinTestFramework):
self.sync_mempools()
assert_array_result(self.nodes[1].listtransactions(), {"txid": txid_4}, {"bip125-replaceable": "unknown"})
- # Check gettransaction as well:
+ self.log.info("Test bip125-replaceable status with gettransaction RPC")
for n in self.nodes[0:2]:
assert_equal(n.gettransaction(txid_1)["bip125-replaceable"], "no")
assert_equal(n.gettransaction(txid_2)["bip125-replaceable"], "no")
@@ -199,7 +204,7 @@ class ListTransactionsTest(BitcoinTestFramework):
assert_equal(n.gettransaction(txid_3b)["bip125-replaceable"], "yes")
assert_equal(n.gettransaction(txid_4)["bip125-replaceable"], "unknown")
- # After mining a transaction, it's no longer BIP125-replaceable
+ self.log.info("Test mined transactions are no longer bip125-replaceable")
self.nodes[0].generate(1)
assert txid_3b not in self.nodes[0].getrawmempool()
assert_equal(self.nodes[0].gettransaction(txid_3b)["bip125-replaceable"], "no")
diff --git a/test/get_previous_releases.py b/test/get_previous_releases.py
index 01e4ef47a7..e92bb402b5 100755
--- a/test/get_previous_releases.py
+++ b/test/get_previous_releases.py
@@ -112,7 +112,11 @@ def download_binary(tag, args) -> int:
tarballHash = hasher.hexdigest()
if tarballHash not in SHA256_SUMS or SHA256_SUMS[tarballHash] != tarball:
- print("Checksum did not match")
+ if tarball in SHA256_SUMS.values():
+ print("Checksum did not match")
+ return 1
+
+ print("Checksum for given version doesn't exist")
return 1
print("Checksum matched")
diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh
index f8f24bb1ff..df5051720b 100755
--- a/test/lint/lint-circular-dependencies.sh
+++ b/test/lint/lint-circular-dependencies.sh
@@ -10,7 +10,6 @@ export LC_ALL=C
EXPECTED_CIRCULAR_DEPENDENCIES=(
"chainparamsbase -> util/system -> chainparamsbase"
- "index/txindex -> validation -> index/txindex"
"node/blockstorage -> validation -> node/blockstorage"
"index/blockfilterindex -> node/blockstorage -> validation -> index/blockfilterindex"
"index/base -> validation -> index/blockfilterindex -> index/base"