diff --git a/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj b/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj index 96bb5843758b3..6c45d4dbd8a0f 100644 --- a/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj +++ b/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj @@ -24,6 +24,7 @@ + @@ -78,6 +79,7 @@ + diff --git a/build_msvc/test_bitcoin/test_bitcoin.vcxproj b/build_msvc/test_bitcoin/test_bitcoin.vcxproj index 5c4b777d5164b..bb1a780bfab12 100644 --- a/build_msvc/test_bitcoin/test_bitcoin.vcxproj +++ b/build_msvc/test_bitcoin/test_bitcoin.vcxproj @@ -16,6 +16,7 @@ + diff --git a/ci/test/00_setup_env_android.sh b/ci/test/00_setup_env_android.sh index f78a84eeac47d..4ef3ae1ceb97d 100755 --- a/ci/test/00_setup_env_android.sh +++ b/ci/test/00_setup_env_android.sh @@ -16,7 +16,7 @@ export RUN_FUNCTIONAL_TESTS=false export ANDROID_API_LEVEL=28 export ANDROID_BUILD_TOOLS_VERSION=28.0.3 -export ANDROID_NDK_VERSION=21.1.6352462 +export ANDROID_NDK_VERSION=22.1.7171670 export ANDROID_TOOLS_URL=https://dl.google.com/android/repository/commandlinetools-linux-6609375_latest.zip export ANDROID_HOME="${DEPENDS_DIR}/SDKs/android" export ANDROID_NDK_HOME="${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}" diff --git a/contrib/guix/INSTALL.md b/contrib/guix/INSTALL.md index 86f91cc87bf0b..63aa3e02b2cc0 100644 --- a/contrib/guix/INSTALL.md +++ b/contrib/guix/INSTALL.md @@ -79,8 +79,8 @@ Guix v1.2.0 is available as a distribution package starting in [Debian 21.04](https://packages.ubuntu.com/hirsute/guix). Note that if you intend on using Guix without using any substitutes (more -details [here][security-model]), v1.2.0 has a known problems when building -GnuTLS from source. Solutions and workarounds are documented +details [here][security-model]), v1.2.0 has a known problem when building GnuTLS +from source. Solutions and workarounds are documented [here](#gnutls-test-suite-fail-status-request-revoked). @@ -124,7 +124,7 @@ particular commit of Guix). Previous experience with using autotools-style build systems to build packages from source will be helpful. *hic sunt dracones.* I strongly urge you to at least skim through the entire section once before you -start issuing commands, as it will save you a lot of unncessary pain and +start issuing commands, as it will save you a lot of unnecessary pain and anguish. ### Installing common build tools @@ -165,7 +165,7 @@ packaged and installable without manually building and installing. For reference, the graphic below outlines Guix v1.3.0's dependency graph: -![boostrap map](https://user-images.githubusercontent.com/6399679/125064185-a9a59880-e0b0-11eb-82c1-9b8e5dc9950d.png) +![bootstrap map](https://user-images.githubusercontent.com/6399679/125064185-a9a59880-e0b0-11eb-82c1-9b8e5dc9950d.png) #### Guile @@ -270,23 +270,11 @@ Note that these environment variables are used to check for packages during `./configure`, so they should be set as soon as possible should you want to use a prefix other than `/usr`. - - - - - - - - - - - - #### Building and installing source-built packages -***IMPORTANT**: A few dependencies have non-obvious quirks/erratas which are documented in the -sub-sections immediately below. Please read these sections before proceeding to -build and install these packages.* +***IMPORTANT**: A few dependencies have non-obvious quirks/errata which are +documented in the sub-sections immediately below. Please read these sections +before proceeding to build and install these packages.* Although you should always refer to the README or INSTALL files for the most accurate information, most of these dependencies use autoconf-style build diff --git a/contrib/guix/guix-build b/contrib/guix/guix-build index f6da8435e99e4..5a908a507d068 100755 --- a/contrib/guix/guix-build +++ b/contrib/guix/guix-build @@ -190,7 +190,7 @@ fi # Services database must have basic entries ################ -if ! getent services http https ftp; then +if ! getent services http https ftp > /dev/null 2>&1; then cat << EOF ERR: Your system's C library can not find service database entries for at least one of the following services: http, https, ftp. diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index bc3391e0896e2..356bd700701ed 100755 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -227,7 +227,6 @@ GIT_ARCHIVE="${DIST_ARCHIVE_BASE}/${DISTNAME}.tar.gz" # Create the source tarball if not already there if [ ! -e "$GIT_ARCHIVE" ]; then mkdir -p "$(dirname "$GIT_ARCHIVE")" - touch "${DIST_ARCHIVE_BASE}"/SKIPATTEST.TAG git archive --prefix="${DISTNAME}/" --output="$GIT_ARCHIVE" HEAD fi diff --git a/contrib/install_db4.sh b/contrib/install_db4.sh index 4037936404bae..dd4d862dee504 100755 --- a/contrib/install_db4.sh +++ b/contrib/install_db4.sh @@ -221,10 +221,10 @@ EOF # The packaged config.guess and config.sub are ancient (2009) and can cause build issues. # Replace them with modern versions. # See https://github.com/bitcoin/bitcoin/issues/16064 -CONFIG_GUESS_URL='https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=55eaf3e779455c4e5cc9f82efb5278be8f8f900b' -CONFIG_GUESS_HASH='2d1ff7bca773d2ec3c6217118129220fa72d8adda67c7d2bf79994b3129232c1' -CONFIG_SUB_URL='https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=55eaf3e779455c4e5cc9f82efb5278be8f8f900b' -CONFIG_SUB_HASH='3a4befde9bcdf0fdb2763fc1bfa74e8696df94e1ad7aac8042d133c8ff1d2e32' +CONFIG_GUESS_URL='https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=4550d2f15b3a7ce2451c1f29500b9339430c877f' +CONFIG_GUESS_HASH='c8f530e01840719871748a8071113435bdfdf75b74c57e78e47898edea8754ae' +CONFIG_SUB_URL='https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=4550d2f15b3a7ce2451c1f29500b9339430c877f' +CONFIG_SUB_HASH='3969f7d5f6967ccc6f792401b8ef3916a1d1b1d0f0de5a4e354c95addb8b800e' rm -f "dist/config.guess" rm -f "dist/config.sub" diff --git a/contrib/tracing/README.md b/contrib/tracing/README.md new file mode 100644 index 0000000000000..047354cda17b1 --- /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 0000000000000..d268eff7f8b8c --- /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 + + - 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. + - 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. + - sets the height after which the benchmark should end. Setting + the end height to 0 disables the benchmark. The script only logs blocks + over . + - Threshold + + 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 + . +*/ +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 0000000000000..f62956aa5e1c3 --- /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 0000000000000..b5b57556324e3 --- /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 + +#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 0000000000000..14e3e3a80123a --- /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 + +// 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 6cbaf2dc0c7c0..51c151add826f 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/depends/packages/libevent.mk b/depends/packages/libevent.mk index dad317193c024..0af5412d94c4e 100644 --- a/depends/packages/libevent.mk +++ b/depends/packages/libevent.mk @@ -16,6 +16,10 @@ define $(package)_set_vars $(package)_cppflags_mingw32=-D_WIN32_WINNT=0x0601 endef +define $(package)_preprocess_cmds + cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub build-aux +endef + define $(package)_config_cmds $($(package)_autoconf) endef diff --git a/depends/packages/native_cctools.mk b/depends/packages/native_cctools.mk index 885207fce9af5..d169eb6723184 100644 --- a/depends/packages/native_cctools.mk +++ b/depends/packages/native_cctools.mk @@ -16,6 +16,10 @@ define $(package)_set_vars $(package)_cxx=$(clangxx_prog) endef +define $(package)_preprocess_cmds + cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub cctools +endef + define $(package)_config_cmds $($(package)_autoconf) endef diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 0c3ef8c82f1f5..9004b064d6e9e 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -9,7 +9,7 @@ $(package)_qt_libs=corelib network widgets gui plugins testlib $(package)_linguist_tools = lrelease lupdate lconvert $(package)_patches = qt.pro qttools_src.pro $(package)_patches += fix_qt_pkgconfig.patch mac-qmake.conf fix_no_printer.patch no-xlib.patch -$(package)_patches+= fix_android_qmake_conf.patch fix_android_jni_static.patch dont_hardcode_pwd.patch +$(package)_patches += support_new_android_ndks.patch fix_android_jni_static.patch dont_hardcode_pwd.patch $(package)_patches+= no_sdk_version_check.patch $(package)_patches+= fix_lib_paths.patch fix_android_pch.patch $(package)_patches+= qtbase-moc-ignore-gcc-macro.patch fix_limits_header.patch @@ -224,7 +224,7 @@ define $(package)_preprocess_cmds patch -p1 -i $($(package)_patch_dir)/dont_hardcode_pwd.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_qt_pkgconfig.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_no_printer.patch && \ - patch -p1 -i $($(package)_patch_dir)/fix_android_qmake_conf.patch && \ + patch -p1 -i $($(package)_patch_dir)/support_new_android_ndks.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_android_jni_static.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_android_pch.patch && \ patch -p1 -i $($(package)_patch_dir)/no-xlib.patch && \ diff --git a/depends/packages/sqlite.mk b/depends/packages/sqlite.mk index 5b3a61b239c64..af5e0d09c9dc8 100644 --- a/depends/packages/sqlite.mk +++ b/depends/packages/sqlite.mk @@ -9,6 +9,10 @@ $(package)_config_opts=--disable-shared --disable-readline --disable-dynamic-ext $(package)_config_opts_linux=--with-pic endef +define $(package)_preprocess_cmds + cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub . +endef + define $(package)_config_cmds $($(package)_autoconf) endef diff --git a/depends/patches/qt/fix_android_pch.patch b/depends/patches/qt/fix_android_pch.patch index bed6e4bb63597..195e1c5e592a1 100644 --- a/depends/patches/qt/fix_android_pch.patch +++ b/depends/patches/qt/fix_android_pch.patch @@ -1,6 +1,6 @@ --- old/qtbase/mkspecs/common/android-base-head.conf +++ new/qtbase/mkspecs/common/android-base-head.conf -@@ -73,6 +73,6 @@ CROSS_COMPILE = $$NDK_TOOLCHAIN_PATH/bin/$$NDK_TOOLS_PREFIX- +@@ -72,6 +72,6 @@ CROSS_COMPILE = $$NDK_TOOLCHAIN_PATH/bin/$$NDK_TOOLS_PREFIX- QMAKE_PCH_OUTPUT_EXT = .gch QMAKE_CFLAGS_PRECOMPILE = -x c-header -c ${QMAKE_PCH_INPUT} -o ${QMAKE_PCH_OUTPUT} diff --git a/depends/patches/qt/fix_android_qmake_conf.patch b/depends/patches/qt/fix_android_qmake_conf.patch deleted file mode 100644 index 3a8753fd1d74c..0000000000000 --- a/depends/patches/qt/fix_android_qmake_conf.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- old/qtbase/mkspecs/android-clang/qmake.conf -+++ new/qtbase/mkspecs/android-clang/qmake.conf -@@ -47,7 +47,7 @@ ANDROID_STDCPP_PATH = $$ANDROID_SOURCES_CXX_STL_LIBDIR/libc++_shared.so - ANDROID_USE_LLVM = true - - exists($$ANDROID_SOURCES_CXX_STL_LIBDIR/libc++.so): \ -- ANDROID_CXX_STL_LIBS = -lc++ -+ ANDROID_CXX_STL_LIBS = -lc++_shared - else: \ - ANDROID_CXX_STL_LIBS = $$ANDROID_SOURCES_CXX_STL_LIBDIR/libc++.so.$$replace(ANDROID_PLATFORM, "android-", "") diff --git a/depends/patches/qt/support_new_android_ndks.patch b/depends/patches/qt/support_new_android_ndks.patch new file mode 100644 index 0000000000000..85c8ae2132af5 --- /dev/null +++ b/depends/patches/qt/support_new_android_ndks.patch @@ -0,0 +1,122 @@ +Follow Google's BuildSystemMaintainers doc to support future NDK releases. + +Upstream commit: + - Qt 5.14: 9b14950ff600a4ce5a8698b67ab38907c50417f1 + +--- old/qtbase/mkspecs/android-clang/qmake.conf ++++ new/qtbase/mkspecs/android-clang/qmake.conf +@@ -14,43 +14,29 @@ + QMAKE_CC = $$NDK_LLVM_PATH/bin/clang + QMAKE_CXX = $$NDK_LLVM_PATH/bin/clang++ + ++# Follow https://android.googlesource.com/platform/ndk/+/ndk-release-r20/docs/BuildSystemMaintainers.md ++ + equals(ANDROID_TARGET_ARCH, armeabi-v7a): \ +- QMAKE_CFLAGS += -target armv7-none-linux-androideabi +-else: equals(ANDROID_TARGET_ARCH, armeabi): \ +- QMAKE_CFLAGS += -target armv5te-none-linux-androideabi ++ QMAKE_CFLAGS = -target armv7a-linux-androideabi$$replace(ANDROID_PLATFORM, "android-", "") + else: equals(ANDROID_TARGET_ARCH, arm64-v8a): \ +- QMAKE_CFLAGS += -target aarch64-none-linux-android ++ QMAKE_CFLAGS = -target aarch64-linux-android$$replace(ANDROID_PLATFORM, "android-", "") + else: equals(ANDROID_TARGET_ARCH, x86): \ +- QMAKE_CFLAGS += -target i686-none-linux-android -mstackrealign ++ QMAKE_CFLAGS = -target i686-linux-android$$replace(ANDROID_PLATFORM, "android-", "") -mstackrealign + else: equals(ANDROID_TARGET_ARCH, x86_64): \ +- QMAKE_CFLAGS += -target x86_64-none-linux-android +-else: equals(ANDROID_TARGET_ARCH, mips): \ +- QMAKE_CFLAGS += -target mipsel-none-linux-android +-else: equals(ANDROID_TARGET_ARCH, mips64): \ +- QMAKE_CFLAGS += -target mips64el-none-linux-android +- +-QMAKE_CFLAGS += -gcc-toolchain $$NDK_TOOLCHAIN_PATH -fno-limit-debug-info +- +-QMAKE_LINK = $$QMAKE_CXX $$QMAKE_CFLAGS -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libatomic.a -nostdlib++ +-equals(ANDROID_TARGET_ARCH, armeabi-v7a): QMAKE_LINK += -Wl,--exclude-libs,libunwind.a +- +-QMAKE_CFLAGS += -DANDROID_HAS_WSTRING --sysroot=$$NDK_ROOT/sysroot \ +- -isystem $$NDK_ROOT/sysroot/usr/include/$$NDK_TOOLS_PREFIX \ +- -isystem $$NDK_ROOT/sources/cxx-stl/llvm-libc++/include \ +- -isystem $$NDK_ROOT/sources/android/support/include \ +- -isystem $$NDK_ROOT/sources/cxx-stl/llvm-libc++abi/include ++ QMAKE_CFLAGS = -target x86_64-linux-android$$replace(ANDROID_PLATFORM, "android-", "") + +-ANDROID_SOURCES_CXX_STL_LIBDIR = $$NDK_ROOT/sources/cxx-stl/llvm-libc++/libs/$$ANDROID_TARGET_ARCH ++QMAKE_CFLAGS += -fno-limit-debug-info + +-ANDROID_STDCPP_PATH = $$ANDROID_SOURCES_CXX_STL_LIBDIR/libc++_shared.so ++QMAKE_LINK = $$QMAKE_CXX $$QMAKE_CFLAGS + +-ANDROID_USE_LLVM = true ++ANDROID_STDCPP_PATH = $$NDK_LLVM_PATH/sysroot/usr/lib/$$NDK_TOOLS_PREFIX/libc++_shared.so + +-exists($$ANDROID_SOURCES_CXX_STL_LIBDIR/libc++.so): \ +- ANDROID_CXX_STL_LIBS = -lc++ +-else: \ +- ANDROID_CXX_STL_LIBS = $$ANDROID_SOURCES_CXX_STL_LIBDIR/libc++.so.$$replace(ANDROID_PLATFORM, "android-", "") ++ANDROID_USE_LLVM = true + +-QMAKE_CFLAGS_OPTIMIZE_SIZE = -Oz ++QMAKE_CFLAGS_OPTIMIZE_SIZE = -Oz ++QMAKE_LIBDIR_POST = ++QMAKE_LFLAGS = ++QMAKE_LIBS_PRIVATE = ++ANDROID_CXX_STL_LIBS = + + include(../common/android-base-tail.conf) + +--- old/qtbase/mkspecs/common/android-base-head.conf ++++ new/qtbase/mkspecs/common/android-base-head.conf +@@ -64,7 +58,6 @@ + } + + CONFIG += $$ANDROID_PLATFORM +-QMAKE_CFLAGS = -D__ANDROID_API__=$$replace(ANDROID_PLATFORM, "android-", "") + + ANDROID_PLATFORM_ROOT_PATH = $$NDK_ROOT/platforms/$$ANDROID_PLATFORM/arch-$$ANDROID_ARCHITECTURE/ + +--- old/qtbase/mkspecs/common/android-base-tail.conf ++++ new/qtbase/mkspecs/common/android-base-tail.conf +@@ -6,22 +6,17 @@ + QMAKE_CFLAGS += -fstack-protector-strong -DANDROID + + equals(ANDROID_TARGET_ARCH, armeabi-v7a): \ +- QMAKE_CFLAGS += -march=armv7-a -mfloat-abi=softfp -mfpu=vfp -fno-builtin-memmove ++ QMAKE_CFLAGS += -march=armv7-a -mfloat-abi=softfp -mfpu=vfp + else: equals(ANDROID_TARGET_ARCH, armeabi): \ +- QMAKE_CFLAGS += -march=armv5te -mtune=xscale -msoft-float -fno-builtin-memmove +-# -fno-builtin-memmove is used to workaround https://code.google.com/p/android/issues/detail?id=81692 ++ QMAKE_CFLAGS += -march=armv5te -mtune=xscale -msoft-float + + QMAKE_CFLAGS_WARN_ON = -Wall -W + QMAKE_CFLAGS_WARN_OFF = + equals(ANDROID_TARGET_ARCH, armeabi-v7a) | equals(ANDROID_TARGET_ARCH, armeabi) { + CONFIG += optimize_size + QMAKE_CFLAGS_DEBUG = -g -marm -O0 +- equals(ANDROID_TARGET_ARCH, armeabi):if(equals(NDK_TOOLCHAIN_VERSION, 4.8)|equals(NDK_TOOLCHAIN_VERSION, 4.9)) { +- DEFINES += QT_OS_ANDROID_GCC_48_WORKAROUND +- } else { +- QMAKE_CFLAGS_RELEASE += -mthumb +- QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -mthumb +- } ++ QMAKE_CFLAGS_RELEASE += -mthumb ++ QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -mthumb + } + + QMAKE_CFLAGS_SHLIB = -fPIC +@@ -61,15 +56,12 @@ + QMAKE_RANLIB = $${CROSS_COMPILE}ranlib + + QMAKE_INCDIR_POST = +-QMAKE_LIBDIR_POST = $$ANDROID_SOURCES_CXX_STL_LIBDIR + QMAKE_INCDIR_X11 = + QMAKE_LIBDIR_X11 = + QMAKE_INCDIR_OPENGL = + QMAKE_LIBDIR_OPENGL = + + QMAKE_LINK_SHLIB = $$QMAKE_LINK +-QMAKE_LFLAGS = --sysroot=$$ANDROID_PLATFORM_ROOT_PATH +-equals(ANDROID_TARGET_ARCH, x86_64) QMAKE_LFLAGS += -L$$ANDROID_PLATFORM_ROOT_PATH/usr/lib64 + QMAKE_LFLAGS_APP = -Wl,--no-undefined -Wl,-z,noexecstack -shared + QMAKE_LFLAGS_SHLIB = -Wl,--no-undefined -Wl,-z,noexecstack -shared + QMAKE_LFLAGS_PLUGIN = $$QMAKE_LFLAGS_SHLIB diff --git a/doc/build-unix.md b/doc/build-unix.md index 73c0bf877950e..4a56114109e49 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 66c5a76b3b8b2..b7634718e8d61 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/release-notes.md b/doc/release-notes.md index dc28ccb9edd5f..cf9edd9b0865a 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,5 +1,3 @@ -# Release notes now being edited on https://github.com/bitcoin-core/bitcoin-devwiki/wiki/22.0-Release-Notes-draft - *After branching off for a major version release of Bitcoin Core, use this template to create the initial release notes draft.* @@ -8,7 +6,7 @@ template to create the initial release notes draft.* for the process.* *Create the draft, named* "*version* Release Notes Draft" -*(e.g. "0.20.0 Release Notes Draft"), as a collaborative wiki in:* +*(e.g. "22.0 Release Notes Draft"), as a collaborative wiki in:* https://github.com/bitcoin-core/bitcoin-devwiki/wiki/ @@ -53,90 +51,15 @@ Core should also work on most other Unix-like systems but is not as frequently tested on them. It is not recommended to use Bitcoin Core on unsupported systems. -From Bitcoin Core 22.0 onwards, macOS versions earlier than 10.14 are no longer supported. - Notable changes =============== P2P and network changes ----------------------- -- This release removes support for Tor version 2 hidden services in favor of Tor - v3 only, as the Tor network [dropped support for Tor - v2](https://blog.torproject.org/v2-deprecation-timeline) with the release of - Tor version 0.4.6. Henceforth, Bitcoin Core ignores Tor v2 addresses; it - neither rumors them over the network to other peers, nor stores them in memory - or to `peers.dat`. (#22050) - -- Added NAT-PMP port mapping support via - [`libnatpmp`](https://miniupnp.tuxfamily.org/libnatpmp.html). (#18077) - Updated RPCs ------------ -- Due to [BIP 350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki) - being implemented, behavior for all RPCs that accept addresses is changed when - a native witness version 1 (or higher) is passed. These now require a Bech32m - encoding instead of a Bech32 one, and Bech32m encoding will be used for such - addresses in RPC output as well. No version 1 addresses should be created - for mainnet until consensus rules are adopted that give them meaning - (e.g. through [BIP 341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki)). - Once that happens, Bech32m is expected to be used for them, so this shouldn't - affect any production systems, but may be observed on other networks where such - addresses already have meaning (like signet). (#20861) - -- The `getpeerinfo` RPC returns two new boolean fields, `bip152_hb_to` and - `bip152_hb_from`, that respectively indicate whether we selected a peer to be - in compact blocks high-bandwidth mode or whether a peer selected us as a - compact blocks high-bandwidth peer. High-bandwidth peers send new block - announcements via a `cmpctblock` message rather than the usual inv/headers - announcements. See BIP 152 for more details. (#19776) - -- `getpeerinfo` no longer returns the following fields: `addnode`, `banscore`, - and `whitelisted`, which were previously deprecated in 0.21. Instead of - `addnode`, the `connection_type` field returns manual. Instead of - `whitelisted`, the `permissions` field indicates if the peer has special - privileges. The `banscore` field has simply been removed. (#20755) - -- The following RPCs: `gettxout`, `getrawtransaction`, `decoderawtransaction`, - `decodescript`, `gettransaction`, and REST endpoints: `/rest/tx`, - `/rest/getutxos`, `/rest/block` deprecated the following fields (which are no - longer returned in the responses by default): `addresses`, `reqSigs`. - The `-deprecatedrpc=addresses` flag must be passed for these fields to be - included in the RPC response. This flag/option will be available only for this major release, after which - the deprecation will be removed entirely. Note that these fields are attributes of - the `scriptPubKey` object returned in the RPC response. However, in the response - of `decodescript` these fields are top-level attributes, and included again as attributes - of the `scriptPubKey` object. (#20286) - -- When creating a hex-encoded bitcoin transaction using the `bitcoin-tx` utility - with the `-json` option set, the following fields: `addresses`, `reqSigs` are no longer - returned in the tx output of the response. (#20286) - -- The `listbanned` RPC now returns two new numeric fields: `ban_duration` and `time_remaining`. - Respectively, these new fields indicate the duration of a ban and the time remaining until a ban expires, - both in seconds. Additionally, the `ban_created` field is repositioned to come before `banned_until`. (#21602) - -- The `getnodeaddresses` RPC now returns a "network" field indicating the - network type (ipv4, ipv6, onion, or i2p) for each address. (#21594) - -- `getnodeaddresses` now also accepts a "network" argument (ipv4, ipv6, onion, - or i2p) to return only addresses of the specified network. (#21843) - -- The `testmempoolaccept` RPC now accepts multiple transactions (still experimental at the moment, - API may be unstable). This is intended for testing transaction packages with dependency - relationships; it is not recommended for batch-validating independent transactions. In addition to - mempool policy, package policies apply: the list cannot contain more than 25 transactions or have a - total size exceeding 101K virtual bytes, and cannot conflict with (spend the same inputs as) each other or - the mempool, even if it would be a valid BIP125 replace-by-fee. There are some known limitations to - the accuracy of the test accept: it's possible for `testmempoolaccept` to return "allowed"=True for a - group of transactions, but "too-long-mempool-chain" if they are actually submitted. (#20833) - -- `addmultisigaddress` and `createmultisig` now support up to 20 keys for - Segwit addresses. (#20867) - -Changes to Wallet or GUI related RPCs can be found in the GUI or Wallet section below. - New RPCs -------- @@ -146,48 +69,17 @@ Build System New settings ------------ -- The `-natpmp` option has been added to use NAT-PMP to map the listening port. - If both UPnP and NAT-PMP are enabled, a successful allocation from UPnP - prevails over one from NAT-PMP. (#18077) - Updated settings ---------------- -Changes to Wallet or GUI related settings can be found in the GUI or Wallet section below. - -- Passing an invalid `-rpcauth` argument now cause bitcoind to fail to start. (#20461) - Tools and Utilities ------------------- -- A new CLI `-addrinfo` command returns the number of addresses known to the - node per network type (including Tor v2 versus v3) and total. This can be - useful to see if the node knows enough addresses in a network to use options - like `-onlynet=` or to upgrade to this release of Bitcoin Core 22.0 - that supports Tor v3 only. (#21595) - -- A new `-rpcwaittimeout` argument to `bitcoin-cli` sets the timeout - in seconds to use with `-rpcwait`. If the timeout expires, - `bitcoin-cli` will report a failure. (#21056) +- Update `-getinfo` to return data in a user-friendly format that also reduces vertical space. (#21832) Wallet ------ -- A new `listdescriptors` RPC is available to inspect the contents of descriptor-enabled wallets. - The RPC returns public versions of all imported descriptors, including their timestamp and flags. - For ranged descriptors, it also returns the range boundaries and the next index to generate addresses from. (#20226) - -- The `bumpfee` RPC is not available with wallets that have private keys - disabled. `psbtbumpfee` can be used instead. (#20891) - -- The `fundrawtransaction`, `send` and `walletcreatefundedpsbt` RPCs now support an `include_unsafe` option - that when `true` allows using unsafe inputs to fund the transaction. - Note that the resulting transaction may become invalid if one of the unsafe inputs disappears. - If that happens, the transaction must be funded with different inputs and republished. (#21359) - -- We now support up to 20 keys in `multi()` and `sortedmulti()` descriptors - under `wsh()`. (#20867) - GUI changes ----------- @@ -197,18 +89,7 @@ Low-level changes RPC --- -- The RPC server can process a limited number of simultaneous RPC requests. - Previously, if this limit was exceeded, the RPC server would respond with - [status code 500 (`HTTP_INTERNAL_SERVER_ERROR`)](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors). - Now it returns status code 503 (`HTTP_SERVICE_UNAVAILABLE`). (#18335) - -- Error codes have been updated to be more accurate for the following error cases (#18466): - - `signmessage` now returns RPC_INVALID_ADDRESS_OR_KEY (-5) if the - passed address is invalid. Previously returned RPC_TYPE_ERROR (-3). - - `verifymessage` now returns RPC_INVALID_ADDRESS_OR_KEY (-5) if the - passed address is invalid. Previously returned RPC_TYPE_ERROR (-3). - - `verifymessage` now returns RPC_TYPE_ERROR (-3) if the passed signature - is malformed. Previously returned RPC_INVALID_ADDRESS_OR_KEY (-5). +- `getblockchaininfo` now returns a new `time` field, that provides the chain tip time. (#22407) Tests ----- diff --git a/doc/tracing.md b/doc/tracing.md new file mode 100644 index 0000000000000..1242a0d25002b --- /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 ` 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.am b/src/Makefile.am index 407fdf5a8fd47..a8d6591e98b54 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -842,9 +842,11 @@ EXTRA_DIST += $(libbitcoin_ipc_mpgen_input) if BUILD_MULTIPROCESS LIBBITCOIN_IPC=libbitcoin_ipc.a libbitcoin_ipc_a_SOURCES = \ + ipc/capnp/context.h \ ipc/capnp/init-types.h \ ipc/capnp/protocol.cpp \ ipc/capnp/protocol.h \ + ipc/context.h \ ipc/exception.h \ ipc/interfaces.cpp \ ipc/process.cpp \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index a1821cafe3512..6f450bbc74ee2 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -40,9 +40,9 @@ QT_MOC_CPP = \ qt/moc_askpassphrasedialog.cpp \ qt/moc_createwalletdialog.cpp \ qt/moc_bantablemodel.cpp \ + qt/moc_bitcoin.cpp \ qt/moc_bitcoinaddressvalidator.cpp \ qt/moc_bitcoinamountfield.cpp \ - qt/moc_bitcoin.cpp \ qt/moc_bitcoingui.cpp \ qt/moc_bitcoinunits.cpp \ qt/moc_clientmodel.cpp \ @@ -51,6 +51,7 @@ QT_MOC_CPP = \ qt/moc_csvmodelwriter.cpp \ qt/moc_editaddressdialog.cpp \ qt/moc_guiutil.cpp \ + qt/moc_initexecutor.cpp \ qt/moc_intro.cpp \ qt/moc_macdockiconhandler.cpp \ qt/moc_macnotificationhandler.cpp \ @@ -109,9 +110,9 @@ BITCOIN_QT_H = \ qt/addresstablemodel.h \ qt/askpassphrasedialog.h \ qt/bantablemodel.h \ + qt/bitcoin.h \ qt/bitcoinaddressvalidator.h \ qt/bitcoinamountfield.h \ - qt/bitcoin.h \ qt/bitcoingui.h \ qt/bitcoinunits.h \ qt/clientmodel.h \ @@ -122,6 +123,7 @@ BITCOIN_QT_H = \ qt/editaddressdialog.h \ qt/guiconstants.h \ qt/guiutil.h \ + qt/initexecutor.h \ qt/intro.h \ qt/macdockiconhandler.h \ qt/macnotificationhandler.h \ @@ -227,6 +229,7 @@ BITCOIN_QT_BASE_CPP = \ qt/clientmodel.cpp \ qt/csvmodelwriter.cpp \ qt/guiutil.cpp \ + qt/initexecutor.cpp \ qt/intro.cpp \ qt/modaloverlay.cpp \ qt/networkstyle.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index fc2fd80166032..a07a1bb002828 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/addrman.h b/src/addrman.h index 6f081b8dc14e0..1fc64ac07fdc2 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -536,12 +536,12 @@ class CAddrMan } //! Mark an entry as accessible. - void Good(const CService &addr, bool test_before_evict = true, int64_t nTime = GetAdjustedTime()) + void Good(const CService &addr, int64_t nTime = GetAdjustedTime()) EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); Check(); - Good_(addr, test_before_evict, nTime); + Good_(addr, /* test_before_evict */ true, nTime); Check(); } diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 7a5f9455112d7..1ec6411e32cd3 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -28,6 +29,10 @@ #include #include +#ifndef WIN32 +#include +#endif + #include #include #include @@ -48,6 +53,9 @@ static constexpr int8_t UNKNOWN_NETWORK{-1}; /** Default number of blocks to generate for RPC generatetoaddress. */ static const std::string DEFAULT_NBLOCKS = "1"; +/** Default -color setting. */ +static const std::string DEFAULT_COLOR_SETTING{"auto"}; + static void SetupCliArgs(ArgsManager& argsman) { SetupHelpOptions(argsman); @@ -66,6 +74,7 @@ static void SetupCliArgs(ArgsManager& argsman) argsman.AddArg("-netinfo", "Get network peer connection information from the remote server. An optional integer argument from 0 to 4 can be passed for different peers listings (default: 0). Pass \"help\" for detailed help documentation.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); SetupChainParamsBaseOptions(argsman); + argsman.AddArg("-color=", strprintf("Color setting for CLI output (default: %s). Valid values: always, auto (add color codes when standard output is connected to a terminal and OS is not WIN32), never.", DEFAULT_COLOR_SETTING), ArgsManager::ALLOW_STRING, OptionsCategory::OPTIONS); argsman.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcclienttimeout=", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcconnect=", strprintf("Send commands to node running on (default: %s)", DEFAULT_RPCCONNECT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -338,7 +347,9 @@ class GetinfoRequestHandler: public BaseRequestHandler result.pushKV("difficulty", batch[ID_BLOCKCHAININFO]["result"]["difficulty"]); result.pushKV("chain", UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"])); if (!batch[ID_WALLETINFO]["result"].isNull()) { + result.pushKV("has_wallet", true); result.pushKV("keypoolsize", batch[ID_WALLETINFO]["result"]["keypoolsize"]); + result.pushKV("walletname", batch[ID_WALLETINFO]["result"]["walletname"]); if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) { result.pushKV("unlocked_until", batch[ID_WALLETINFO]["result"]["unlocked_until"]); } @@ -873,6 +884,100 @@ static void GetWalletBalances(UniValue& result) result.pushKV("balances", balances); } +/** + * ParseGetInfoResult takes in -getinfo result in UniValue object and parses it + * into a user friendly UniValue string to be printed on the console. + * @param[out] result Reference to UniValue result containing the -getinfo output. + */ +static void ParseGetInfoResult(UniValue& result) +{ + if (!find_value(result, "error").isNull()) return; + + std::string RESET, GREEN, BLUE, YELLOW, MAGENTA, CYAN; + bool should_colorize = false; + +#ifndef WIN32 + if (isatty(fileno(stdout))) { + // By default, only print colored text if OS is not WIN32 and stdout is connected to a terminal. + should_colorize = true; + } +#endif + + if (gArgs.IsArgSet("-color")) { + const std::string color{gArgs.GetArg("-color", DEFAULT_COLOR_SETTING)}; + if (color == "always") { + should_colorize = true; + } else if (color == "never") { + should_colorize = false; + } else if (color != "auto") { + throw std::runtime_error("Invalid value for -color option. Valid values: always, auto, never."); + } + } + + if (should_colorize) { + RESET = "\x1B[0m"; + GREEN = "\x1B[32m"; + BLUE = "\x1B[34m"; + YELLOW = "\x1B[33m"; + MAGENTA = "\x1B[35m"; + CYAN = "\x1B[36m"; + } + + std::string result_string = strprintf("%sChain: %s%s\n", BLUE, result["chain"].getValStr(), RESET); + result_string += strprintf("Blocks: %s\n", result["blocks"].getValStr()); + result_string += strprintf("Headers: %s\n", result["headers"].getValStr()); + result_string += strprintf("Verification progress: %.4f%%\n", result["verificationprogress"].get_real() * 100); + result_string += strprintf("Difficulty: %s\n\n", result["difficulty"].getValStr()); + + result_string += strprintf( + "%sNetwork: in %s, out %s, total %s%s\n", + GREEN, + result["connections"]["in"].getValStr(), + result["connections"]["out"].getValStr(), + result["connections"]["total"].getValStr(), + RESET); + result_string += strprintf("Version: %s\n", result["version"].getValStr()); + result_string += strprintf("Time offset (s): %s\n", result["timeoffset"].getValStr()); + const std::string proxy = result["proxy"].getValStr(); + result_string += strprintf("Proxy: %s\n", proxy.empty() ? "N/A" : proxy); + result_string += strprintf("Min tx relay fee rate (%s/kvB): %s\n\n", CURRENCY_UNIT, result["relayfee"].getValStr()); + + if (!result["has_wallet"].isNull()) { + const std::string walletname = result["walletname"].getValStr(); + result_string += strprintf("%sWallet: %s%s\n", MAGENTA, walletname.empty() ? "\"\"" : walletname, RESET); + + result_string += strprintf("Keypool size: %s\n", result["keypoolsize"].getValStr()); + if (!result["unlocked_until"].isNull()) { + result_string += strprintf("Unlocked until: %s\n", result["unlocked_until"].getValStr()); + } + result_string += strprintf("Transaction fee rate (-paytxfee) (%s/kvB): %s\n\n", CURRENCY_UNIT, result["paytxfee"].getValStr()); + } + if (!result["balance"].isNull()) { + result_string += strprintf("%sBalance:%s %s\n\n", CYAN, RESET, result["balance"].getValStr()); + } + + if (!result["balances"].isNull()) { + result_string += strprintf("%sBalances%s\n", CYAN, RESET); + + size_t max_balance_length{10}; + + for (const std::string& wallet : result["balances"].getKeys()) { + max_balance_length = std::max(result["balances"][wallet].getValStr().length(), max_balance_length); + } + + for (const std::string& wallet : result["balances"].getKeys()) { + result_string += strprintf("%*s %s\n", + max_balance_length, + result["balances"][wallet].getValStr(), + wallet.empty() ? "\"\"" : wallet); + } + result_string += "\n"; + } + + result_string += strprintf("%sWarnings:%s %s", YELLOW, RESET, result["warnings"].getValStr()); + result.setStr(result_string); +} + /** * Call RPC getnewaddress. * @returns getnewaddress response as a UniValue object. @@ -994,9 +1099,13 @@ static int CommandLineRPC(int argc, char *argv[]) UniValue result = find_value(reply, "result"); const UniValue& error = find_value(reply, "error"); if (error.isNull()) { - if (gArgs.IsArgSet("-getinfo") && !gArgs.IsArgSet("-rpcwallet")) { - GetWalletBalances(result); // fetch multiwallet balances and append to result + if (gArgs.GetBoolArg("-getinfo", false)) { + if (!gArgs.IsArgSet("-rpcwallet")) { + GetWalletBalances(result); // fetch multiwallet balances and append to result + } + ParseGetInfoResult(result); } + ParseResult(result, strPrint); } else { ParseError(error, strPrint, nRet); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index eb6c382f23e76..a8b45685d0e5d 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -490,11 +490,8 @@ void CRegTestParams::UpdateActivationParametersFromArgs(const ArgsManager& args) { if (args.IsArgSet("-segwitheight")) { int64_t height = args.GetArg("-segwitheight", consensus.SegwitHeight); - if (height < -1 || height >= std::numeric_limits::max()) { - throw std::runtime_error(strprintf("Activation height %ld for segwit is out of valid range. Use -1 to disable segwit.", height)); - } else if (height == -1) { - LogPrintf("Segwit disabled for testing\n"); - height = std::numeric_limits::max(); + if (height < 0 || height >= std::numeric_limits::max()) { + throw std::runtime_error(strprintf("Activation height %ld for segwit is out of valid range.", height)); } consensus.SegwitHeight = static_cast(height); } diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index e71b4bc859375..79c1bc25bc175 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -20,7 +20,7 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman) argsman.AddArg("-chain=", "Use the chain (default: main). Allowed values: main, test, signet, regtest", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-regtest", "Enter regression test mode, which uses a special chain in which blocks can be solved instantly. " "This is intended for regression testing tools and app development. Equivalent to -chain=regtest.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); - argsman.AddArg("-segwitheight=", "Set the activation height of segwit. -1 to disable. (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-segwitheight=", "Set the activation height of segwit. (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-testnet", "Use the test chain. Equivalent to -chain=test.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-vbparams=deployment:start:end[:min_activation_height]", "Use given start/end times and min_activation_height for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-signet", "Use the signet chain. Equivalent to -chain=signet. Note that the network is defined by the -signetchallenge parameter", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); diff --git a/src/init.cpp b/src/init.cpp index 3915fa8fcbc00..75394d96b1da9 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -715,7 +715,7 @@ namespace { // Variables internal to initialization process only int nMaxConnections; int nUserMaxConnections; int nFD; -ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK | NODE_NETWORK_LIMITED); +ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK | NODE_NETWORK_LIMITED | NODE_WITNESS); int64_t peer_connect_timeout; std::set g_enabled_filter_types; @@ -1588,12 +1588,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } } - if (DeploymentEnabled(chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) { - // Advertise witness capabilities. - // The option to not set NODE_WITNESS is only used in the tests and should be removed. - nLocalServices = ServiceFlags(nLocalServices | NODE_WITNESS); - } - // ********************************************************* Step 11: import blocks if (!CheckDiskSpace(gArgs.GetDataDirNet())) { diff --git a/src/interfaces/ipc.h b/src/interfaces/ipc.h index e9e6c78053d3e..963649fc9ab2e 100644 --- a/src/interfaces/ipc.h +++ b/src/interfaces/ipc.h @@ -9,6 +9,10 @@ #include #include +namespace ipc { +struct Context; +} // namespace ipc + namespace interfaces { class Init; @@ -58,6 +62,9 @@ class Ipc addCleanup(typeid(Interface), &iface, std::move(cleanup)); } + //! IPC context struct accessor (see struct definition for more description). + virtual ipc::Context& context() = 0; + protected: //! Internal implementation of public addCleanup method (above) as a //! type-erased virtual function, since template functions can't be virtual. diff --git a/src/ipc/capnp/context.h b/src/ipc/capnp/context.h new file mode 100644 index 0000000000000..06e16144941ba --- /dev/null +++ b/src/ipc/capnp/context.h @@ -0,0 +1,23 @@ +// 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_IPC_CAPNP_CONTEXT_H +#define BITCOIN_IPC_CAPNP_CONTEXT_H + +#include + +namespace ipc { +namespace capnp { +//! Cap'n Proto context struct. Generally the parent ipc::Context struct should +//! be used instead of this struct to give all IPC protocols access to +//! application state, so there aren't unnecessary differences between IPC +//! protocols. But this specialized struct can be used to pass capnp-specific +//! function and object types to capnp hooks. +struct Context : ipc::Context +{ +}; +} // namespace capnp +} // namespace ipc + +#endif // BITCOIN_IPC_CAPNP_CONTEXT_H diff --git a/src/ipc/capnp/protocol.cpp b/src/ipc/capnp/protocol.cpp index 74c66c899acf4..37b57a95251bd 100644 --- a/src/ipc/capnp/protocol.cpp +++ b/src/ipc/capnp/protocol.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include #include @@ -54,7 +55,7 @@ class CapnpProtocol : public Protocol { assert(!m_loop); mp::g_thread_context.thread_name = mp::ThreadName(exe_name); - m_loop.emplace(exe_name, &IpcLogFn, nullptr); + m_loop.emplace(exe_name, &IpcLogFn, &m_context); mp::ServeStream(*m_loop, fd, init); m_loop->loop(); m_loop.reset(); @@ -63,13 +64,14 @@ class CapnpProtocol : public Protocol { mp::ProxyTypeRegister::types().at(type)(iface).cleanup.emplace_back(std::move(cleanup)); } + Context& context() override { return m_context; } void startLoop(const char* exe_name) { if (m_loop) return; std::promise promise; m_loop_thread = std::thread([&] { util::ThreadRename("capnp-loop"); - m_loop.emplace(exe_name, &IpcLogFn, nullptr); + m_loop.emplace(exe_name, &IpcLogFn, &m_context); { std::unique_lock lock(m_loop->m_mutex); m_loop->addClient(lock); @@ -80,6 +82,7 @@ class CapnpProtocol : public Protocol }); promise.get_future().wait(); } + Context m_context; std::thread m_loop_thread; std::optional m_loop; }; diff --git a/src/ipc/context.h b/src/ipc/context.h new file mode 100644 index 0000000000000..924d7d7450f20 --- /dev/null +++ b/src/ipc/context.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_IPC_CONTEXT_H +#define BITCOIN_IPC_CONTEXT_H + +namespace ipc { +//! Context struct used to give IPC protocol implementations or implementation +//! hooks access to application state, in case they need to run extra code that +//! isn't needed within a single process, like code copying global state from an +//! existing process to a new process when it's initialized, or code dealing +//! with shared objects that are created or destroyed remotely. +struct Context +{ +}; +} // namespace ipc + +#endif // BITCOIN_IPC_CONTEXT_H diff --git a/src/ipc/interfaces.cpp b/src/ipc/interfaces.cpp index ad4b78ed81262..580590fde93e1 100644 --- a/src/ipc/interfaces.cpp +++ b/src/ipc/interfaces.cpp @@ -60,6 +60,7 @@ class IpcImpl : public interfaces::Ipc { m_protocol->addCleanup(type, iface, std::move(cleanup)); } + Context& context() override { return m_protocol->context(); } const char* m_exe_name; const char* m_process_argv0; interfaces::Init& m_init; diff --git a/src/ipc/protocol.h b/src/ipc/protocol.h index af955b000786e..4cd892e411786 100644 --- a/src/ipc/protocol.h +++ b/src/ipc/protocol.h @@ -12,6 +12,8 @@ #include namespace ipc { +struct Context; + //! IPC protocol interface for calling IPC methods over sockets. //! //! There may be different implementations of this interface for different IPC @@ -33,6 +35,9 @@ class Protocol //! Add cleanup callback to interface that will run when the interface is //! deleted. virtual void addCleanup(std::type_index type, void* iface, std::function cleanup) = 0; + + //! Context accessor. + virtual Context& context() = 0; }; } // namespace ipc diff --git a/src/net.cpp b/src/net.cpp index 3a1bb138ab51a..194410c3d403c 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #ifdef WIN32 @@ -658,7 +659,7 @@ bool CNode::ReceiveMsgBytes(Span msg_bytes, bool& complete) i->second += result->m_raw_message_size; // push the message to the process queue, - vRecvMsg.push_back(std::move(*result)); + m_recv_msg_most_recent = vRecvMsg.insert_after(m_recv_msg_most_recent, std::move(*result)); complete = true; } @@ -1576,14 +1577,18 @@ void CConnman::SocketHandler() if (notify) { size_t nSizeAdded = 0; auto it(pnode->vRecvMsg.begin()); - for (; it != pnode->vRecvMsg.end(); ++it) { + // it2 will hold the before end iterator. + auto it2 = it; + for (; it != pnode->vRecvMsg.end(); it2 = it++) { // vRecvMsg contains only completed CNetMessage // the single possible partially deserialized message are held by TransportDeserializer nSizeAdded += it->m_raw_message_size; } { LOCK(pnode->cs_vProcessMsg); - pnode->vProcessMsg.splice(pnode->vProcessMsg.end(), pnode->vRecvMsg, pnode->vRecvMsg.begin(), it); + pnode->vProcessMsg.splice_after(pnode->m_process_msg_most_recent, pnode->vRecvMsg, pnode->vRecvMsg.before_begin(), it); + pnode->m_process_msg_most_recent = it2; + pnode->m_recv_msg_most_recent = pnode->vRecvMsg.before_begin(); pnode->nProcessQueueSize += nSizeAdded; pnode->fPauseRecv = pnode->nProcessQueueSize > nReceiveFloodSize; } @@ -2974,15 +2979,20 @@ unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; } CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, ConnectionType conn_type_in, bool inbound_onion) : nTimeConnected(GetTimeSeconds()), - addr(addrIn), - addrBind(addrBindIn), - m_inbound_onion(inbound_onion), - nKeyedNetGroup(nKeyedNetGroupIn), - id(idIn), - nLocalHostNonce(nLocalHostNonceIn), - m_conn_type(conn_type_in), - nLocalServices(nLocalServicesIn) -{ + addr(addrIn), + addrBind(addrBindIn), + m_inbound_onion(inbound_onion), + nKeyedNetGroup(nKeyedNetGroupIn), + // Don't relay addr messages to peers that we connect to as block-relay-only + // peers (to prevent adversaries from inferring these links from addr + // traffic). + id(idIn), + nLocalHostNonce(nLocalHostNonceIn), + m_conn_type(conn_type_in), + nLocalServices(nLocalServicesIn) +{ + m_recv_msg_most_recent = vRecvMsg.before_begin(); + m_process_msg_most_recent = vProcessMsg.before_begin(); if (inbound_onion) assert(conn_type_in == ConnectionType::INBOUND); hSocket = hSocketIn; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; @@ -3017,11 +3027,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 serializedHeader; pnode->m_serializer->prepareForTransport(msg, serializedHeader); diff --git a/src/net.h b/src/net.h index a8836dfcb4363..151bcdd944d92 100644 --- a/src/net.h +++ b/src/net.h @@ -37,6 +37,13 @@ #include #include #include +#include +#include + +#ifndef WIN32 +#include +#endif + class CScheduler; class CNode; @@ -414,7 +421,8 @@ class CNode Mutex cs_vRecv; RecursiveMutex cs_vProcessMsg; - std::list vProcessMsg GUARDED_BY(cs_vProcessMsg); + std::forward_list vProcessMsg GUARDED_BY(cs_vProcessMsg); + std::forward_list::iterator m_process_msg_most_recent GUARDED_BY(cs_vProcessMsg); size_t nProcessQueueSize{0}; RecursiveMutex cs_sendProcessing; @@ -693,7 +701,8 @@ class CNode //! service advertisements. const ServiceFlags nLocalServices; - std::list vRecvMsg; // Used only by SocketHandler thread + std::forward_list vRecvMsg; // Used only by SocketHandler thread + std::forward_list::iterator m_recv_msg_most_recent; mutable RecursiveMutex cs_addrName; std::string addrName GUARDED_BY(cs_addrName); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 44f9879a232e9..bc5354bf8767b 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -34,6 +34,7 @@ #include // For NDEBUG compile time check #include #include +#include #include #include @@ -877,7 +878,7 @@ void PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid) } m_connman.ForNode(nodeid, [this](CNode* pfrom) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { AssertLockHeld(::cs_main); - uint64_t nCMPCTBLOCKVersion = (pfrom->GetLocalServices() & NODE_WITNESS) ? 2 : 1; + uint64_t nCMPCTBLOCKVersion = 2; if (lNodesAnnouncingHeaderAndIDs.size() >= 3) { // As per BIP152, we only get 3 of our peers to announce // blocks using compact encodings. @@ -1280,14 +1281,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, @@ -1973,7 +1980,7 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic static uint32_t GetFetchFlags(const CNode& pfrom) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { uint32_t nFetchFlags = 0; - if ((pfrom.GetLocalServices() & NODE_WITNESS) && State(pfrom.GetId())->fHaveWitness) { + if (State(pfrom.GetId())->fHaveWitness) { nFetchFlags |= MSG_WITNESS_FLAG; } return nFetchFlags; @@ -2688,8 +2695,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // they may wish to request compact blocks from us bool fAnnounceUsingCMPCTBLOCK = false; uint64_t nCMPCTBLOCKVersion = 2; - if (pfrom.GetLocalServices() & NODE_WITNESS) - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion)); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion)); nCMPCTBLOCKVersion = 1; m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion)); } @@ -2707,7 +2713,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, bool fAnnounceUsingCMPCTBLOCK = false; uint64_t nCMPCTBLOCKVersion = 0; vRecv >> fAnnounceUsingCMPCTBLOCK >> nCMPCTBLOCKVersion; - if (nCMPCTBLOCKVersion == 1 || ((pfrom.GetLocalServices() & NODE_WITNESS) && nCMPCTBLOCKVersion == 2)) { + if (nCMPCTBLOCKVersion == 1 || nCMPCTBLOCKVersion == 2) { LOCK(cs_main); // fProvidesHeaderAndIDs is used to "lock in" version of compact blocks we send (fWantsCmpctWitness) if (!State(pfrom.GetId())->fProvidesHeaderAndIDs) { @@ -2721,10 +2727,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, pfrom.m_bip152_highbandwidth_from = fAnnounceUsingCMPCTBLOCK; } if (!State(pfrom.GetId())->fSupportsDesiredCmpctVersion) { - if (pfrom.GetLocalServices() & NODE_WITNESS) - State(pfrom.GetId())->fSupportsDesiredCmpctVersion = (nCMPCTBLOCKVersion == 2); - else - State(pfrom.GetId())->fSupportsDesiredCmpctVersion = (nCMPCTBLOCKVersion == 1); + State(pfrom.GetId())->fSupportsDesiredCmpctVersion = (nCMPCTBLOCKVersion == 2); } } return; @@ -4038,18 +4041,29 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic& interrupt // Don't bother if send buffer is too full to respond anyway if (pfrom->fPauseSend) return false; - std::list msgs; + std::forward_list msgs; { LOCK(pfrom->cs_vProcessMsg); if (pfrom->vProcessMsg.empty()) return false; // Just take one message - msgs.splice(msgs.begin(), pfrom->vProcessMsg, pfrom->vProcessMsg.begin()); + msgs.splice_after(msgs.before_begin(), pfrom->vProcessMsg, pfrom->vProcessMsg.before_begin()); + if (pfrom->vProcessMsg.empty()) + pfrom->m_process_msg_most_recent = pfrom->vProcessMsg.before_begin(); pfrom->nProcessQueueSize -= msgs.front().m_raw_message_size; pfrom->fPauseRecv = pfrom->nProcessQueueSize > m_connman.GetReceiveFloodSize(); fMoreWork = !pfrom->vProcessMsg.empty(); } 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/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 442c813a5a60a..4ab4e388d9dc4 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -7,12 +7,19 @@ #endif #include -#include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include +#include #include #include #include @@ -20,6 +27,11 @@ #include #include #include +#include +#include +#include +#include +#include #ifdef ENABLE_WALLET #include @@ -27,18 +39,6 @@ #include #endif // ENABLE_WALLET -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include #include @@ -155,54 +155,11 @@ void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons } } -BitcoinCore::BitcoinCore(interfaces::Node& node) : - QObject(), m_node(node) -{ -} - -void BitcoinCore::handleRunawayException(const std::exception *e) -{ - PrintExceptionContinue(e, "Runaway exception"); - Q_EMIT runawayException(QString::fromStdString(m_node.getWarnings().translated)); -} - -void BitcoinCore::initialize() -{ - try - { - util::ThreadRename("qt-init"); - qDebug() << __func__ << ": Running initialization in thread"; - interfaces::BlockAndHeaderTipInfo tip_info; - bool rv = m_node.appInitMain(&tip_info); - Q_EMIT initializeResult(rv, tip_info); - } catch (const std::exception& e) { - handleRunawayException(&e); - } catch (...) { - handleRunawayException(nullptr); - } -} - -void BitcoinCore::shutdown() -{ - try - { - qDebug() << __func__ << ": Running Shutdown in thread"; - m_node.appShutdown(); - qDebug() << __func__ << ": Shutdown finished"; - Q_EMIT shutdownResult(); - } catch (const std::exception& e) { - handleRunawayException(&e); - } catch (...) { - handleRunawayException(nullptr); - } -} - static int qt_argc = 1; static const char* qt_argv = "bitcoin-qt"; BitcoinApplication::BitcoinApplication(): QApplication(qt_argc, const_cast(&qt_argv)), - coreThread(nullptr), optionsModel(nullptr), clientModel(nullptr), window(nullptr), @@ -230,13 +187,7 @@ void BitcoinApplication::setupPlatformStyle() BitcoinApplication::~BitcoinApplication() { - if(coreThread) - { - qDebug() << __func__ << ": Stopping thread"; - coreThread->quit(); - coreThread->wait(); - qDebug() << __func__ << ": Stopped thread"; - } + m_executor.reset(); delete window; window = nullptr; @@ -291,22 +242,15 @@ bool BitcoinApplication::baseInitialize() void BitcoinApplication::startThread() { - if(coreThread) - return; - coreThread = new QThread(this); - BitcoinCore *executor = new BitcoinCore(node()); - executor->moveToThread(coreThread); + assert(!m_executor); + m_executor.emplace(node()); /* communication to and from thread */ - connect(executor, &BitcoinCore::initializeResult, this, &BitcoinApplication::initializeResult); - connect(executor, &BitcoinCore::shutdownResult, this, &BitcoinApplication::shutdownResult); - connect(executor, &BitcoinCore::runawayException, this, &BitcoinApplication::handleRunawayException); - connect(this, &BitcoinApplication::requestedInitialize, executor, &BitcoinCore::initialize); - connect(this, &BitcoinApplication::requestedShutdown, executor, &BitcoinCore::shutdown); - /* make sure executor object is deleted in its own thread */ - connect(coreThread, &QThread::finished, executor, &QObject::deleteLater); - - coreThread->start(); + connect(&m_executor.value(), &InitExecutor::initializeResult, this, &BitcoinApplication::initializeResult); + connect(&m_executor.value(), &InitExecutor::shutdownResult, this, &BitcoinApplication::shutdownResult); + connect(&m_executor.value(), &InitExecutor::runawayException, this, &BitcoinApplication::handleRunawayException); + connect(this, &BitcoinApplication::requestedInitialize, &m_executor.value(), &InitExecutor::initialize); + connect(this, &BitcoinApplication::requestedShutdown, &m_executor.value(), &InitExecutor::shutdown); } void BitcoinApplication::parameterSetup() @@ -339,7 +283,6 @@ void BitcoinApplication::requestShutdown() shutdownWindow.reset(ShutdownWindow::showShutdownWindow(window)); qDebug() << __func__ << ": Requesting shutdown"; - startThread(); window->hide(); // Must disconnect node signals otherwise current thread can deadlock since // no event loop is running. diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h index f9fab0534ba94..ed2f26b7f384d 100644 --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -9,11 +9,14 @@ #include #endif -#include +#include +#include + #include #include +#include -#include +#include class BitcoinGUI; class ClientModel; @@ -26,31 +29,6 @@ class WalletController; class WalletModel; -/** Class encapsulating Bitcoin Core startup and shutdown. - * Allows running startup and shutdown in a different thread from the UI thread. - */ -class BitcoinCore: public QObject -{ - Q_OBJECT -public: - explicit BitcoinCore(interfaces::Node& node); - -public Q_SLOTS: - void initialize(); - void shutdown(); - -Q_SIGNALS: - void initializeResult(bool success, interfaces::BlockAndHeaderTipInfo tip_info); - void shutdownResult(); - void runawayException(const QString &message); - -private: - /// Pass fatal exception message to UI thread - void handleRunawayException(const std::exception *e); - - interfaces::Node& m_node; -}; - /** Main Bitcoin application object */ class BitcoinApplication: public QApplication { @@ -112,7 +90,7 @@ public Q_SLOTS: void windowShown(BitcoinGUI* window); private: - QThread *coreThread; + std::optional m_executor; OptionsModel *optionsModel; ClientModel *clientModel; BitcoinGUI *window; diff --git a/src/qt/initexecutor.cpp b/src/qt/initexecutor.cpp new file mode 100644 index 0000000000000..7060f74dab81c --- /dev/null +++ b/src/qt/initexecutor.cpp @@ -0,0 +1,66 @@ +// Copyright (c) 2014-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 + +#include +#include +#include + +#include + +#include +#include +#include +#include + +InitExecutor::InitExecutor(interfaces::Node& node) + : QObject(), m_node(node) +{ + this->moveToThread(&m_thread); + m_thread.start(); +} + +InitExecutor::~InitExecutor() +{ + qDebug() << __func__ << ": Stopping thread"; + m_thread.quit(); + m_thread.wait(); + qDebug() << __func__ << ": Stopped thread"; +} + +void InitExecutor::handleRunawayException(const std::exception* e) +{ + PrintExceptionContinue(e, "Runaway exception"); + Q_EMIT runawayException(QString::fromStdString(m_node.getWarnings().translated)); +} + +void InitExecutor::initialize() +{ + try { + util::ThreadRename("qt-init"); + qDebug() << __func__ << ": Running initialization in thread"; + interfaces::BlockAndHeaderTipInfo tip_info; + bool rv = m_node.appInitMain(&tip_info); + Q_EMIT initializeResult(rv, tip_info); + } catch (const std::exception& e) { + handleRunawayException(&e); + } catch (...) { + handleRunawayException(nullptr); + } +} + +void InitExecutor::shutdown() +{ + try { + qDebug() << __func__ << ": Running Shutdown in thread"; + m_node.appShutdown(); + qDebug() << __func__ << ": Shutdown finished"; + Q_EMIT shutdownResult(); + } catch (const std::exception& e) { + handleRunawayException(&e); + } catch (...) { + handleRunawayException(nullptr); + } +} diff --git a/src/qt/initexecutor.h b/src/qt/initexecutor.h new file mode 100644 index 0000000000000..319ce40465d45 --- /dev/null +++ b/src/qt/initexecutor.h @@ -0,0 +1,46 @@ +// Copyright (c) 2014-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_QT_INITEXECUTOR_H +#define BITCOIN_QT_INITEXECUTOR_H + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +/** Class encapsulating Bitcoin Core startup and shutdown. + * Allows running startup and shutdown in a different thread from the UI thread. + */ +class InitExecutor : public QObject +{ + Q_OBJECT +public: + explicit InitExecutor(interfaces::Node& node); + ~InitExecutor(); + +public Q_SLOTS: + void initialize(); + void shutdown(); + +Q_SIGNALS: + void initializeResult(bool success, interfaces::BlockAndHeaderTipInfo tip_info); + void shutdownResult(); + void runawayException(const QString& message); + +private: + /// Pass fatal exception message to UI thread + void handleRunawayException(const std::exception* e); + + interfaces::Node& m_node; + QThread m_thread; +}; + +#endif // BITCOIN_QT_INITEXECUTOR_H diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index eb86f027ef942..7d66f67f8a993 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c4a89c9772abf..f8a4d4ccab053 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1377,23 +1377,24 @@ static void SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniValue& case ThresholdState::ACTIVE: bip9.pushKV("status", "active"); break; case ThresholdState::FAILED: bip9.pushKV("status", "failed"); break; } - if (ThresholdState::STARTED == thresholdState) - { + const bool has_signal = (ThresholdState::STARTED == thresholdState || ThresholdState::LOCKED_IN == thresholdState); + if (has_signal) { bip9.pushKV("bit", consensusParams.vDeployments[id].bit); } bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); int64_t since_height = g_versionbitscache.StateSinceHeight(active_chain_tip, consensusParams, id); bip9.pushKV("since", since_height); - if (ThresholdState::STARTED == thresholdState) - { + if (has_signal) { UniValue statsUV(UniValue::VOBJ); BIP9Stats statsStruct = g_versionbitscache.Statistics(active_chain_tip, consensusParams, id); statsUV.pushKV("period", statsStruct.period); - statsUV.pushKV("threshold", statsStruct.threshold); statsUV.pushKV("elapsed", statsStruct.elapsed); statsUV.pushKV("count", statsStruct.count); - statsUV.pushKV("possible", statsStruct.possible); + if (ThresholdState::LOCKED_IN != thresholdState) { + statsUV.pushKV("threshold", statsStruct.threshold); + statsUV.pushKV("possible", statsStruct.possible); + } bip9.pushKV("statistics", statsUV); } bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height); @@ -1422,7 +1423,8 @@ RPCHelpMan getblockchaininfo() {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"}, {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"}, {RPCResult::Type::NUM, "difficulty", "the current difficulty"}, - {RPCResult::Type::NUM, "mediantime", "median time for the current best block"}, + {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME}, + {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"}, {RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in Initial Block Download mode"}, {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"}, @@ -1439,18 +1441,18 @@ RPCHelpMan getblockchaininfo() {RPCResult::Type::OBJ, "bip9", "status of bip9 softforks (only for \"bip9\" type)", { {RPCResult::Type::STR, "status", "one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\""}, - {RPCResult::Type::NUM, "bit", "the bit (0-28) in the block version field used to signal this softfork (only for \"started\" status)"}, + {RPCResult::Type::NUM, "bit", "the bit (0-28) in the block version field used to signal this softfork (only for \"started\" and \"locked_in\" status)"}, {RPCResult::Type::NUM_TIME, "start_time", "the minimum median time past of a block at which the bit gains its meaning"}, {RPCResult::Type::NUM_TIME, "timeout", "the median time past of a block at which the deployment is considered failed if not yet locked in"}, {RPCResult::Type::NUM, "since", "height of the first block to which the status applies"}, {RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"}, - {RPCResult::Type::OBJ, "statistics", "numeric statistics about BIP9 signalling for a softfork (only for \"started\" status)", + {RPCResult::Type::OBJ, "statistics", "numeric statistics about signalling for a softfork (only for \"started\" and \"locked_in\" status)", { - {RPCResult::Type::NUM, "period", "the length in blocks of the BIP9 signalling period"}, - {RPCResult::Type::NUM, "threshold", "the number of blocks with the version bit set required to activate the feature"}, + {RPCResult::Type::NUM, "period", "the length in blocks of the signalling period"}, + {RPCResult::Type::NUM, "threshold", "the number of blocks with the version bit set required to activate the feature (only for \"started\" status)"}, {RPCResult::Type::NUM, "elapsed", "the number of blocks elapsed since the beginning of the current period"}, {RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"}, - {RPCResult::Type::BOOL, "possible", "returns false if there are not enough blocks left in this period to pass activation threshold"}, + {RPCResult::Type::BOOL, "possible", "returns false if there are not enough blocks left in this period to pass activation threshold (only for \"started\" status)"}, }}, }}, {RPCResult::Type::NUM, "height", "height of the first block which the rules are or will be enforced (only for \"buried\" type, or \"bip9\" type with \"active\" status)"}, @@ -1478,6 +1480,7 @@ RPCHelpMan getblockchaininfo() obj.pushKV("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1); obj.pushKV("bestblockhash", tip->GetBlockHash().GetHex()); obj.pushKV("difficulty", (double)GetDifficulty(tip)); + obj.pushKV("time", (int64_t)tip->nTime); obj.pushKV("mediantime", (int64_t)tip->GetMedianTimePast()); obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), tip)); obj.pushKV("initialblockdownload", active_chainstate.IsInitialBlockDownload()); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 617dfec98f845..6dfccd9023cc0 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -74,12 +74,10 @@ static RPCHelpMan getrawtransaction() "getrawtransaction", "\nReturn the raw transaction data.\n" - "\nBy default this function only works for mempool transactions. When called with a blockhash\n" - "argument, getrawtransaction will return the transaction if the specified block is available and\n" - "the transaction is found in that block. When called without a blockhash argument, getrawtransaction\n" - "will return the transaction if it is in the mempool, or if -txindex is enabled and the transaction\n" - "is in a block in the blockchain.\n" - + "\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" "\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.cpp b/src/script/interpreter.cpp index ef48f89965817..dd7c0a4a05101 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1807,16 +1807,16 @@ bool GenericTransactionSignatureChecker::CheckSequence(const CScriptNum& nSeq template class GenericTransactionSignatureChecker; template class GenericTransactionSignatureChecker; -static bool ExecuteWitnessScript(const Span& stack_span, const CScript& scriptPubKey, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, ScriptError* serror) +static bool ExecuteWitnessScript(const Span& stack_span, const CScript& exec_script, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, ScriptError* serror) { std::vector stack{stack_span.begin(), stack_span.end()}; if (sigversion == SigVersion::TAPSCRIPT) { // OP_SUCCESSx processing overrides everything, including stack element size limits - CScript::const_iterator pc = scriptPubKey.begin(); - while (pc < scriptPubKey.end()) { + CScript::const_iterator pc = exec_script.begin(); + while (pc < exec_script.end()) { opcodetype opcode; - if (!scriptPubKey.GetOp(pc, opcode)) { + if (!exec_script.GetOp(pc, opcode)) { // Note how this condition would not be reached if an unknown OP_SUCCESSx was found return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } @@ -1839,7 +1839,7 @@ static bool ExecuteWitnessScript(const Span& stack_span, const CS } // Run the script interpreter. - if (!EvalScript(stack, scriptPubKey, flags, checker, sigversion, execdata, serror)) return false; + if (!EvalScript(stack, exec_script, flags, checker, sigversion, execdata, serror)) return false; // Scripts inside witness implicitly require cleanstack behaviour if (stack.size() != 1) return set_error(serror, SCRIPT_ERR_CLEANSTACK); diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 37ae7139968a4..93136a0b79d7e 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 &vchSig, unsigned int flags, ScriptError* serror); diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index 1103292c1a873..5e5c5eba69cfe 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -76,7 +76,7 @@ class CAddrManTest : public CAddrMan { int64_t nLastSuccess = 1; // Set last good connection in the deep past. - Good(addr, true, nLastSuccess); + Good(addr, nLastSuccess); bool count_failure = false; int64_t nLastTry = GetAdjustedTime()-61; diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index db0b461873076..92c34e74d9861 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -44,6 +44,17 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman) addr_man.m_asmap.clear(); } } + if (fuzzed_data_provider.ConsumeBool()) { + const std::vector serialized_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)}; + CDataStream ds(serialized_data, SER_DISK, INIT_PROTO_VERSION); + const auto ser_version{fuzzed_data_provider.ConsumeIntegral()}; + ds.SetVersion(ser_version); + try { + ds >> addr_man; + } catch (const std::ios_base::failure&) { + addr_man.Clear(); + } + } while (fuzzed_data_provider.ConsumeBool()) { CallOneOf( fuzzed_data_provider, @@ -80,7 +91,7 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman) [&] { const std::optional opt_service = ConsumeDeserializable(fuzzed_data_provider); if (opt_service) { - addr_man.Good(*opt_service, fuzzed_data_provider.ConsumeBool(), ConsumeTime(fuzzed_data_provider)); + addr_man.Good(*opt_service, ConsumeTime(fuzzed_data_provider)); } }, [&] { diff --git a/src/test/fuzz/banman.cpp b/src/test/fuzz/banman.cpp index 182aabc79b86b..1986b5e4c8751 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 51956bbe9ec29..447f32ed16cf4 100644 --- a/src/test/fuzz/prevector.cpp +++ b/src/test/fuzz/prevector.cpp @@ -206,10 +206,14 @@ class prevector_tester 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(0, 13 + 3 * (test.size() > 0))) { case 0: test.insert(prov.ConsumeIntegralInRange(0, test.size()), prov.ConsumeIntegral()); diff --git a/src/test/fuzz/rolling_bloom_filter.cpp b/src/test/fuzz/rolling_bloom_filter.cpp index 07059cce76a87..3b33115e72b04 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(1, 1000), 0.999 / fuzzed_data_provider.ConsumeIntegralInRange(1, std::numeric_limits::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 u256 = ConsumeDeserializable(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 bab34ea3402a8..dadf772bc1f23 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(&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 23195c0a26c5f..1924ea55b1e41 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/test/util/net.cpp b/src/test/util/net.cpp index 28d79670786bb..6231a2ace6a19 100644 --- a/src/test/util/net.cpp +++ b/src/test/util/net.cpp @@ -16,14 +16,17 @@ void ConnmanTestMsg::NodeReceiveMsgBytes(CNode& node, Span msg_by if (complete) { size_t nSizeAdded = 0; auto it(node.vRecvMsg.begin()); - for (; it != node.vRecvMsg.end(); ++it) { + auto it2 = it; + for (; it != node.vRecvMsg.end(); it2 = it++) { // vRecvMsg contains only completed CNetMessage // the single possible partially deserialized message are held by TransportDeserializer nSizeAdded += it->m_raw_message_size; } { LOCK(node.cs_vProcessMsg); - node.vProcessMsg.splice(node.vProcessMsg.end(), node.vRecvMsg, node.vRecvMsg.begin(), it); + node.vProcessMsg.splice_after(node.m_process_msg_most_recent, node.vRecvMsg, node.vRecvMsg.before_begin(), it); + node.m_process_msg_most_recent = it2; + node.m_recv_msg_most_recent = node.vRecvMsg.before_begin(); node.nProcessQueueSize += nSizeAdded; node.fPauseRecv = node.nProcessQueueSize > nReceiveFloodSize; } diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index a0499fa51fe7e..bb296456bafc6 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -132,28 +132,35 @@ void TorControlConnection::eventcb(struct bufferevent *bev, short what, void *ct bool TorControlConnection::Connect(const std::string& tor_control_center, const ConnectionCB& _connected, const ConnectionCB& _disconnected) { - if (b_conn) + if (b_conn) { Disconnect(); - // Parse tor_control_center address:port - struct sockaddr_storage connect_to_addr; - int connect_to_addrlen = sizeof(connect_to_addr); - if (evutil_parse_sockaddr_port(tor_control_center.c_str(), - (struct sockaddr*)&connect_to_addr, &connect_to_addrlen)<0) { + } + + CService control_service; + if (!Lookup(tor_control_center, control_service, 9051, fNameLookup)) { + LogPrintf("tor: Failed to look up control center %s\n", tor_control_center); + return false; + } + + struct sockaddr_storage control_address; + socklen_t control_address_len = sizeof(control_address); + if (!control_service.GetSockAddr(reinterpret_cast(&control_address), &control_address_len)) { LogPrintf("tor: Error parsing socket address %s\n", tor_control_center); return false; } // Create a new socket, set up callbacks and enable notification bits b_conn = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); - if (!b_conn) + if (!b_conn) { return false; + } bufferevent_setcb(b_conn, TorControlConnection::readcb, nullptr, TorControlConnection::eventcb, this); bufferevent_enable(b_conn, EV_READ|EV_WRITE); this->connected = _connected; this->disconnected = _disconnected; // Finally, connect to tor_control_center - if (bufferevent_socket_connect(b_conn, (struct sockaddr*)&connect_to_addr, connect_to_addrlen) < 0) { + if (bufferevent_socket_connect(b_conn, reinterpret_cast(&control_address), control_address_len) < 0) { LogPrintf("tor: Error connecting to address %s\n", tor_control_center); return false; } diff --git a/src/util/epochguard.h b/src/util/epochguard.h index 1570ec4eb4c80..3e63e093daa19 100644 --- a/src/util/epochguard.h +++ b/src/util/epochguard.h @@ -40,6 +40,9 @@ class LOCKABLE Epoch Epoch() = default; Epoch(const Epoch&) = delete; Epoch& operator=(const Epoch&) = delete; + Epoch(Epoch&&) = delete; + Epoch& operator=(Epoch&&) = delete; + ~Epoch() = default; bool guarded() const { return m_guarded; } @@ -51,6 +54,13 @@ class LOCKABLE Epoch // only allow modification via Epoch member functions friend class Epoch; Marker& operator=(const Marker&) = delete; + + public: + Marker() = default; + Marker(const Marker&) = default; + Marker(Marker&&) = delete; + Marker& operator=(Marker&&) = delete; + ~Marker() = default; }; class SCOPED_LOCKABLE Guard diff --git a/src/validation.cpp b/src/validation.cpp index f81c27e8e3f0c..20d641bf40089 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -1158,6 +1159,20 @@ CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMe { 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)) { @@ -1168,15 +1183,6 @@ CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMe } } } - return nullptr; - } - if (mempool) { - CTransactionRef ptx = mempool->get(hash); - if (ptx) return ptx; - } - if (g_txindex) { - CTransactionRef tx; - if (g_txindex->FindTx(hash, hashBlock, tx)) return tx; } return nullptr; } @@ -1645,13 +1651,8 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consens pindex->phashBlock == nullptr || // this is a new candidate block, eg from TestBlockValidity() *pindex->phashBlock != consensusparams.BIP16Exception) // this block isn't the historical exception { - flags |= SCRIPT_VERIFY_P2SH; - } - - // Enforce WITNESS rules whenever P2SH is in effect (and the segwit - // deployment is defined). - if (flags & SCRIPT_VERIFY_P2SH && DeploymentEnabled(consensusparams, Consensus::DEPLOYMENT_SEGWIT)) { - flags |= SCRIPT_VERIFY_WITNESS; + // Enforce WITNESS rules whenever P2SH is in effect + flags |= SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS; } // Enforce the DERSIG (BIP66) rule @@ -1997,6 +1998,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; } @@ -3101,25 +3112,23 @@ std::vector GenerateCoinbaseCommitment(CBlock& block, const CBloc std::vector commitment; int commitpos = GetWitnessCommitmentIndex(block); std::vector ret(32, 0x00); - if (DeploymentEnabled(consensusParams, Consensus::DEPLOYMENT_SEGWIT)) { - if (commitpos == NO_WITNESS_COMMITMENT) { - uint256 witnessroot = BlockWitnessMerkleRoot(block, nullptr); - CHash256().Write(witnessroot).Write(ret).Finalize(witnessroot); - CTxOut out; - out.nValue = 0; - out.scriptPubKey.resize(MINIMUM_WITNESS_COMMITMENT); - out.scriptPubKey[0] = OP_RETURN; - out.scriptPubKey[1] = 0x24; - out.scriptPubKey[2] = 0xaa; - out.scriptPubKey[3] = 0x21; - out.scriptPubKey[4] = 0xa9; - out.scriptPubKey[5] = 0xed; - memcpy(&out.scriptPubKey[6], witnessroot.begin(), 32); - commitment = std::vector(out.scriptPubKey.begin(), out.scriptPubKey.end()); - CMutableTransaction tx(*block.vtx[0]); - tx.vout.push_back(out); - block.vtx[0] = MakeTransactionRef(std::move(tx)); - } + if (commitpos == NO_WITNESS_COMMITMENT) { + uint256 witnessroot = BlockWitnessMerkleRoot(block, nullptr); + CHash256().Write(witnessroot).Write(ret).Finalize(witnessroot); + CTxOut out; + out.nValue = 0; + out.scriptPubKey.resize(MINIMUM_WITNESS_COMMITMENT); + out.scriptPubKey[0] = OP_RETURN; + out.scriptPubKey[1] = 0x24; + out.scriptPubKey[2] = 0xaa; + out.scriptPubKey[3] = 0x21; + out.scriptPubKey[4] = 0xa9; + out.scriptPubKey[5] = 0xed; + memcpy(&out.scriptPubKey[6], witnessroot.begin(), 32); + commitment = std::vector(out.scriptPubKey.begin(), out.scriptPubKey.end()); + CMutableTransaction tx(*block.vtx[0]); + tx.vout.push_back(out); + block.vtx[0] = MakeTransactionRef(std::move(tx)); } UpdateUncommittedBlockStructures(block, pindexPrev, consensusParams); return commitment; diff --git a/src/validation.h b/src/validation.h index 18a09d4aa3b4c..d3e4f3b98322e 100644 --- a/src/validation.h +++ b/src/validation.h @@ -141,15 +141,16 @@ void StartScriptCheckWorkerThreads(int threads_num); /** Stop all of the script checking worker threads */ void StopScriptCheckWorkerThreads(); /** - * Return transaction from the block at block_index. - * If block_index is not provided, fall back to mempool. - * If mempool is not provided or the tx couldn't be found in mempool, fall back to g_txindex. + * 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 block_index is not provided, look in the mempool, if provided + * @param[in] mempool If provided, check mempool for tx * @param[in] hash The txid * @param[in] consensusParams The params - * @param[out] hashBlock The hash of block_index, if the tx was found via block_index + * @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); diff --git a/src/wallet/test/spend_tests.cpp b/src/wallet/test/spend_tests.cpp new file mode 100644 index 0000000000000..66e7de4273c92 --- /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 +#include +#include +#include +#include + +#include + +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 0000000000000..c3061b93c061c --- /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 + +#include +#include +#include +#include +#include + +#include + +#include + +std::unique_ptr CreateSyncedWallet(interfaces::Chain& chain, CChain& cchain, const CKey& key) +{ + auto wallet = std::make_unique(&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 0000000000000..288c111571c4d --- /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 + +class CChain; +class CKey; +class CWallet; +namespace interfaces { +class Chain; +} // namespace interfaces + +std::unique_ptr 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 a0070b8dd321f..75a08b6f74ca6 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -480,20 +481,7 @@ class ListCoinsTestingSetup : public TestChain100Setup ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - wallet = std::make_unique(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_presegwit_node_upgrade.py b/test/functional/feature_presegwit_node_upgrade.py new file mode 100755 index 0000000000000..0428588da396e --- /dev/null +++ b/test/functional/feature_presegwit_node_upgrade.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017-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. +"""Test a pre-segwit node upgrading to segwit consensus""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + softfork_active, +) + +class SegwitUpgradeTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + self.extra_args = [["-segwitheight=10"]] + + def run_test(self): + """A pre-segwit node with insufficiently validated blocks needs to redownload blocks""" + + self.log.info("Testing upgrade behaviour for pre-segwit node to segwit rules") + node = self.nodes[0] + + # Node hasn't been used or connected yet + assert_equal(node.getblockcount(), 0) + + assert not softfork_active(node, "segwit") + + # Generate 8 blocks without witness data + node.generate(8) + assert_equal(node.getblockcount(), 8) + + self.stop_node(0) + # Restarting the node (with segwit activation height set to 5) should result in a shutdown + # because the blockchain consists of 3 insufficiently validated blocks per segwit consensus rules. + node.assert_start_raises_init_error( + extra_args=["-segwitheight=5"], + expected_msg=": Witness data for blocks after height 5 requires validation. Please restart with -reindex..\nPlease restart with -reindex or -reindex-chainstate to recover.") + + # As directed, the user restarts the node with -reindex + self.start_node(0, extra_args=["-reindex", "-segwitheight=5"]) + + # With the segwit consensus rules, the node is able to validate only up to block 4 + assert_equal(node.getblockcount(), 4) + + # The upgraded node should now have segwit activated + assert softfork_active(node, "segwit") + + +if __name__ == '__main__': + SegwitUpgradeTest().main() diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index 22eec59600dff..dfa448a1a867a 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -5,6 +5,7 @@ """Test bitcoin-cli""" from decimal import Decimal +import re from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework @@ -29,6 +30,41 @@ WALLET_NOT_LOADED = 'Requested wallet does not exist or is not loaded' WALLET_NOT_SPECIFIED = 'Wallet file not specified' + +def cli_get_info_string_to_dict(cli_get_info_string): + """Helper method to convert human-readable -getinfo into a dictionary""" + cli_get_info = {} + lines = cli_get_info_string.splitlines() + line_idx = 0 + ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]') + while line_idx < len(lines): + # Remove ansi colour code + line = ansi_escape.sub('', lines[line_idx]) + if "Balances" in line: + # When "Balances" appears in a line, all of the following lines contain "balance: wallet" until an empty line + cli_get_info["Balances"] = {} + while line_idx < len(lines) and not (lines[line_idx + 1] == ''): + line_idx += 1 + balance, wallet = lines[line_idx].strip().split(" ") + # Remove right justification padding + wallet = wallet.strip() + if wallet == '""': + # Set default wallet("") to empty string + wallet = '' + cli_get_info["Balances"][wallet] = balance.strip() + elif ": " in line: + key, value = line.split(": ") + if key == 'Wallet' and value == '""': + # Set default wallet("") to empty string + value = '' + if key == "Proxy" and value == "N/A": + # Set N/A to empty string to represent no proxy + value = '' + cli_get_info[key.strip()] = value.strip() + line_idx += 1 + return cli_get_info + + class TestBitcoinCli(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -67,37 +103,43 @@ def run_test(self): self.log.info("Test -getinfo with arguments fails") assert_raises_process_error(1, "-getinfo takes no arguments", self.nodes[0].cli('-getinfo').help) + self.log.info("Test -getinfo with -color=never does not return ANSI escape codes") + assert "\u001b[0m" not in self.nodes[0].cli('-getinfo', '-color=never').send_cli() + + self.log.info("Test -getinfo with -color=always returns ANSI escape codes") + assert "\u001b[0m" in self.nodes[0].cli('-getinfo', '-color=always').send_cli() + + self.log.info("Test -getinfo with invalid value for -color option") + assert_raises_process_error(1, "Invalid value for -color option. Valid values: always, auto, never.", self.nodes[0].cli('-getinfo', '-color=foo').send_cli) + self.log.info("Test -getinfo returns expected network and blockchain info") if self.is_wallet_compiled(): self.nodes[0].encryptwallet(password) - cli_get_info = self.nodes[0].cli('-getinfo').send_cli() + cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli() + cli_get_info = cli_get_info_string_to_dict(cli_get_info_string) + network_info = self.nodes[0].getnetworkinfo() blockchain_info = self.nodes[0].getblockchaininfo() - assert_equal(cli_get_info['version'], network_info['version']) - assert_equal(cli_get_info['blocks'], blockchain_info['blocks']) - assert_equal(cli_get_info['headers'], blockchain_info['headers']) - assert_equal(cli_get_info['timeoffset'], network_info['timeoffset']) - assert_equal( - cli_get_info['connections'], - { - 'in': network_info['connections_in'], - 'out': network_info['connections_out'], - 'total': network_info['connections'] - } - ) - assert_equal(cli_get_info['proxy'], network_info['networks'][0]['proxy']) - assert_equal(cli_get_info['difficulty'], blockchain_info['difficulty']) - assert_equal(cli_get_info['chain'], blockchain_info['chain']) + assert_equal(int(cli_get_info['Version']), network_info['version']) + assert_equal(cli_get_info['Verification progress'], "%.4f%%" % (blockchain_info['verificationprogress'] * 100)) + assert_equal(int(cli_get_info['Blocks']), blockchain_info['blocks']) + assert_equal(int(cli_get_info['Headers']), blockchain_info['headers']) + assert_equal(int(cli_get_info['Time offset (s)']), network_info['timeoffset']) + expected_network_info = f"in {network_info['connections_in']}, out {network_info['connections_out']}, total {network_info['connections']}" + assert_equal(cli_get_info["Network"], expected_network_info) + assert_equal(cli_get_info['Proxy'], network_info['networks'][0]['proxy']) + assert_equal(Decimal(cli_get_info['Difficulty']), blockchain_info['difficulty']) + assert_equal(cli_get_info['Chain'], blockchain_info['chain']) if self.is_wallet_compiled(): self.log.info("Test -getinfo and bitcoin-cli getwalletinfo return expected wallet info") - assert_equal(cli_get_info['balance'], BALANCE) - assert 'balances' not in cli_get_info.keys() + assert_equal(Decimal(cli_get_info['Balance']), BALANCE) + assert 'Balances' not in cli_get_info_string wallet_info = self.nodes[0].getwalletinfo() - assert_equal(cli_get_info['keypoolsize'], wallet_info['keypoolsize']) - assert_equal(cli_get_info['unlocked_until'], wallet_info['unlocked_until']) - assert_equal(cli_get_info['paytxfee'], wallet_info['paytxfee']) - assert_equal(cli_get_info['relayfee'], network_info['relayfee']) + assert_equal(int(cli_get_info['Keypool size']), wallet_info['keypoolsize']) + assert_equal(int(cli_get_info['Unlocked until']), wallet_info['unlocked_until']) + assert_equal(Decimal(cli_get_info['Transaction fee rate (-paytxfee) (BTC/kvB)']), wallet_info['paytxfee']) + assert_equal(Decimal(cli_get_info['Min tx relay fee rate (BTC/kvB)']), network_info['relayfee']) assert_equal(self.nodes[0].cli.getwalletinfo(), wallet_info) # Setup to test -getinfo, -generate, and -rpcwallet= with multiple wallets. @@ -120,44 +162,57 @@ def run_test(self): self.log.info("Test -getinfo with multiple wallets and -rpcwallet returns specified wallet balance") for i in range(len(wallets)): - cli_get_info = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[i])).send_cli() - assert 'balances' not in cli_get_info.keys() - assert_equal(cli_get_info['balance'], amounts[i]) + cli_get_info_string = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[i])).send_cli() + cli_get_info = cli_get_info_string_to_dict(cli_get_info_string) + assert 'Balances' not in cli_get_info_string + assert_equal(cli_get_info["Wallet"], wallets[i]) + assert_equal(Decimal(cli_get_info['Balance']), amounts[i]) self.log.info("Test -getinfo with multiple wallets and -rpcwallet=non-existing-wallet returns no balances") - cli_get_info_keys = self.nodes[0].cli('-getinfo', '-rpcwallet=does-not-exist').send_cli().keys() - assert 'balance' not in cli_get_info_keys - assert 'balances' not in cli_get_info_keys + cli_get_info_string = self.nodes[0].cli('-getinfo', '-rpcwallet=does-not-exist').send_cli() + assert 'Balance' not in cli_get_info_string + assert 'Balances' not in cli_get_info_string self.log.info("Test -getinfo with multiple wallets returns all loaded wallet names and balances") assert_equal(set(self.nodes[0].listwallets()), set(wallets)) - cli_get_info = self.nodes[0].cli('-getinfo').send_cli() - assert 'balance' not in cli_get_info.keys() - assert_equal(cli_get_info['balances'], {k: v for k, v in zip(wallets, amounts)}) + cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli() + cli_get_info = cli_get_info_string_to_dict(cli_get_info_string) + assert 'Balance' not in cli_get_info + for k, v in zip(wallets, amounts): + assert_equal(Decimal(cli_get_info['Balances'][k]), v) # Unload the default wallet and re-verify. self.nodes[0].unloadwallet(wallets[0]) assert wallets[0] not in self.nodes[0].listwallets() - cli_get_info = self.nodes[0].cli('-getinfo').send_cli() - assert 'balance' not in cli_get_info.keys() - assert_equal(cli_get_info['balances'], {k: v for k, v in zip(wallets[1:], amounts[1:])}) + cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli() + cli_get_info = cli_get_info_string_to_dict(cli_get_info_string) + assert 'Balance' not in cli_get_info + assert 'Balances' in cli_get_info_string + for k, v in zip(wallets[1:], amounts[1:]): + assert_equal(Decimal(cli_get_info['Balances'][k]), v) + assert wallets[0] not in cli_get_info self.log.info("Test -getinfo after unloading all wallets except a non-default one returns its balance") self.nodes[0].unloadwallet(wallets[2]) assert_equal(self.nodes[0].listwallets(), [wallets[1]]) - cli_get_info = self.nodes[0].cli('-getinfo').send_cli() - assert 'balances' not in cli_get_info.keys() - assert_equal(cli_get_info['balance'], amounts[1]) + cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli() + cli_get_info = cli_get_info_string_to_dict(cli_get_info_string) + assert 'Balances' not in cli_get_info_string + assert_equal(cli_get_info['Wallet'], wallets[1]) + assert_equal(Decimal(cli_get_info['Balance']), amounts[1]) self.log.info("Test -getinfo with -rpcwallet=remaining-non-default-wallet returns only its balance") - cli_get_info = self.nodes[0].cli('-getinfo', rpcwallet2).send_cli() - assert 'balances' not in cli_get_info.keys() - assert_equal(cli_get_info['balance'], amounts[1]) + cli_get_info_string = self.nodes[0].cli('-getinfo', rpcwallet2).send_cli() + cli_get_info = cli_get_info_string_to_dict(cli_get_info_string) + assert 'Balances' not in cli_get_info_string + assert_equal(cli_get_info['Wallet'], wallets[1]) + assert_equal(Decimal(cli_get_info['Balance']), amounts[1]) self.log.info("Test -getinfo with -rpcwallet=unloaded wallet returns no balances") - cli_get_info_keys = self.nodes[0].cli('-getinfo', rpcwallet3).send_cli().keys() - assert 'balance' not in cli_get_info_keys - assert 'balances' not in cli_get_info_keys + cli_get_info_string = self.nodes[0].cli('-getinfo', rpcwallet3).send_cli() + cli_get_info_keys = cli_get_info_string_to_dict(cli_get_info_string) + assert 'Balance' not in cli_get_info_keys + assert 'Balances' not in cli_get_info_string # Test bitcoin-cli -generate. n1 = 3 diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index ba467c1517385..01fc02f27e922 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -13,6 +13,7 @@ from test_framework.blocktools import ( create_coinbase, + get_witness_script, NORMAL_GBT_REQUEST_PARAMS, TIME_GENESIS_BLOCK, ) @@ -20,6 +21,7 @@ CBlock, CBlockHeader, BLOCK_HEADER_SIZE, + ser_uint256, ) from test_framework.p2p import P2PDataStore from test_framework.test_framework import BitcoinTestFramework @@ -49,6 +51,9 @@ def set_test_params(self): self.setup_clean_chain = True self.supports_cli = False + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + def mine_chain(self): self.log.info('Create some old blocks') for t in range(TIME_GENESIS_BLOCK, TIME_GENESIS_BLOCK + 200 * 600, 600): @@ -89,7 +94,21 @@ def assert_submitblock(block, result_str_1, result_str_2=None): assert_equal(mining_info['networkhashps'], Decimal('0.003333333333333334')) assert_equal(mining_info['pooledtx'], 0) - # Mine a block to leave initial block download + self.log.info("getblocktemplate: Test default witness commitment") + txid = int(node.sendtoaddress(node.getnewaddress(), 1), 16) + tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) + + # Check that default_witness_commitment is present. + assert 'default_witness_commitment' in tmpl + witness_commitment = tmpl['default_witness_commitment'] + + # Check that default_witness_commitment is correct. + witness_root = CBlock.get_merkle_root([ser_uint256(0), + ser_uint256(txid)]) + script = get_witness_script(witness_root, 0) + assert_equal(witness_commitment, script.hex()) + + # Mine a block to leave initial block download and clear the mempool node.generatetoaddress(1, node.get_deterministic_priv_key().address) tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) self.log.info("getblocktemplate: Test capability advertised") diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index ead9d852febf3..db96e6bdcfe8f 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -9,11 +9,10 @@ import struct import time -from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment, get_witness_script, WITNESS_COMMITMENT_HEADER +from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment, WITNESS_COMMITMENT_HEADER from test_framework.key import ECKey from test_framework.messages import ( BIP125_SEQUENCE_NUMBER, - CBlock, CBlockHeader, CInv, COutPoint, @@ -206,24 +205,17 @@ def request_block(self, blockhash, inv_type, timeout=60): class SegWitTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 3 + self.num_nodes = 2 # This test tests SegWit both pre and post-activation, so use the normal BIP9 activation. self.extra_args = [ ["-acceptnonstdtxn=1", "-segwitheight={}".format(SEGWIT_HEIGHT), "-whitelist=noban@127.0.0.1"], ["-acceptnonstdtxn=0", "-segwitheight={}".format(SEGWIT_HEIGHT)], - ["-acceptnonstdtxn=1", "-segwitheight=-1"], ] self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() - def setup_network(self): - self.setup_nodes() - self.connect_nodes(0, 1) - self.connect_nodes(0, 2) - self.sync_all() - # Helper functions def build_next_block(self, version=4): @@ -264,7 +256,6 @@ def run_test(self): self.test_non_witness_transaction() self.test_v0_outputs_arent_spendable() self.test_block_relay() - self.test_getblocktemplate_before_lockin() self.test_unnecessary_witness_before_segwit_activation() self.test_witness_tx_relay_before_segwit_activation() self.test_standardness_v0() @@ -292,7 +283,6 @@ def run_test(self): self.test_signature_version_1() self.test_non_standard_witness_blinding() self.test_non_standard_witness() - self.test_upgrade_after_activation() self.test_witness_sigops() self.test_superfluous_witness() self.test_wtxid_relay() @@ -482,11 +472,6 @@ def test_v0_outputs_arent_spendable(self): witness, and so can't be spent before segwit activation (the point at which blocks are permitted to contain witnesses).""" - # node2 doesn't need to be connected for this test. - # (If it's connected, node0 may propagate an invalid block to it over - # compact blocks and the nodes would have inconsistent tips.) - self.disconnect_nodes(0, 2) - # Create two outputs, a p2wsh and p2sh-p2wsh witness_program = CScript([OP_TRUE]) script_pubkey = script_to_p2wsh_script(witness_program) @@ -544,37 +529,9 @@ def test_v0_outputs_arent_spendable(self): # TODO: support multiple acceptable reject reasons. test_witness_block(self.nodes[0], self.test_node, block, accepted=False, with_witness=False) - self.connect_nodes(0, 2) - self.utxo.pop(0) self.utxo.append(UTXO(txid, 2, value)) - @subtest # type: ignore - def test_getblocktemplate_before_lockin(self): - txid = int(self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1), 16) - - for node in [self.nodes[0], self.nodes[2]]: - gbt_results = node.getblocktemplate({"rules": ["segwit"]}) - if node == self.nodes[2]: - # If this is a non-segwit node, we should not get a witness - # commitment. - assert 'default_witness_commitment' not in gbt_results - else: - # For segwit-aware nodes, check the witness - # commitment is correct. - assert 'default_witness_commitment' in gbt_results - witness_commitment = gbt_results['default_witness_commitment'] - - # Check that default_witness_commitment is present. - witness_root = CBlock.get_merkle_root([ser_uint256(0), - ser_uint256(txid)]) - script = get_witness_script(witness_root, 0) - assert_equal(witness_commitment, script.hex()) - - # Clear out the mempool - self.nodes[0].generate(1) - self.sync_blocks() - @subtest # type: ignore def test_witness_tx_relay_before_segwit_activation(self): @@ -1927,39 +1884,6 @@ def test_non_standard_witness(self): self.utxo.pop(0) - @subtest # type: ignore - def test_upgrade_after_activation(self): - """Test the behavior of starting up a segwit-aware node after the softfork has activated.""" - - # All nodes are caught up and node 2 is a pre-segwit node that will soon upgrade. - for n in range(2): - assert_equal(self.nodes[n].getblockcount(), self.nodes[2].getblockcount()) - assert softfork_active(self.nodes[n], "segwit") - assert SEGWIT_HEIGHT < self.nodes[2].getblockcount() - assert 'segwit' not in self.nodes[2].getblockchaininfo()['softforks'] - - # Restarting node 2 should result in a shutdown because the blockchain consists of - # insufficiently validated blocks per segwit consensus rules. - self.stop_node(2) - self.nodes[2].assert_start_raises_init_error( - extra_args=[f"-segwitheight={SEGWIT_HEIGHT}"], - expected_msg=f": Witness data for blocks after height {SEGWIT_HEIGHT} requires validation. Please restart with -reindex..\nPlease restart with -reindex or -reindex-chainstate to recover.", - ) - - # As directed, the user restarts the node with -reindex - self.start_node(2, extra_args=["-reindex", f"-segwitheight={SEGWIT_HEIGHT}"]) - - # With the segwit consensus rules, the node is able to validate only up to SEGWIT_HEIGHT - 1 - assert_equal(self.nodes[2].getblockcount(), SEGWIT_HEIGHT - 1) - self.connect_nodes(0, 2) - - # We reconnect more than 100 blocks, give it plenty of time - # sync_blocks() also verifies the best block hash is the same for all nodes - self.sync_blocks(timeout=240) - - # The upgraded node should now have segwit activated - assert softfork_active(self.nodes[2], "segwit") - @subtest # type: ignore def test_witness_sigops(self): """Test sigop counting is correct inside witnesses.""" diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 90715cae2689b..f7290ff229163 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -93,11 +93,14 @@ def _test_getblockchaininfo(self): 'pruned', 'size_on_disk', 'softforks', + 'time', 'verificationprogress', 'warnings', ] res = self.nodes[0].getblockchaininfo() + assert isinstance(res['time'], int) + # result should have these additional pruning keys if manual pruning is enabled assert_equal(sorted(res.keys()), sorted(['pruneheight', 'automatic_pruning'] + keys)) diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 3ff74dc5a476d..9d4a5525d14a2 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -515,6 +515,15 @@ def run_test(self): assert_equal(testres['allowed'], True) self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex'], maxfeerate='0.20000000') + self.log.info('sendrawtransaction/testmempoolaccept with tx that is already in the chain') + self.nodes[2].generate(1) + self.sync_blocks() + for node in self.nodes: + testres = node.testmempoolaccept([rawTxSigned['hex']])[0] + assert_equal(testres['allowed'], False) + assert_equal(testres['reject-reason'], 'txn-already-known') + assert_raises_rpc_error(-27, 'Transaction already in block chain', node.sendrawtransaction, rawTxSigned['hex']) + if __name__ == '__main__': RawTransactionsTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 725706947f147..32da2202db7dc 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -297,6 +297,7 @@ 'wallet_startup.py', 'p2p_i2p_ports.py', 'feature_config_args.py', + 'feature_presegwit_node_upgrade.py', 'feature_settings.py', 'rpc_getdescriptorinfo.py', 'rpc_addresses_deprecation.py', diff --git a/test/get_previous_releases.py b/test/get_previous_releases.py index 01e4ef47a7b1d..e92bb402b5888 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")