diff options
67 files changed, 4138 insertions, 1649 deletions
diff --git a/.gitignore b/.gitignore index 810ef5db6b..010586ee5a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ src/bitcoin-cli src/bitcoin-gui src/bitcoin-node src/bitcoin-tx +src/bitcoin-util src/bitcoin-wallet src/test/fuzz/* !src/test/fuzz/*.* diff --git a/Makefile.am b/Makefile.am index b4a55907df..c554f630ae 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,6 +25,7 @@ BITCOIND_BIN=$(top_builddir)/src/$(BITCOIN_DAEMON_NAME)$(EXEEXT) BITCOIN_QT_BIN=$(top_builddir)/src/qt/$(BITCOIN_GUI_NAME)$(EXEEXT) BITCOIN_CLI_BIN=$(top_builddir)/src/$(BITCOIN_CLI_NAME)$(EXEEXT) BITCOIN_TX_BIN=$(top_builddir)/src/$(BITCOIN_TX_NAME)$(EXEEXT) +BITCOIN_UTIL_BIN=$(top_builddir)/src/$(BITCOIN_UTIL_NAME)$(EXEEXT) BITCOIN_WALLET_BIN=$(top_builddir)/src/$(BITCOIN_WALLET_TOOL_NAME)$(EXEEXT) BITCOIN_NODE_BIN=$(top_builddir)/src/$(BITCOIN_MP_NODE_NAME)$(EXEEXT) BITCOIN_GUI_BIN=$(top_builddir)/src/$(BITCOIN_MP_GUI_NAME)$(EXEEXT) @@ -81,6 +82,7 @@ $(BITCOIN_WIN_INSTALLER): all-recursive STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_CLI_BIN) $(top_builddir)/release STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_TX_BIN) $(top_builddir)/release STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_WALLET_BIN) $(top_builddir)/release + STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_UTIL_BIN) $(top_builddir)/release @test -f $(MAKENSIS) && echo 'OutFile "$@"' | cat $(top_builddir)/share/setup.nsi - | $(MAKENSIS) -V2 - || \ echo error: could not build $@ @echo built $@ @@ -177,6 +179,9 @@ $(BITCOIN_CLI_BIN): FORCE $(BITCOIN_TX_BIN): FORCE $(MAKE) -C src $(@F) +$(BITCOIN_UTIL_BIN): FORCE + $(MAKE) -C src $(@F) + $(BITCOIN_WALLET_BIN): FORCE $(MAKE) -C src $(@F) diff --git a/configure.ac b/configure.ac index 0fa99cfc47..b64c22450c 100644 --- a/configure.ac +++ b/configure.ac @@ -23,6 +23,7 @@ BITCOIN_DAEMON_NAME=bitcoind BITCOIN_GUI_NAME=bitcoin-qt BITCOIN_CLI_NAME=bitcoin-cli BITCOIN_TX_NAME=bitcoin-tx +BITCOIN_UTIL_NAME=bitcoin-util BITCOIN_WALLET_TOOL_NAME=bitcoin-wallet dnl Multi Process BITCOIN_MP_NODE_NAME=bitcoin-node @@ -571,7 +572,7 @@ CPPFLAGS="$CPPFLAGS -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS" AC_ARG_WITH([utils], [AS_HELP_STRING([--with-utils], - [build bitcoin-cli bitcoin-tx bitcoin-wallet (default=yes)])], + [build bitcoin-cli bitcoin-tx bitcoin-util bitcoin-wallet (default=yes)])], [build_bitcoin_utils=$withval], [build_bitcoin_utils=yes]) @@ -593,6 +594,12 @@ AC_ARG_ENABLE([util-wallet], [build_bitcoin_wallet=$enableval], [build_bitcoin_wallet=$build_bitcoin_utils]) +AC_ARG_ENABLE([util-util], + [AS_HELP_STRING([--enable-util-util], + [build bitcoin-util])], + [build_bitcoin_util=$enableval], + [build_bitcoin_util=$build_bitcoin_utils]) + AC_ARG_WITH([libs], [AS_HELP_STRING([--with-libs], [build libraries (default=yes)])], @@ -1209,6 +1216,7 @@ if test "x$enable_fuzz" = "xyes"; then build_bitcoin_utils=no build_bitcoin_cli=no build_bitcoin_tx=no + build_bitcoin_util=no build_bitcoin_wallet=no build_bitcoind=no build_bitcoin_libs=no @@ -1433,7 +1441,7 @@ fi dnl univalue check need_bundled_univalue=yes -if test x$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench = xnonononononono; then +if test x$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoin_util$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench = xnononononononono; then need_bundled_univalue=no else if test x$system_univalue != xno; then @@ -1516,6 +1524,10 @@ AC_MSG_CHECKING([whether to build bitcoin-wallet]) AM_CONDITIONAL([BUILD_BITCOIN_WALLET], [test x$build_bitcoin_wallet = xyes]) AC_MSG_RESULT($build_bitcoin_wallet) +AC_MSG_CHECKING([whether to build bitcoin-util]) +AM_CONDITIONAL([BUILD_BITCOIN_UTIL], [test x$build_bitcoin_util = xyes]) +AC_MSG_RESULT($build_bitcoin_util) + AC_MSG_CHECKING([whether to build libraries]) AM_CONDITIONAL([BUILD_BITCOIN_LIBS], [test x$build_bitcoin_libs = xyes]) if test x$build_bitcoin_libs = xyes; then @@ -1717,6 +1729,7 @@ AC_SUBST(BITCOIN_DAEMON_NAME) AC_SUBST(BITCOIN_GUI_NAME) AC_SUBST(BITCOIN_CLI_NAME) AC_SUBST(BITCOIN_TX_NAME) +AC_SUBST(BITCOIN_UTIL_NAME) AC_SUBST(BITCOIN_WALLET_TOOL_NAME) AC_SUBST(BITCOIN_MP_NODE_NAME) AC_SUBST(BITCOIN_MP_GUI_NAME) diff --git a/contrib/devtools/gen-manpages.sh b/contrib/devtools/gen-manpages.sh index b933b264e7..b7bf76ce77 100755 --- a/contrib/devtools/gen-manpages.sh +++ b/contrib/devtools/gen-manpages.sh @@ -14,13 +14,14 @@ BITCOIND=${BITCOIND:-$BINDIR/bitcoind} BITCOINCLI=${BITCOINCLI:-$BINDIR/bitcoin-cli} BITCOINTX=${BITCOINTX:-$BINDIR/bitcoin-tx} WALLET_TOOL=${WALLET_TOOL:-$BINDIR/bitcoin-wallet} +BITCOINUTIL=${BITCOINQT:-$BINDIR/bitcoin-util} BITCOINQT=${BITCOINQT:-$BINDIR/qt/bitcoin-qt} [ ! -x $BITCOIND ] && echo "$BITCOIND not found or not executable." && exit 1 # Don't allow man pages to be generated for binaries built from a dirty tree DIRTY="" -for cmd in $BITCOIND $BITCOINCLI $BITCOINTX $WALLET_TOOL $BITCOINQT; do +for cmd in $BITCOIND $BITCOINCLI $BITCOINTX $WALLET_TOOL $BITCOINUTIL $BITCOINQT; do VERSION_OUTPUT=$($cmd --version) if [[ $VERSION_OUTPUT == *"dirty"* ]]; then DIRTY="${DIRTY}${cmd}\n" @@ -43,7 +44,7 @@ read -r -a BTCVER <<< "$($BITCOINCLI --version | head -n1 | awk -F'[ -]' '{ prin echo "[COPYRIGHT]" > footer.h2m $BITCOIND --version | sed -n '1!p' >> footer.h2m -for cmd in $BITCOIND $BITCOINCLI $BITCOINTX $WALLET_TOOL $BITCOINQT; do +for cmd in $BITCOIND $BITCOINCLI $BITCOINTX $WALLET_TOOL $BITCOINUTIL $BITCOINQT; do cmdname="${cmd##*/}" help2man -N --version-string=${BTCVER[0]} --include=footer.h2m -o ${MANDIR}/${cmdname}.1 ${cmd} sed -i "s/\\\-${BTCVER[1]}//g" ${MANDIR}/${cmdname}.1 diff --git a/contrib/guix/README.md b/contrib/guix/README.md index dffcf99607..dbe1ea837b 100644 --- a/contrib/guix/README.md +++ b/contrib/guix/README.md @@ -40,25 +40,27 @@ Otherwise, follow the [Guix installation guide][guix/bin-install]. Guix allows us to achieve better binary security by using our CPU time to build everything from scratch. However, it doesn't sacrifice user choice in pursuit of -this: users can decide whether or not to bootstrap and to use substitutes. +this: users can decide whether or not to bootstrap and to use substitutes +(pre-built packages). After installation, you may want to consider [adding substitute -servers](#speeding-up-builds-with-substitute-servers) to speed up your build if -that fits your security model (say, if you're just testing that this works). -This is skippable if you're using the [Dockerfile][fanquake/guix-docker]. +servers](#speeding-up-builds-with-substitute-servers) from which to download +pre-built packages to speed up your build if that fits your security model (say, +if you're just testing that this works). Substitute servers are set up by +default if you're using the [Dockerfile][fanquake/guix-docker]. -If you prefer not to use any substitutes, make sure to set -`ADDITIONAL_GUIX_ENVIRONMENT_FLAGS` like the following snippet. The first build -will take a while, but the resulting packages will be cached for future builds. +If you prefer not to use any substitutes, make sure to supply `--no-substitutes` +like in the following snippet. The first build will take a while, but the +resulting packages will be cached for future builds. ```sh -export ADDITIONAL_GUIX_ENVIRONMENT_FLAGS='--no-substitutes' +export ADDITIONAL_GUIX_COMMON_FLAGS='--no-substitutes' ``` Likewise, to perform a bootstrapped build (takes even longer): ```sh -export ADDITIONAL_GUIX_ENVIRONMENT_FLAGS='--bootstrap --no-substitutes' +export ADDITIONAL_GUIX_COMMON_FLAGS='--no-substitutes' ADDITIONAL_GUIX_ENVIRONMENT_FLAGS='--bootstrap' ``` ### Using a version of Guix with `guix time-machine` capabilities @@ -82,17 +84,6 @@ export PATH="${HOME}/.config/guix/current/bin${PATH:+:}$PATH" ## Usage -### As a Development Environment - -For a Bitcoin Core depends development environment, simply invoke - -```sh -guix environment --manifest=contrib/guix/manifest.scm -``` - -And you'll land back in your shell with all the build dependencies required for -a `depends` build injected into your environment. - ### As a Tool for Deterministic Builds From the top of a clean Bitcoin Core repository: @@ -113,10 +104,8 @@ find output/ -type f -print0 | sort -z | xargs -r0 sha256sum * _**HOSTS**_ Override the space-separated list of platform triples for which to perform a - bootstrappable build. _(defaults to "x86\_64-linux-gnu - arm-linux-gnueabihf aarch64-linux-gnu riscv64-linux-gnu")_ - - > Windows and OS X platform triplet support are WIP. + bootstrappable build. _(defaults to "x86\_64-linux-gnu arm-linux-gnueabihf + aarch64-linux-gnu riscv64-linux-gnu x86_64-w64-mingw32")_ * _**SOURCES_PATH**_ @@ -147,13 +136,29 @@ find output/ -type f -print0 | sort -z | xargs -r0 sha256sum string) is interpreted the same way as not setting `V` at all, and that `V=0` has the same effect as `V=1`. -* _**ADDITIONAL_GUIX_ENVIRONMENT_FLAGS**_ +* _**SUBSTITUTE_URLS**_ - Additional flags to be passed to `guix environment`. For a fully-bootstrapped + A whitespace-delimited list of URLs from which to download pre-built packages. + A URL is only used if its signing key is authorized (refer to the [substitute + servers section](#speeding-up-builds-with-substitute-servers) for more + details). + +* _**ADDITIONAL_GUIX_COMMON_FLAGS**_ + + Additional flags to be passed to all `guix` commands. For a fully-bootstrapped build, set this to `--bootstrap --no-substitutes` (refer to the [security model section](#choosing-your-security-model) for more details). Note that a fully-bootstrapped build will take quite a long time on the first run. +* _**ADDITIONAL_GUIX_TIMEMACHINE_FLAGS**_ + + Additional flags to be passed to `guix time-machine`. + +* _**ADDITIONAL_GUIX_ENVIRONMENT_FLAGS**_ + + Additional flags to be passed to the invocation of `guix environment` inside + `guix time-machine`. + ## Tips and Tricks ### Speeding up builds with substitute servers @@ -161,14 +166,15 @@ find output/ -type f -print0 | sort -z | xargs -r0 sha256sum _This whole section is automatically done in the convenience [Dockerfiles][fanquake/guix-docker]_ -For those who are used to life in the fast _(and trustful)_ lane, you can use -[substitute servers][guix/substitutes] to enable binary downloads of packages. +For those who are used to life in the fast _(and trustful)_ lane, you can +specify [substitute servers][guix/substitutes] from which to download pre-built +packages. > For those who only want to use substitutes from the official Guix build farm > and have authorized the build farm's signing key during Guix's installation, > you don't need to do anything. -#### Authorize the signing keys +#### Step 1: Authorize the signing keys For the official Guix build farm at https://ci.guix.gnu.org, run as root: @@ -182,7 +188,7 @@ For dongcarl's substitute server at https://guix.carldong.io, run as root: wget -qO- 'https://guix.carldong.io/signing-key.pub' | guix archive --authorize ``` -#### Use the substitute servers +#### Step 2: Specify the substitute servers The official Guix build farm at https://ci.guix.gnu.org is automatically used unless the `--no-substitutes` flag is supplied. @@ -196,7 +202,7 @@ To use dongcarl's substitute server for Bitcoin Core builds after having [authorized his signing key](#authorize-the-signing-keys): ``` -export ADDITIONAL_GUIX_ENVIRONMENT_FLAGS='--substitute-urls="https://guix.carldong.io https://ci.guix.gnu.org"' +export SUBSTITUTE_URLS='https://guix.carldong.io https://ci.guix.gnu.org' ``` ## FAQ @@ -212,9 +218,9 @@ As mentioned at the bottom of [this manual page][guix/bin-install]: ### When will Guix be packaged in debian? -Vagrant Cascadian has been making good progress on this -[here][debian/guix-package]. We have all the pieces needed to put up an APT -repository and will likely put one up soon. +Thanks to Vagrant Cascadian's diligent work, Guix is now [in debian +experimental][debian/guix-experimental]! Hopefully it will make its way into a +release soon. [b17e]: http://bootstrappable.org/ [r12e/source-date-epoch]: https://reproducible-builds.org/docs/source-date-epoch/ @@ -226,5 +232,5 @@ repository and will likely put one up soon. [guix/substitute-server-auth]: https://www.gnu.org/software/guix/manual/en/html_node/Substitute-Server-Authorization.html [guix/time-machine]: https://guix.gnu.org/manual/en/html_node/Invoking-guix-time_002dmachine.html -[debian/guix-package]: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=850644 +[debian/guix-experimental]: https://packages.debian.org/experimental/guix [fanquake/guix-docker]: https://github.com/fanquake/core-review/tree/master/guix diff --git a/contrib/guix/guix-build.sh b/contrib/guix/guix-build.sh index 11d2c8b867..54cc5793f6 100755 --- a/contrib/guix/guix-build.sh +++ b/contrib/guix/guix-build.sh @@ -2,6 +2,116 @@ export LC_ALL=C set -e -o pipefail +################### +## Sanity Checks ## +################### + +################ +# Check 1: Make sure that we can invoke required tools +################ +for cmd in git make guix cat mkdir; do + if ! command -v "$cmd" > /dev/null 2>&1; then + echo "ERR: This script requires that '$cmd' is installed and available in your \$PATH" + exit 1 + fi +done + +################ +# Check 2: Make sure GUIX_BUILD_OPTIONS is empty +################ +# +# GUIX_BUILD_OPTIONS is an environment variable recognized by guix commands that +# can perform builds. This seems like what we want instead of +# ADDITIONAL_GUIX_COMMON_FLAGS, but the value of GUIX_BUILD_OPTIONS is actually +# _appended_ to normal command-line options. Meaning that they will take +# precedence over the command-specific ADDITIONAL_GUIX_<CMD>_FLAGS. +# +# This seems like a poor user experience. Thus we check for GUIX_BUILD_OPTIONS's +# existence here and direct users of this script to use our (more flexible) +# custom environment variables. +if [ -n "$GUIX_BUILD_OPTIONS" ]; then +cat << EOF +Error: Environment variable GUIX_BUILD_OPTIONS is not empty: + '$GUIX_BUILD_OPTIONS' + +Unfortunately this script is incompatible with GUIX_BUILD_OPTIONS, please unset +GUIX_BUILD_OPTIONS and use ADDITIONAL_GUIX_COMMON_FLAGS to set build options +across guix commands or ADDITIONAL_GUIX_<CMD>_FLAGS to set build options for a +specific guix command. + +See contrib/guix/README.md for more details. +EOF +exit 1 +fi + +################ +# Check 3: Make sure that we're not in a dirty worktree +################ +if ! git diff-index --quiet HEAD -- && [ -z "$FORCE_DIRTY_WORKTREE" ]; then +cat << EOF +ERR: The current git worktree is dirty, which may lead to broken builds. + + Aborting... + +Hint: To make your git worktree clean, You may want to: + 1. Commit your changes, + 2. Stash your changes, or + 3. Set the 'FORCE_DIRTY_WORKTREE' environment variable if you insist on + using a dirty worktree +EOF +exit 1 +else + GIT_COMMIT=$(git rev-parse --short=12 HEAD) +fi + +################ +# Check 4: Make sure that build directories do no exist +################ + +# Default to building for all supported HOSTs (overridable by environment) +export HOSTS="${HOSTS:-x86_64-linux-gnu arm-linux-gnueabihf aarch64-linux-gnu riscv64-linux-gnu + x86_64-w64-mingw32}" + +DISTSRC_BASE="${DISTSRC_BASE:-${PWD}}" + +# Usage: distsrc_for_host HOST +# +# HOST: The current platform triple we're building for +# +distsrc_for_host() { + echo "${DISTSRC_BASE}/distsrc-${GIT_COMMIT}-${1}" +} + +# Accumulate a list of build directories that already exist... +hosts_distsrc_exists="" +for host in $HOSTS; do + if [ -e "$(distsrc_for_host "$host")" ]; then + hosts_distsrc_exists+=" ${host}" + fi +done + +if [ -n "$hosts_distsrc_exists" ]; then +# ...so that we can print them out nicely in an error message +cat << EOF +ERR: Build directories for this commit already exist for the following platform + triples you're attempting to build, probably because of previous builds. + Please remove, or otherwise deal with them prior to starting another build. + + Aborting... + +EOF +for host in $hosts_distsrc_exists; do + echo " ${host} '$(distsrc_for_host "$host")'" +done +exit 1 +else + mkdir -p "$DISTSRC_BASE" +fi + +######### +# Setup # +######### + # Determine the maximum number of jobs to run simultaneously (overridable by # environment) MAX_JOBS="${MAX_JOBS:-$(nproc)}" @@ -16,11 +126,23 @@ SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git log --format=%at -1)}" # Execute "$@" in a pinned, possibly older version of Guix, for reproducibility # across time. time-machine() { + # shellcheck disable=SC2086 guix time-machine --url=https://github.com/dongcarl/guix.git \ --commit=b066c25026f21fb57677aa34692a5034338e7ee3 \ + --max-jobs="$MAX_JOBS" \ + ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \ + ${ADDITIONAL_GUIX_COMMON_FLAGS} ${ADDITIONAL_GUIX_TIMEMACHINE_FLAGS} \ -- "$@" } +# Make sure an output directory exists for our builds +OUTDIR="${OUTDIR:-${PWD}/output}" +[ -e "$OUTDIR" ] || mkdir -p "$OUTDIR" + +######### +# Build # +######### + # Function to be called when building for host ${1} and the user interrupts the # build int_trap() { @@ -38,9 +160,9 @@ and untracked files and directories will be wiped, allowing you to start anew. EOF } -# Deterministically build Bitcoin Core for HOSTs (overridable by environment) +# Deterministically build Bitcoin Core # shellcheck disable=SC2153 -for host in ${HOSTS=x86_64-linux-gnu arm-linux-gnueabihf aarch64-linux-gnu riscv64-linux-gnu x86_64-w64-mingw32}; do +for host in $HOSTS; do # Display proper warning when the user interrupts the build trap 'int_trap ${host}' INT @@ -50,6 +172,19 @@ for host in ${HOSTS=x86_64-linux-gnu arm-linux-gnueabihf aarch64-linux-gnu riscv # for the particular $HOST we're building for export HOST="$host" + # shellcheck disable=SC2030 +cat << EOF +INFO: Building commit ${GIT_COMMIT:?not set} for platform triple ${HOST:?not set}: + ...using reference timestamp: ${SOURCE_DATE_EPOCH:?not set} + ...running at most ${MAX_JOBS:?not set} jobs + ...from worktree directory: '${PWD}' + ...bind-mounted in container to: '/bitcoin' + ...in build directory: '$(distsrc_for_host "$HOST")' + ...bind-mounted in container to: '$(DISTSRC_BASE=/distsrc-base && distsrc_for_host "$HOST")' + ...outputting in: '${OUTDIR:?not set}' + ...bind-mounted in container to: '/outdir' +EOF + # Run the build script 'contrib/guix/libexec/build.sh' in the build # container specified by 'contrib/guix/manifest.scm'. # @@ -99,20 +234,36 @@ for host in ${HOSTS=x86_64-linux-gnu arm-linux-gnueabihf aarch64-linux-gnu riscv # make the downloaded depends sources available to it. The sources # should have been downloaded prior to this invocation. # - # shellcheck disable=SC2086 + # ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} + # + # fetch substitute from SUBSTITUTE_URLS if they are + # authorized + # + # Depending on the user's security model, it may be desirable to use + # substitutes (pre-built packages) from servers that the user trusts. + # Please read the README.md in the same directory as this file for + # more information. + # + # shellcheck disable=SC2086,SC2031 time-machine environment --manifest="${PWD}/contrib/guix/manifest.scm" \ --container \ --pure \ --no-cwd \ --share="$PWD"=/bitcoin \ + --share="$DISTSRC_BASE"=/distsrc-base \ + --share="$OUTDIR"=/outdir \ --expose="$(git rev-parse --git-common-dir)" \ ${SOURCES_PATH:+--share="$SOURCES_PATH"} \ - ${ADDITIONAL_GUIX_ENVIRONMENT_FLAGS} \ + --max-jobs="$MAX_JOBS" \ + ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \ + ${ADDITIONAL_GUIX_COMMON_FLAGS} ${ADDITIONAL_GUIX_ENVIRONMENT_FLAGS} \ -- env HOST="$host" \ MAX_JOBS="$MAX_JOBS" \ SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:?unable to determine value}" \ ${V:+V=1} \ ${SOURCES_PATH:+SOURCES_PATH="$SOURCES_PATH"} \ + DISTSRC="$(DISTSRC_BASE=/distsrc-base && distsrc_for_host "$HOST")" \ + OUTDIR=/outdir \ bash -c "cd /bitcoin && bash contrib/guix/libexec/build.sh" ) diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index d658c4f6a6..b00c42ce01 100644 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -11,9 +11,15 @@ if [ -n "$V" ]; then export VERBOSE="$V" fi -# Check that environment variables assumed to be set by the environment are set -echo "Building for platform triple ${HOST:?not set} with reference timestamp ${SOURCE_DATE_EPOCH:?not set}..." -echo "At most ${MAX_JOBS:?not set} jobs will run at once..." +# Check that required environment variables are set +cat << EOF +Required environment variables as seen inside the container: + HOST: ${HOST:?not set} + SOURCE_DATE_EPOCH: ${SOURCE_DATE_EPOCH:?not set} + MAX_JOBS: ${MAX_JOBS:?not set} + DISTSRC: ${DISTSRC:?not set} + OUTDIR: ${OUTDIR:?not set} +EOF ##################### # Environment Setup # @@ -23,19 +29,6 @@ echo "At most ${MAX_JOBS:?not set} jobs will run at once..." # $HOSTs after successfully building. BASEPREFIX="${PWD}/depends" -# Setup an output directory for our build -OUTDIR="${OUTDIR:-${PWD}/output}" -[ -e "$OUTDIR" ] || mkdir -p "$OUTDIR" - -# Setup the directory where our Bitcoin Core build for HOST will occur -DISTSRC="${DISTSRC:-${PWD}/distsrc-${HOST}}" -if [ -e "$DISTSRC" ]; then - echo "DISTSRC directory '${DISTSRC}' exists, probably because of previous builds... Aborting..." - exit 1 -else - mkdir -p "$DISTSRC" -fi - # Given a package name and an output name, return the path of that output in our # current guix environment store_path() { @@ -189,6 +182,7 @@ esac # Make $HOST-specific native binaries from depends available in $PATH export PATH="${BASEPREFIX}/${HOST}/native/bin:${PATH}" +mkdir -p "$DISTSRC" ( cd "$DISTSRC" diff --git a/contrib/signet/README.md b/contrib/signet/README.md index c4aa5ae2f7..71dc2f9638 100644 --- a/contrib/signet/README.md +++ b/contrib/signet/README.md @@ -17,3 +17,64 @@ Syntax: `getcoins.py [-h|--help] [-c|--cmd=<bitcoin-cli path>] [-f|--faucet=<fau If using the default network, invoking the script with no arguments should be sufficient under normal circumstances, but if multiple people are behind the same IP address, the faucet will by default only accept one claim per day. See `--password` above. + +miner +===== + +To mine the first block in your custom chain, you can run: + + cd src/ + CLI="./bitcoin-cli -conf=mysignet.conf" + MINER="..contrib/signet/miner" + GRIND="./bitcoin-util grind" + ADDR=$($CLI -signet getnewaddress) + $MINER --cli="$CLI" generate --grind-cmd="$GRIND" --address="$ADDR" --set-block-time=-1 + +This will mine a block with the current timestamp. If you want to backdate the chain, you can give a different timestamp to --set-block-time. + +You will then need to pick a difficulty target. Since signet chains are primarily protected by a signature rather than proof of work, there is no need to spend as much energy as possible mining, however you may wish to choose to spend more time than the absolute minimum. The calibrate subcommand can be used to pick a target, eg: + + $MINER calibrate --grind-cmd="$GRIND" + nbits=1e00f403 for 25s average mining time + +It defaults to estimating an nbits value resulting in 25s average time to find a block, but the --seconds parameter can be used to pick a different target, or the --nbits parameter can be used to estimate how long it will take for a given difficulty. + +Using the --ongoing parameter will then cause the signet miner to create blocks indefinitely. It will pick the time between blocks so that difficulty is adjusted to match the provided --nbits value. + + $MINER --cli="$CLI" generate --grind-cmd="$GRIND" --address="$ADDR" --nbits=1e00f403 --ongoing + +Other options +------------- + +The --debug and --quiet options are available to control how noisy the signet miner's output is. Note that the --debug, --quiet and --cli parameters must all appear before the subcommand (generate, calibrate, etc) if used. + +Instead of specifying --ongoing, you can specify --max-blocks=N to mine N blocks and stop. + +Instead of using a single address, a ranged descriptor may be provided instead (via the --descriptor parameter), with the reward for the block at height H being sent to the H'th address generated from the descriptor. + +Instead of calculating a specific nbits value, --min-nbits can be specified instead, in which case the mininmum signet difficulty will be targeted. + +By default, the signet miner mines blocks at fixed intervals with minimal variation. If you want blocks to appear more randomly, as they do in mainnet, specify the --poisson option. + +Using the --multiminer parameter allows mining to be distributed amongst multiple miners. For example, if you have 3 miners and want to share blocks between them, specify --multiminer=1/3 on one, --multiminer=2/3 on another, and --multiminer=3/3 on the last one. If you want one to do 10% of blocks and two others to do 45% each, --multiminer=1-10/100 on the first, and --multiminer=11-55 and --multiminer=56-100 on the others. Note that which miner mines which block is determined by the previous block hash, so occasional runs of one miner doing many blocks in a row is to be expected. + +When --multiminer is used, if a miner is down and does not mine a block within five minutes of when it is due, the other miners will automatically act as redundant backups ensuring the chain does not halt. The --backup-delay parameter can be used to change how long a given miner waits, allowing one to be the primary backup (after five minutes) and another to be the secondary backup (after six minutes, eg). + +The --standby-delay parameter can be used to make a backup miner that only mines if a block doesn't arrive on time. This can be combined with --multiminer if desired. Setting --standby-delay also prevents the first block from being mined immediately. + +Advanced usage +-------------- + +The process generate follows internally is to get a block template, convert that into a PSBT, sign the PSBT, move the signature from the signed PSBT into the block template's coinbase, grind proof of work for the block, and then submit the block to the network. + +These steps can instead be done explicitly: + + $CLI -signet getblocktemplate '{"rules": ["signet","segwit"]}' | + $MINER --cli="$CLI" genpsbt --address="$ADDR" | + $CLI -signet -stdin walletprocesspsbt | + jq -r .psbt | + $MINER --cli="$CLI" solvepsbt --grind-cmd="$GRIND" | + $CLI -signet -stdin submitblock + +This is intended to allow you to replace part of the pipeline for further experimentation, if desired. + diff --git a/contrib/signet/miner b/contrib/signet/miner new file mode 100755 index 0000000000..a3fba49d0e --- /dev/null +++ b/contrib/signet/miner @@ -0,0 +1,639 @@ +#!/usr/bin/env python3 +# 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. + +import argparse +import base64 +import json +import logging +import math +import os.path +import re +import struct +import sys +import time +import subprocess + +from binascii import unhexlify +from io import BytesIO + +PATH_BASE_CONTRIB_SIGNET = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) +PATH_BASE_TEST_FUNCTIONAL = os.path.abspath(os.path.join(PATH_BASE_CONTRIB_SIGNET, "..", "..", "test", "functional")) +sys.path.insert(0, PATH_BASE_TEST_FUNCTIONAL) + +from test_framework.blocktools import WITNESS_COMMITMENT_HEADER, script_BIP34_coinbase_height # noqa: E402 +from test_framework.messages import CBlock, CBlockHeader, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, FromHex, ToHex, deser_string, hash256, ser_compact_size, ser_string, ser_uint256, uint256_from_str # noqa: E402 +from test_framework.script import CScriptOp # noqa: E402 + +logging.basicConfig( + format='%(asctime)s %(levelname)s %(message)s', + level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') + +SIGNET_HEADER = b"\xec\xc7\xda\xa2" +PSBT_SIGNET_BLOCK = b"\xfc\x06signetb" # proprietary PSBT global field holding the block being signed +RE_MULTIMINER = re.compile("^(\d+)(-(\d+))?/(\d+)$") + +# #### some helpers that could go into test_framework + +# like FromHex, but without the hex part +def FromBinary(cls, stream): + """deserialize a binary stream (or bytes object) into an object""" + # handle bytes object by turning it into a stream + was_bytes = isinstance(stream, bytes) + if was_bytes: + stream = BytesIO(stream) + obj = cls() + obj.deserialize(stream) + if was_bytes: + assert len(stream.read()) == 0 + return obj + +class PSBTMap: + """Class for serializing and deserializing PSBT maps""" + + def __init__(self, map=None): + self.map = map if map is not None else {} + + def deserialize(self, f): + m = {} + while True: + k = deser_string(f) + if len(k) == 0: + break + v = deser_string(f) + if len(k) == 1: + k = k[0] + assert k not in m + m[k] = v + self.map = m + + def serialize(self): + m = b"" + for k,v in self.map.items(): + if isinstance(k, int) and 0 <= k and k <= 255: + k = bytes([k]) + m += ser_compact_size(len(k)) + k + m += ser_compact_size(len(v)) + v + m += b"\x00" + return m + +class PSBT: + """Class for serializing and deserializing PSBTs""" + + def __init__(self): + self.g = PSBTMap() + self.i = [] + self.o = [] + self.tx = None + + def deserialize(self, f): + assert f.read(5) == b"psbt\xff" + self.g = FromBinary(PSBTMap, f) + assert 0 in self.g.map + self.tx = FromBinary(CTransaction, self.g.map[0]) + self.i = [FromBinary(PSBTMap, f) for _ in self.tx.vin] + self.o = [FromBinary(PSBTMap, f) for _ in self.tx.vout] + return self + + def serialize(self): + assert isinstance(self.g, PSBTMap) + assert isinstance(self.i, list) and all(isinstance(x, PSBTMap) for x in self.i) + assert isinstance(self.o, list) and all(isinstance(x, PSBTMap) for x in self.o) + assert 0 in self.g.map + tx = FromBinary(CTransaction, self.g.map[0]) + assert len(tx.vin) == len(self.i) + assert len(tx.vout) == len(self.o) + + psbt = [x.serialize() for x in [self.g] + self.i + self.o] + return b"psbt\xff" + b"".join(psbt) + + def to_base64(self): + return base64.b64encode(self.serialize()).decode("utf8") + + @classmethod + def from_base64(cls, b64psbt): + return FromBinary(cls, base64.b64decode(b64psbt)) + +# ##### + +def create_coinbase(height, value, spk): + cb = CTransaction() + cb.vin = [CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff)] + cb.vout = [CTxOut(value, spk)] + return cb + +def get_witness_script(witness_root, witness_nonce): + commitment = uint256_from_str(hash256(ser_uint256(witness_root) + ser_uint256(witness_nonce))) + return b"\x6a" + CScriptOp.encode_op_pushdata(WITNESS_COMMITMENT_HEADER + ser_uint256(commitment)) + +def signet_txs(block, challenge): + # assumes signet solution has not been added yet so does not need + # to be removed + + txs = block.vtx[:] + txs[0] = CTransaction(txs[0]) + txs[0].vout[-1].scriptPubKey += CScriptOp.encode_op_pushdata(SIGNET_HEADER) + hashes = [] + for tx in txs: + tx.rehash() + hashes.append(ser_uint256(tx.sha256)) + mroot = block.get_merkle_root(hashes) + + sd = b"" + sd += struct.pack("<i", block.nVersion) + sd += ser_uint256(block.hashPrevBlock) + sd += ser_uint256(mroot) + sd += struct.pack("<I", block.nTime) + + to_spend = CTransaction() + to_spend.nVersion = 0 + to_spend.nLockTime = 0 + to_spend.vin = [CTxIn(COutPoint(0, 0xFFFFFFFF), b"\x00" + CScriptOp.encode_op_pushdata(sd), 0)] + to_spend.vout = [CTxOut(0, challenge)] + to_spend.rehash() + + spend = CTransaction() + spend.nVersion = 0 + spend.nLockTime = 0 + spend.vin = [CTxIn(COutPoint(to_spend.sha256, 0), b"", 0)] + spend.vout = [CTxOut(0, b"\x6a")] + + return spend, to_spend + +def do_createpsbt(block, signme, spendme): + psbt = PSBT() + psbt.g = PSBTMap( {0: signme.serialize(), + PSBT_SIGNET_BLOCK: block.serialize() + } ) + psbt.i = [ PSBTMap( {0: spendme.serialize(), + 3: bytes([1,0,0,0])}) + ] + psbt.o = [ PSBTMap() ] + return psbt.to_base64() + +def do_decode_psbt(b64psbt): + psbt = PSBT.from_base64(b64psbt) + + assert len(psbt.tx.vin) == 1 + assert len(psbt.tx.vout) == 1 + assert PSBT_SIGNET_BLOCK in psbt.g.map + + scriptSig = psbt.i[0].map.get(7, b"") + scriptWitness = psbt.i[0].map.get(8, b"\x00") + + return FromBinary(CBlock, psbt.g.map[PSBT_SIGNET_BLOCK]), ser_string(scriptSig) + scriptWitness + +def finish_block(block, signet_solution, grind_cmd): + block.vtx[0].vout[-1].scriptPubKey += CScriptOp.encode_op_pushdata(SIGNET_HEADER + signet_solution) + block.vtx[0].rehash() + block.hashMerkleRoot = block.calc_merkle_root() + if grind_cmd is None: + block.solve() + else: + headhex = CBlockHeader.serialize(block).hex() + cmd = grind_cmd.split(" ") + [headhex] + newheadhex = subprocess.run(cmd, stdout=subprocess.PIPE, input=b"", check=True).stdout.strip() + newhead = FromHex(CBlockHeader(), newheadhex.decode('utf8')) + block.nNonce = newhead.nNonce + block.rehash() + return block + +def generate_psbt(tmpl, reward_spk, *, blocktime=None): + signet_spk = tmpl["signet_challenge"] + signet_spk_bin = unhexlify(signet_spk) + + cbtx = create_coinbase(height=tmpl["height"], value=tmpl["coinbasevalue"], spk=reward_spk) + cbtx.vin[0].nSequence = 2**32-2 + cbtx.rehash() + + block = CBlock() + block.nVersion = tmpl["version"] + block.hashPrevBlock = int(tmpl["previousblockhash"], 16) + block.nTime = tmpl["curtime"] if blocktime is None else blocktime + if block.nTime < tmpl["mintime"]: + block.nTime = tmpl["mintime"] + block.nBits = int(tmpl["bits"], 16) + block.nNonce = 0 + block.vtx = [cbtx] + [FromHex(CTransaction(), t["data"]) for t in tmpl["transactions"]] + + witnonce = 0 + witroot = block.calc_witness_merkle_root() + cbwit = CTxInWitness() + cbwit.scriptWitness.stack = [ser_uint256(witnonce)] + block.vtx[0].wit.vtxinwit = [cbwit] + block.vtx[0].vout.append(CTxOut(0, get_witness_script(witroot, witnonce))) + + signme, spendme = signet_txs(block, signet_spk_bin) + + return do_createpsbt(block, signme, spendme) + +def get_reward_address(args, height): + if args.address is not None: + return args.address + + if '*' not in args.descriptor: + addr = json.loads(args.bcli("deriveaddresses", args.descriptor))[0] + args.address = addr + return addr + + remove = [k for k in args.derived_addresses.keys() if k+20 <= height] + for k in remove: + del args.derived_addresses[k] + + addr = args.derived_addresses.get(height, None) + if addr is None: + addrs = json.loads(args.bcli("deriveaddresses", args.descriptor, "[%d,%d]" % (height, height+20))) + addr = addrs[0] + for k, a in enumerate(addrs): + args.derived_addresses[height+k] = a + + return addr + +def get_reward_addr_spk(args, height): + assert args.address is not None or args.descriptor is not None + + if hasattr(args, "reward_spk"): + return args.address, args.reward_spk + + reward_addr = get_reward_address(args, height) + reward_spk = unhexlify(json.loads(args.bcli("getaddressinfo", reward_addr))["scriptPubKey"]) + if args.address is not None: + # will always be the same, so cache + args.reward_spk = reward_spk + + return reward_addr, reward_spk + +def do_genpsbt(args): + tmpl = json.load(sys.stdin) + _, reward_spk = get_reward_addr_spk(args, tmpl["height"]) + psbt = generate_psbt(tmpl, reward_spk) + print(psbt) + +def do_solvepsbt(args): + block, signet_solution = do_decode_psbt(sys.stdin.read()) + block = finish_block(block, signet_solution, args.grind_cmd) + print(ToHex(block)) + +def nbits_to_target(nbits): + shift = (nbits >> 24) & 0xff + return (nbits & 0x00ffffff) * 2**(8*(shift - 3)) + +def target_to_nbits(target): + tstr = "{0:x}".format(target) + if len(tstr) < 6: + tstr = ("000000"+tstr)[-6:] + if len(tstr) % 2 != 0: + tstr = "0" + tstr + if int(tstr[0],16) >= 0x8: + # avoid "negative" + tstr = "00" + tstr + fix = int(tstr[:6], 16) + sz = len(tstr)//2 + if tstr[6:] != "0"*(sz*2-6): + fix += 1 + + return int("%02x%06x" % (sz,fix), 16) + +def seconds_to_hms(s): + if s == 0: + return "0s" + neg = (s < 0) + if neg: + s = -s + out = "" + if s % 60 > 0: + out = "%ds" % (s % 60) + s //= 60 + if s % 60 > 0: + out = "%dm%s" % (s % 60, out) + s //= 60 + if s > 0: + out = "%dh%s" % (s, out) + if neg: + out = "-" + out + return out + +def next_block_delta(last_nbits, last_hash, ultimate_target, do_poisson): + # strategy: + # 1) work out how far off our desired target we are + # 2) cap it to a factor of 4 since that's the best we can do in a single retarget period + # 3) use that to work out the desired average interval in this retarget period + # 4) if doing poisson, use the last hash to pick a uniformly random number in [0,1), and work out a random multiplier to vary the average by + # 5) cap the resulting interval between 1 second and 1 hour to avoid extremes + + INTERVAL = 600.0*2016/2015 # 10 minutes, adjusted for the off-by-one bug + + current_target = nbits_to_target(last_nbits) + retarget_factor = ultimate_target / current_target + retarget_factor = max(0.25, min(retarget_factor, 4.0)) + + avg_interval = INTERVAL * retarget_factor + + if do_poisson: + det_rand = int(last_hash[-8:], 16) * 2**-32 + this_interval_variance = -math.log1p(-det_rand) + else: + this_interval_variance = 1 + + this_interval = avg_interval * this_interval_variance + this_interval = max(1, min(this_interval, 3600)) + + return this_interval + +def next_block_is_mine(last_hash, my_blocks): + det_rand = int(last_hash[-16:-8], 16) + return my_blocks[0] <= (det_rand % my_blocks[2]) < my_blocks[1] + +def do_generate(args): + if args.max_blocks is not None: + if args.ongoing: + logging.error("Cannot specify both --ongoing and --max-blocks") + return 1 + if args.max_blocks < 1: + logging.error("N must be a positive integer") + return 1 + max_blocks = args.max_blocks + elif args.ongoing: + max_blocks = None + else: + max_blocks = 1 + + if args.set_block_time is not None and max_blocks != 1: + logging.error("Cannot specify --ongoing or --max-blocks > 1 when using --set-block-time") + return 1 + if args.set_block_time is not None and args.set_block_time < 0: + args.set_block_time = time.time() + logging.info("Treating negative block time as current time (%d)" % (args.set_block_time)) + + if args.min_nbits: + if args.nbits is not None: + logging.error("Cannot specify --nbits and --min-nbits") + return 1 + args.nbits = "1e0377ae" + logging.info("Using nbits=%s" % (args.nbits)) + + if args.set_block_time is None: + if args.nbits is None or len(args.nbits) != 8: + logging.error("Must specify --nbits (use calibrate command to determine value)") + return 1 + + if args.multiminer is None: + my_blocks = (0,1,1) + else: + if not args.ongoing: + logging.error("Cannot specify --multiminer without --ongoing") + return 1 + m = RE_MULTIMINER.match(args.multiminer) + if m is None: + logging.error("--multiminer argument must be k/m or j-k/m") + return 1 + start,_,stop,total = m.groups() + if stop is None: + stop = start + start, stop, total = map(int, (start, stop, total)) + if stop < start or start <= 0 or total < stop or total == 0: + logging.error("Inconsistent values for --multiminer") + return 1 + my_blocks = (start-1, stop, total) + + ultimate_target = nbits_to_target(int(args.nbits,16)) + + mined_blocks = 0 + bestheader = {"hash": None} + lastheader = None + while max_blocks is None or mined_blocks < max_blocks: + + # current status? + bci = json.loads(args.bcli("getblockchaininfo")) + + if bestheader["hash"] != bci["bestblockhash"]: + bestheader = json.loads(args.bcli("getblockheader", bci["bestblockhash"])) + + if lastheader is None: + lastheader = bestheader["hash"] + elif bestheader["hash"] != lastheader: + next_delta = next_block_delta(int(bestheader["bits"], 16), bestheader["hash"], ultimate_target, args.poisson) + next_delta += bestheader["time"] - time.time() + next_is_mine = next_block_is_mine(bestheader["hash"], my_blocks) + logging.info("Received new block at height %d; next in %s (%s)", bestheader["height"], seconds_to_hms(next_delta), ("mine" if next_is_mine else "backup")) + lastheader = bestheader["hash"] + + # when is the next block due to be mined? + now = time.time() + if args.set_block_time is not None: + logging.debug("Setting start time to %d", args.set_block_time) + mine_time = args.set_block_time + action_time = now + is_mine = True + elif bestheader["height"] == 0: + logging.error("When mining first block in a new signet, must specify --set-block-time") + return 1 + else: + + time_delta = next_block_delta(int(bestheader["bits"], 16), bci["bestblockhash"], ultimate_target, args.poisson) + mine_time = bestheader["time"] + time_delta + + is_mine = next_block_is_mine(bci["bestblockhash"], my_blocks) + + action_time = mine_time + if not is_mine: + action_time += args.backup_delay + + if args.standby_delay > 0: + action_time += args.standby_delay + elif mined_blocks == 0: + # for non-standby, always mine immediately on startup, + # even if the next block shouldn't be ours + action_time = now + + # don't want fractional times so round down + mine_time = int(mine_time) + action_time = int(action_time) + + # can't mine a block 2h in the future; 1h55m for some safety + action_time = max(action_time, mine_time - 6900) + + # ready to go? otherwise sleep and check for new block + if now < action_time: + sleep_for = min(action_time - now, 60) + if mine_time < now: + # someone else might have mined the block, + # so check frequently, so we don't end up late + # mining the next block if it's ours + sleep_for = min(20, sleep_for) + minestr = "mine" if is_mine else "backup" + logging.debug("Sleeping for %s, next block due in %s (%s)" % (seconds_to_hms(sleep_for), seconds_to_hms(mine_time - now), minestr)) + time.sleep(sleep_for) + continue + + # gbt + tmpl = json.loads(args.bcli("getblocktemplate", '{"rules":["signet","segwit"]}')) + if tmpl["previousblockhash"] != bci["bestblockhash"]: + logging.warning("GBT based off unexpected block (%s not %s), retrying", tmpl["previousblockhash"], bci["bestblockhash"]) + time.sleep(1) + continue + + logging.debug("GBT template: %s", tmpl) + + if tmpl["mintime"] > mine_time: + logging.info("Updating block time from %d to %d", mine_time, tmpl["mintime"]) + mine_time = tmpl["mintime"] + if mine_time > now: + logging.error("GBT mintime is in the future: %d is %d seconds later than %d", mine_time, (mine_time-now), now) + return 1 + + # address for reward + reward_addr, reward_spk = get_reward_addr_spk(args, tmpl["height"]) + + # mine block + logging.debug("Mining block delta=%s start=%s mine=%s", seconds_to_hms(mine_time-bestheader["time"]), mine_time, is_mine) + mined_blocks += 1 + psbt = generate_psbt(tmpl, reward_spk, blocktime=mine_time) + psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=psbt.encode('utf8'))) + if not psbt_signed.get("complete",False): + logging.debug("Generated PSBT: %s" % (psbt,)) + sys.stderr.write("PSBT signing failed") + return 1 + block, signet_solution = do_decode_psbt(psbt_signed["psbt"]) + block = finish_block(block, signet_solution, args.grind_cmd) + + # submit block + r = args.bcli("-stdin", "submitblock", input=ToHex(block).encode('utf8')) + + # report + bstr = "block" if is_mine else "backup block" + + next_delta = next_block_delta(block.nBits, block.hash, ultimate_target, args.poisson) + next_delta += block.nTime - time.time() + next_is_mine = next_block_is_mine(block.hash, my_blocks) + + logging.debug("Block hash %s payout to %s", block.hash, reward_addr) + logging.info("Mined %s at height %d; next in %s (%s)", bstr, tmpl["height"], seconds_to_hms(next_delta), ("mine" if next_is_mine else "backup")) + if r != "": + logging.warning("submitblock returned %s for height %d hash %s", r, tmpl["height"], block.hash) + lastheader = block.hash + +def do_calibrate(args): + if args.nbits is not None and args.seconds is not None: + sys.stderr.write("Can only specify one of --nbits or --seconds\n") + return 1 + if args.nbits is not None and len(args.nbits) != 8: + sys.stderr.write("Must specify 8 hex digits for --nbits") + return 1 + + TRIALS = 600 # gets variance down pretty low + TRIAL_BITS = 0x1e3ea75f # takes about 5m to do 600 trials + #TRIAL_BITS = 0x1e7ea75f # XXX + + header = CBlockHeader() + header.nBits = TRIAL_BITS + targ = nbits_to_target(header.nBits) + + start = time.time() + count = 0 + #CHECKS=[] + for i in range(TRIALS): + header.nTime = i + header.nNonce = 0 + headhex = header.serialize().hex() + cmd = args.grind_cmd.split(" ") + [headhex] + newheadhex = subprocess.run(cmd, stdout=subprocess.PIPE, input=b"", check=True).stdout.strip() + #newhead = FromHex(CBlockHeader(), newheadhex.decode('utf8')) + #count += newhead.nNonce + #if (i+1) % 100 == 0: + # CHECKS.append((i+1, count, time.time()-start)) + + #print("checks =", [c*1.0 / (b*targ*2**-256) for _,b,c in CHECKS]) + + avg = (time.time() - start) * 1.0 / TRIALS + #exp_count = 2**256 / targ * TRIALS + #print("avg =", avg, "count =", count, "exp_count =", exp_count) + + if args.nbits is not None: + want_targ = nbits_to_target(int(args.nbits,16)) + want_time = avg*targ/want_targ + else: + want_time = args.seconds if args.seconds is not None else 25 + want_targ = int(targ*(avg/want_time)) + + print("nbits=%08x for %ds average mining time" % (target_to_nbits(want_targ), want_time)) + return 0 + +def bitcoin_cli(basecmd, args, **kwargs): + cmd = basecmd + ["-signet"] + args + logging.debug("Calling bitcoin-cli: %r", cmd) + out = subprocess.run(cmd, stdout=subprocess.PIPE, **kwargs, check=True).stdout + if isinstance(out, bytes): + out = out.decode('utf8') + return out.strip() + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--cli", default="bitcoin-cli", type=str, help="bitcoin-cli command") + parser.add_argument("--debug", action="store_true", help="Print debugging info") + parser.add_argument("--quiet", action="store_true", help="Only print warnings/errors") + + cmds = parser.add_subparsers(help="sub-commands") + genpsbt = cmds.add_parser("genpsbt", help="Generate a block PSBT for signing") + genpsbt.set_defaults(fn=do_genpsbt) + + solvepsbt = cmds.add_parser("solvepsbt", help="Solve a signed block PSBT") + solvepsbt.set_defaults(fn=do_solvepsbt) + + generate = cmds.add_parser("generate", help="Mine blocks") + generate.set_defaults(fn=do_generate) + generate.add_argument("--ongoing", action="store_true", help="Keep mining blocks") + generate.add_argument("--max-blocks", default=None, type=int, help="Max blocks to mine (default=1)") + generate.add_argument("--set-block-time", default=None, type=int, help="Set block time (unix timestamp)") + generate.add_argument("--nbits", default=None, type=str, help="Target nBits (specify difficulty)") + generate.add_argument("--min-nbits", action="store_true", help="Target minimum nBits (use min difficulty)") + generate.add_argument("--poisson", action="store_true", help="Simulate randomised block times") + #generate.add_argument("--signcmd", default=None, type=str, help="Alternative signing command") + generate.add_argument("--multiminer", default=None, type=str, help="Specify which set of blocks to mine (eg: 1-40/100 for the first 40%%, 2/3 for the second 3rd)") + generate.add_argument("--backup-delay", default=300, type=int, help="Seconds to delay before mining blocks reserved for other miners (default=300)") + generate.add_argument("--standby-delay", default=0, type=int, help="Seconds to delay before mining blocks (default=0)") + + calibrate = cmds.add_parser("calibrate", help="Calibrate difficulty") + calibrate.set_defaults(fn=do_calibrate) + calibrate.add_argument("--nbits", type=str, default=None) + calibrate.add_argument("--seconds", type=int, default=None) + + for sp in [genpsbt, generate]: + sp.add_argument("--address", default=None, type=str, help="Address for block reward payment") + sp.add_argument("--descriptor", default=None, type=str, help="Descriptor for block reward payment") + + for sp in [solvepsbt, generate, calibrate]: + sp.add_argument("--grind-cmd", default=None, type=str, help="Command to grind a block header for proof-of-work") + + args = parser.parse_args(sys.argv[1:]) + + args.bcli = lambda *a, input=b"", **kwargs: bitcoin_cli(args.cli.split(" "), list(a), input=input, **kwargs) + + if hasattr(args, "address") and hasattr(args, "descriptor"): + if args.address is None and args.descriptor is None: + sys.stderr.write("Must specify --address or --descriptor\n") + return 1 + elif args.address is not None and args.descriptor is not None: + sys.stderr.write("Only specify one of --address or --descriptor\n") + return 1 + args.derived_addresses = {} + + if args.debug: + logging.getLogger().setLevel(logging.DEBUG) + elif args.quiet: + logging.getLogger().setLevel(logging.WARNING) + else: + logging.getLogger().setLevel(logging.INFO) + + if hasattr(args, "fn"): + return args.fn(args) + else: + logging.error("Must specify command") + return 1 + +if __name__ == "__main__": + main() + + diff --git a/doc/REST-interface.md b/doc/REST-interface.md index 3b127703b7..6237734390 100644 --- a/doc/REST-interface.md +++ b/doc/REST-interface.md @@ -4,7 +4,7 @@ Unauthenticated REST Interface The REST API can be enabled with the `-rest` option. The interface runs on the same port as the JSON-RPC interface, by default port 8332 for mainnet, port 18332 for testnet, -and port 18443 for regtest. +port 38332 for signet, and port 18443 for regtest. REST Interface consistency guarantees ------------------------------------- @@ -62,7 +62,7 @@ Given a height: returns hash of block in best-block-chain at height provided. Returns various state info regarding block chain processing. Only supports JSON as output format. -* chain : (string) current network name (main, test, regtest) +* chain : (string) current network name (main, test, signet, regtest) * blocks : (numeric) the current number of blocks processed in the server * headers : (numeric) the current number of headers we have validated * bestblockhash : (string) the hash of the currently best block diff --git a/doc/guix.md b/doc/guix.md new file mode 100644 index 0000000000..fa9c74f486 --- /dev/null +++ b/doc/guix.md @@ -0,0 +1,3 @@ +# Bootstrappable Bitcoin Core Builds + +See [contrib/guix/README.md](../contrib/guix/README.md) diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am index edbc0911a1..8f890da532 100644 --- a/doc/man/Makefile.am +++ b/doc/man/Makefile.am @@ -16,6 +16,10 @@ if BUILD_BITCOIN_TX dist_man1_MANS+=bitcoin-tx.1 endif +if BUILD_BITCOIN_UTIL + dist_man1_MANS+=bitcoin-util.1 +endif + if ENABLE_WALLET if BUILD_BITCOIN_WALLET dist_man1_MANS+=bitcoin-wallet.1 diff --git a/doc/man/bitcoin-cli.1 b/doc/man/bitcoin-cli.1 index 588ae81fce..6bcad7006b 100644 --- a/doc/man/bitcoin-cli.1 +++ b/doc/man/bitcoin-cli.1 @@ -2,4 +2,4 @@ .SH NAME bitcoin-cli \- manual page for bitcoin-cli -This is a placefolder file. Please follow the instructions in \fIcontrib/devtools/README.md\fR to generate the manual pages after a release. +This is a placeholder file. Please follow the instructions in \fIcontrib/devtools/README.md\fR to generate the manual pages after a release. diff --git a/doc/man/bitcoin-qt.1 b/doc/man/bitcoin-qt.1 index 9c75e9fe54..ff4d1d2c7a 100644 --- a/doc/man/bitcoin-qt.1 +++ b/doc/man/bitcoin-qt.1 @@ -2,4 +2,4 @@ .SH NAME bitcoin-qt \- manual page for bitcoin-qt -This is a placefolder file. Please follow the instructions in \fIcontrib/devtools/README.md\fR to generate the manual pages after a release. +This is a placeholder file. Please follow the instructions in \fIcontrib/devtools/README.md\fR to generate the manual pages after a release. diff --git a/doc/man/bitcoin-tx.1 b/doc/man/bitcoin-tx.1 index 148a5890b0..776bb46234 100644 --- a/doc/man/bitcoin-tx.1 +++ b/doc/man/bitcoin-tx.1 @@ -2,4 +2,4 @@ .SH NAME bitcoin-tx \- manual page for bitcoin-tx -This is a placefolder file. Please follow the instructions in \fIcontrib/devtools/README.md\fR to generate the manual pages after a release. +This is a placeholder file. Please follow the instructions in \fIcontrib/devtools/README.md\fR to generate the manual pages after a release. diff --git a/doc/man/bitcoin-util.1 b/doc/man/bitcoin-util.1 new file mode 100644 index 0000000000..5c733c6e21 --- /dev/null +++ b/doc/man/bitcoin-util.1 @@ -0,0 +1,5 @@ +.TH BITCOIN-UTIL "1" +.SH NAME +bitcoin-util \- manual page for bitcoin-util + +This is a placeholder file. Please follow the instructions in \fIcontrib/devtools/README.md\fR to generate the manual pages after a release. diff --git a/doc/man/bitcoin-wallet.1 b/doc/man/bitcoin-wallet.1 index 69133b33f7..2da43dec66 100644 --- a/doc/man/bitcoin-wallet.1 +++ b/doc/man/bitcoin-wallet.1 @@ -2,4 +2,4 @@ .SH NAME bitcoin-wallet \- manual page for bitcoin-wallet -This is a placefolder file. Please follow the instructions in \fIcontrib/devtools/README.md\fR to generate the manual pages after a release. +This is a placeholder file. Please follow the instructions in \fIcontrib/devtools/README.md\fR to generate the manual pages after a release. diff --git a/doc/man/bitcoind.1 b/doc/man/bitcoind.1 index de338182ff..2c88f74520 100644 --- a/doc/man/bitcoind.1 +++ b/doc/man/bitcoind.1 @@ -2,4 +2,4 @@ .SH NAME bitcoind \- manual page for bitcoind -This is a placefolder file. Please follow the instructions in \fIcontrib/devtools/README.md\fR to generate the manual pages after a release. +This is a placeholder file. Please follow the instructions in \fIcontrib/devtools/README.md\fR to generate the manual pages after a release. diff --git a/doc/release-notes/release-notes-0.21.0.md b/doc/release-notes/release-notes-0.21.0.md new file mode 100644 index 0000000000..66aee77643 --- /dev/null +++ b/doc/release-notes/release-notes-0.21.0.md @@ -0,0 +1,1336 @@ +0.21.0 Release Notes +==================== + +Bitcoin Core version 0.21.0 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-0.21.0/> + +This release includes new features, various bug fixes and performance +improvements, as well as updated translations. + +Please report bugs using the issue tracker at GitHub: + + <https://github.com/bitcoin/bitcoin/issues> + +To receive security and update notifications, please subscribe to: + + <https://bitcoincore.org/en/list/announcements/join/> + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes in some cases), then run the +installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on Mac) +or `bitcoind`/`bitcoin-qt` (on Linux). + +Upgrading directly from a version of Bitcoin Core that has reached its EOL is +possible, but it might take some time if the data directory needs to be migrated. Old +wallet versions of Bitcoin Core are generally supported. + +Compatibility +============== + +Bitcoin Core is supported and extensively tested on operating systems +using the Linux kernel, macOS 10.12+, and Windows 7 and newer. Bitcoin +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 0.20.0 onwards, macOS versions earlier than 10.12 are no +longer supported. Additionally, Bitcoin Core does not yet change appearance +when macOS "dark mode" is activated. + +The node's known peers are persisted to disk in a file called `peers.dat`. The +format of this file has been changed in a backwards-incompatible way in order to +accommodate the storage of Tor v3 and other BIP155 addresses. This means that if +the file is modified by 0.21.0 or newer then older versions will not be able to +read it. Those old versions, in the event of a downgrade, will log an error +message "Incorrect keysize in addrman deserialization" and will continue normal +operation as if the file was missing, creating a new empty one. (#19954, #20284) + +Notable changes +=============== + +P2P and network changes +----------------------- + +- The mempool now tracks whether transactions submitted via the wallet or RPCs + have been successfully broadcast. Every 10-15 minutes, the node will try to + announce unbroadcast transactions until a peer requests it via a `getdata` + message or the transaction is removed from the mempool for other reasons. + The node will not track the broadcast status of transactions submitted to the + node using P2P relay. This version reduces the initial broadcast guarantees + for wallet transactions submitted via P2P to a node running the wallet. (#18038) + +- The size of the set of transactions that peers have announced and we consider + for requests has been reduced from 100000 to 5000 (per peer), and further + announcements will be ignored when that limit is reached. If you need to dump + (very) large batches of transactions, exceptions can be made for trusted + peers using the "relay" network permission. For localhost for example it can + be enabled using the command line option `-whitelist=relay@127.0.0.1`. + (#19988) + +- This release adds support for Tor version 3 hidden services, and rumoring them + over the network to other peers using + [BIP155](https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki). + Version 2 hidden services are still fully supported by Bitcoin Core, but the + Tor network will start + [deprecating](https://blog.torproject.org/v2-deprecation-timeline) them in the + coming months. (#19954) + +- The Tor onion service that is automatically created by setting the + `-listenonion` configuration parameter will now be created as a Tor v3 service + instead of Tor v2. The private key that was used for Tor v2 (if any) will be + left untouched in the `onion_private_key` file in the data directory (see + `-datadir`) and can be removed if not needed. Bitcoin Core will no longer + attempt to read it. The private key for the Tor v3 service will be saved in a + file named `onion_v3_private_key`. To use the deprecated Tor v2 service (not + recommended), the `onion_private_key` can be copied over + `onion_v3_private_key`, e.g. + `cp -f onion_private_key onion_v3_private_key`. (#19954) + +- The client writes a file (`anchors.dat`) at shutdown with the network addresses + of the node’s two outbound block-relay-only peers (so called "anchors"). The + next time the node starts, it reads this file and attempts to reconnect to those + same two peers. This prevents an attacker from using node restarts to trigger a + complete change in peers, which would be something they could use as part of an + eclipse attack. (#17428) + +- This release adds support for serving + [BIP157](https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki) compact + filters to peers on the network when enabled using + `-blockfilterindex=1 -peercfilters=1`. (#16442) + +- This release adds support for signets + ([BIP325](https://github.com/bitcoin/bips/blob/master/bip-0325.mediawiki)) in + addition to the existing mainnet, testnet, and regtest networks. Signets are + centrally-controlled test networks, allowing them to be more predictable + test environments than the older testnet. One public signet is maintained, and + selectable using `-signet`. It is also possible to create personal signets. + (#18267). + +- This release implements + [BIP339](https://github.com/bitcoin/bips/blob/master/bip-0339.mediawiki) + wtxid relay. When negotiated, transactions are announced using their wtxid + instead of their txid. (#18044). + +- This release implements the proposed Taproot consensus rules + ([BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki) and + [BIP342](https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki)), + without activation on mainnet. Experimentation with Taproot can be done on + signet, where its rules are already active. (#19553) + +Updated RPCs +------------ + +- The `getpeerinfo` RPC has a new `network` field that provides the type of + network ("ipv4", "ipv6", or "onion") that the peer connected through. (#20002) + +- The `getpeerinfo` RPC now has additional `last_block` and `last_transaction` + fields that return the UNIX epoch time of the last block and the last *valid* + transaction received from each peer. (#19731) + +- `getnetworkinfo` now returns two new fields, `connections_in` and + `connections_out`, that provide the number of inbound and outbound peer + connections. These new fields are in addition to the existing `connections` + field, which returns the total number of peer connections. (#19405) + +- Exposed transaction version numbers are now treated as unsigned 32-bit + integers instead of signed 32-bit integers. This matches their treatment in + consensus logic. Versions greater than 2 continue to be non-standard + (matching previous behavior of smaller than 1 or greater than 2 being + non-standard). Note that this includes the `joinpsbt` command, which combines + partially-signed transactions by selecting the highest version number. + (#16525) + +- `getmempoolinfo` now returns an additional `unbroadcastcount` field. The + mempool tracks locally submitted transactions until their initial broadcast + is acknowledged by a peer. This field returns the count of transactions + waiting for acknowledgement. + +- Mempool RPCs such as `getmempoolentry` and `getrawmempool` with + `verbose=true` now return an additional `unbroadcast` field. This indicates + whether initial broadcast of the transaction has been acknowledged by a + peer. `getmempoolancestors` and `getmempooldescendants` are also updated. + +- The `getpeerinfo` RPC no longer returns the `banscore` field unless the configuration + option `-deprecatedrpc=banscore` is used. The `banscore` field will be fully + removed in the next major release. (#19469) + +- The `testmempoolaccept` RPC returns `vsize` and a `fees` object with the `base` fee + if the transaction would pass validation. (#19940) + +- The `getpeerinfo` RPC now returns a `connection_type` field. This indicates + the type of connection established with the peer. It will return one of six + options. For more information, see the `getpeerinfo` help documentation. + (#19725) + +- The `getpeerinfo` RPC no longer returns the `addnode` field by default. This + field will be fully removed in the next major release. It can be accessed + with the configuration option `-deprecatedrpc=getpeerinfo_addnode`. However, + it is recommended to instead use the `connection_type` field (it will return + `manual` when addnode is true). (#19725) + +- The `getpeerinfo` RPC no longer returns the `whitelisted` field by default. + This field will be fully removed in the next major release. It can be accessed + with the configuration option `-deprecatedrpc=getpeerinfo_whitelisted`. However, + it is recommended to instead use the `permissions` field to understand if specific + privileges have been granted to the peer. (#19770) + +- The `walletcreatefundedpsbt` RPC call will now fail with + `Insufficient funds` when inputs are manually selected but are not enough to cover + the outputs and fee. Additional inputs can automatically be added through the + new `add_inputs` option. (#16377) + +- The `fundrawtransaction` RPC now supports `add_inputs` option that when `false` + prevents adding more inputs if necessary and consequently the RPC fails. + +Changes to Wallet or GUI related RPCs can be found in the GUI or Wallet section below. + +New RPCs +-------- + +- The `getindexinfo` RPC returns the actively running indices of the node, + including their current sync status and height. It also accepts an `index_name` + to specify returning the status of that index only. (#19550) + +Build System +------------ + +Updated settings +---------------- + +- The same ZeroMQ notification (e.g. `-zmqpubhashtx=address`) can now be + specified multiple times to publish the same notification to different ZeroMQ + sockets. (#18309) + +- The `-banscore` configuration option, which modified the default threshold for + disconnecting and discouraging misbehaving peers, has been removed as part of + changes in 0.20.1 and in this release to the handling of misbehaving peers. + Refer to "Changes regarding misbehaving peers" in the 0.20.1 release notes for + details. (#19464) + +- The `-debug=db` logging category, which was deprecated in 0.20 and replaced by + `-debug=walletdb` to distinguish it from `coindb`, has been removed. (#19202) + +- A `download` permission has been extracted from the `noban` permission. For + compatibility, `noban` implies the `download` permission, but this may change + in future releases. Refer to the help of the affected settings `-whitebind` + and `-whitelist` for more details. (#19191) + +- Netmasks that contain 1-bits after 0-bits (the 1-bits are not contiguous on + the left side, e.g. 255.0.255.255) are no longer accepted. They are invalid + according to RFC 4632. Netmasks are used in the `-rpcallowip` and `-whitelist` + configuration options and in the `setban` RPC. (#19628) + +- The `-blocksonly` setting now completely disables fee estimation. (#18766) + +Changes to Wallet or GUI related settings can be found in the GUI or Wallet section below. + +Tools and Utilities +------------------- + +- A new `bitcoin-cli -netinfo` command provides a network peer connections + dashboard that displays data from the `getpeerinfo` and `getnetworkinfo` RPCs + in a human-readable format. An optional integer argument from `0` to `4` may + be passed to see increasing levels of detail. (#19643) + +- A new `bitcoin-cli -generate` command, equivalent to RPC `generatenewaddress` + followed by `generatetoaddress`, can generate blocks for command line testing + purposes. This is a client-side version of the former `generate` RPC. See the + help for details. (#19133) + +- The `bitcoin-cli -getinfo` command now displays the wallet name and balance for + each of the loaded wallets when more than one is loaded (e.g. in multiwallet + mode) and a wallet is not specified with `-rpcwallet`. (#18594) + +- The `connections` field of `bitcoin-cli -getinfo` is now expanded to return a JSON + object with `in`, `out` and `total` numbers of peer connections. It previously + returned a single integer value for the total number of peer connections. (#19405) + +New settings +------------ + +- The `startupnotify` option is used to specify a command to + execute when Bitcoin Core has finished with its startup + sequence. (#15367) + +Wallet +------ + +- Backwards compatibility has been dropped for two `getaddressinfo` RPC + deprecations, as notified in the 0.20 release notes. The deprecated `label` + field has been removed as well as the deprecated `labels` behavior of + returning a JSON object containing `name` and `purpose` key-value pairs. Since + 0.20, the `labels` field returns a JSON array of label names. (#19200) + +- To improve wallet privacy, the frequency of wallet rebroadcast attempts is + reduced from approximately once every 15 minutes to once every 12-36 hours. + To maintain a similar level of guarantee for initial broadcast of wallet + transactions, the mempool tracks these transactions as a part of the newly + introduced unbroadcast set. See the "P2P and network changes" section for + more information on the unbroadcast set. (#18038) + +- The `sendtoaddress` and `sendmany` RPCs accept an optional `verbose=True` + argument to also return the fee reason about the sent tx. (#19501) + +- The wallet can create a transaction without change even when the keypool is + empty. Previously it failed. (#17219) + +- The `-salvagewallet` startup option has been removed. A new `salvage` command + has been added to the `bitcoin-wallet` tool which performs the salvage + operations that `-salvagewallet` did. (#18918) + +- A new configuration flag `-maxapsfee` has been added, which sets the max + allowed avoid partial spends (APS) fee. It defaults to 0 (i.e. fee is the + same with and without APS). Setting it to -1 will disable APS, unless + `-avoidpartialspends` is set. (#14582) + +- The wallet will now avoid partial spends (APS) by default, if this does not + result in a difference in fees compared to the non-APS variant. The allowed + fee threshold can be adjusted using the new `-maxapsfee` configuration + option. (#14582) + +- The `createwallet`, `loadwallet`, and `unloadwallet` RPCs now accept + `load_on_startup` options to modify the settings list. Unless these options + are explicitly set to true or false, the list is not modified, so the RPC + methods remain backwards compatible. (#15937) + +- A new `send` RPC with similar syntax to `walletcreatefundedpsbt`, including + support for coin selection and a custom fee rate, is added. The `send` RPC is + experimental and may change in subsequent releases. (#16378) + +- The `estimate_mode` parameter is now case-insensitive in the `bumpfee`, + `fundrawtransaction`, `sendmany`, `sendtoaddress`, `send` and + `walletcreatefundedpsbt` RPCs. (#11413) + +- The `bumpfee` RPC now uses `conf_target` rather than `confTarget` in the + options. (#11413) + +- `fundrawtransaction` and `walletcreatefundedpsbt` when used with the + `lockUnspents` argument now lock manually selected coins, in addition to + automatically selected coins. Note that locked coins are never used in + automatic coin selection, but can still be manually selected. (#18244) + +- The `-zapwallettxes` startup option has been removed and its functionality + removed from the wallet. This option was originally intended to allow for + rescuing wallets which were affected by a malleability attack. More recently, + it has been used in the fee bumping of transactions that did not signal RBF. + This functionality has been superseded with the abandon transaction feature. (#19671) + +- The error code when no wallet is loaded, but a wallet RPC is called, has been + changed from `-32601` (method not found) to `-18` (wallet not found). + (#20101) + +### Automatic wallet creation removed + +Bitcoin Core will no longer automatically create new wallets on startup. It will +load existing wallets specified by `-wallet` options on the command line or in +`bitcoin.conf` or `settings.json` files. And by default it will also load a +top-level unnamed ("") wallet. However, if specified wallets don't exist, +Bitcoin Core will now just log warnings instead of creating new wallets with +new keys and addresses like previous releases did. + +New wallets can be created through the GUI (which has a more prominent create +wallet option), through the `bitcoin-cli createwallet` or `bitcoin-wallet +create` commands, or the `createwallet` RPC. (#15454, #20186) + +### Experimental Descriptor Wallets + +Please note that Descriptor Wallets are still experimental and not all expected functionality +is available. Additionally there may be some bugs and current functions may change in the future. +Bugs and missing functionality can be reported to the [issue tracker](https://github.com/bitcoin/bitcoin/issues). + +0.21 introduces a new type of wallet - Descriptor Wallets. Descriptor Wallets store +scriptPubKey information using output descriptors. This is in contrast to the Legacy Wallet +structure where keys are used to implicitly generate scriptPubKeys and addresses. Because of this +shift to being script based instead of key based, many of the confusing things that Legacy +Wallets do are not possible with Descriptor Wallets. Descriptor Wallets use a definition +of "mine" for scripts which is simpler and more intuitive than that used by Legacy Wallets. +Descriptor Wallets also uses different semantics for watch-only things and imports. + +As Descriptor Wallets are a new type of wallet, their introduction does not affect existing wallets. +Users who already have a Bitcoin Core wallet can continue to use it as they did before without +any change in behavior. Newly created Legacy Wallets (which remains the default type of wallet) will +behave as they did in previous versions of Bitcoin Core. + +The differences between Descriptor Wallets and Legacy Wallets are largely limited to non user facing +things. They are intended to behave similarly except for the import/export and watchonly functionality +as described below. + +#### Creating Descriptor Wallets + +Descriptor wallets are not the default type of wallet. + +In the GUI, a checkbox has been added to the Create Wallet Dialog to indicate that a +Descriptor Wallet should be created. And a `descriptors` option has been added to `createwallet` RPC. +Setting `descriptors` to `true` will create a Descriptor Wallet instead of a Legacy Wallet. + +Without those options being set, a Legacy Wallet will be created instead. + +#### `IsMine` Semantics + +`IsMine` refers to the function used to determine whether a script belongs to the wallet. +This is used to determine whether an output belongs to the wallet. `IsMine` in Legacy Wallets +returns true if the wallet would be able to sign an input that spends an output with that script. +Since keys can be involved in a variety of different scripts, this definition for `IsMine` can +lead to many unexpected scripts being considered part of the wallet. + +With Descriptor Wallets, descriptors explicitly specify the set of scripts that are owned by +the wallet. Since descriptors are deterministic and easily enumerable, users will know exactly +what scripts the wallet will consider to belong to it. Additionally the implementation of `IsMine` +in Descriptor Wallets is far simpler than for Legacy Wallets. Notably, in Legacy Wallets, `IsMine` +allowed for users to take one type of address (e.g. P2PKH), mutate it into another address type +(e.g. P2WPKH), and the wallet would still detect outputs sending to the new address type +even without that address being requested from the wallet. Descriptor Wallets do not +allow for this and will only watch for the addresses that were explicitly requested from the wallet. + +These changes to `IsMine` will make it easier to reason about what scripts the wallet will +actually be watching for in outputs. However for the vast majority of users, this change is +largely transparent and will not have noticeable effect. + +#### Imports and Exports + +In Legacy Wallets, raw scripts and keys could be imported to the wallet. Those imported scripts +and keys are treated separately from the keys generated by the wallet. This complicates the `IsMine` +logic as it has to distinguish between spendable and watchonly. + +Descriptor Wallets handle importing scripts and keys differently. Only complete descriptors can be +imported. These descriptors are then added to the wallet as if it were a descriptor generated by +the wallet itself. This simplifies the `IsMine` logic so that it no longer has to distinguish +between spendable and watchonly. As such, the watchonly model for Descriptor Wallets is also +different and described in more detail in the next section. + +To import into a Descriptor Wallet, a new `importdescriptors` RPC has been added that uses a syntax +similar to that of `importmulti`. + +As Legacy Wallets and Descriptor Wallets use different mechanisms for storing and importing scripts and keys +the existing import RPCs have been disabled for descriptor wallets. +New export RPCs for Descriptor Wallets have not yet been added. + +The following RPCs are disabled for Descriptor Wallets: + +* `importprivkey` +* `importpubkey` +* `importaddress` +* `importwallet` +* `dumpprivkey` +* `dumpwallet` +* `importmulti` +* `addmultisigaddress` +* `sethdseed` + +#### Watchonly Wallets + +A Legacy Wallet contains both private keys and scripts that were being watched. +Those watched scripts would not contribute to your normal balance. In order to see the watchonly +balance and to use watchonly things in transactions, an `include_watchonly` option was added +to many RPCs that would allow users to do that. However it is easy to forget to include this option. + +Descriptor Wallets move to a per-wallet watchonly model. Instead an entire wallet is considered to be +watchonly depending on whether it was created with private keys disabled. This eliminates the need +to distinguish between things that are watchonly and things that are not within a wallet itself. + +This change does have a caveat. If a Descriptor Wallet with private keys *enabled* has +a multiple key descriptor without all of the private keys (e.g. `multi(...)` with only one private key), +then the wallet will fail to sign and broadcast transactions. Such wallets would need to use the PSBT +workflow but the typical GUI Send, `sendtoaddress`, etc. workflows would still be available, just +non-functional. + +This issue is worsened if the wallet contains both single key (e.g. `wpkh(...)`) descriptors and such +multiple key descriptors as some transactions could be signed and broadcast and others not. This is +due to some transactions containing only single key inputs, while others would contain both single +key and multiple key inputs, depending on which are available and how the coin selection algorithm +selects inputs. However this is not considered to be a supported use case; multisigs +should be in their own wallets which do not already have descriptors. Although users cannot export +descriptors with private keys for now as explained earlier. + +#### BIP 44/49/84 Support + +The change to using descriptors changes the default derivation paths used by Bitcoin Core +to adhere to BIP 44/49/84. Descriptors with different derivation paths can be imported without +issue. + +#### SQLite Database Backend + +Descriptor wallets use SQLite for the wallet file instead of the Berkeley DB used in legacy wallets. +This will break compatibility with any existing tooling that operates on wallets, however compatibility +was already being broken by the move to descriptors. + +### Wallet RPC changes + +- The `upgradewallet` RPC replaces the `-upgradewallet` command line option. + (#15761) + +- The `settxfee` RPC will fail if the fee was set higher than the `-maxtxfee` + command line setting. The wallet will already fail to create transactions + with fees higher than `-maxtxfee`. (#18467) + +- A new `fee_rate` parameter/option denominated in satoshis per vbyte (sat/vB) + is introduced to the `sendtoaddress`, `sendmany`, `fundrawtransaction` and + `walletcreatefundedpsbt` RPCs as well as to the experimental new `send` + RPC. The legacy `feeRate` option in `fundrawtransaction` and + `walletcreatefundedpsbt` still exists for setting a fee rate in BTC per 1,000 + vbytes (BTC/kvB), but it is expected to be deprecated soon to avoid + confusion. For these RPCs, the fee rate error message is updated from BTC/kB + to sat/vB and the help documentation in BTC/kB is updated to BTC/kvB. The + `send` and `sendtoaddress` RPC examples are updated to aid users in creating + transactions with explicit fee rates. (#20305, #11413) + +- The `bumpfee` RPC `fee_rate` option is changed from BTC/kvB to sat/vB and the + help documentation is updated. Users are warned that this is a breaking API + change, but it should be relatively benign: the large (100,000 times) + difference between BTC/kvB and sat/vB units means that a transaction with a + fee rate mistakenly calculated in BTC/kvB rather than sat/vB should raise an + error due to the fee rate being set too low. In the worst case, the + transaction may send at 1 sat/vB, but as Replace-by-Fee (BIP125 RBF) is active + by default when an explicit fee rate is used, the transaction fee can be + bumped. (#20305) + +GUI changes +----------- + +- Wallets created or loaded in the GUI will now be automatically loaded on + startup, so they don't need to be manually reloaded next time Bitcoin Core is + started. The list of wallets to load on startup is stored in + `\<datadir\>/settings.json` and augments any command line or `bitcoin.conf` + `-wallet=` settings that specify more wallets to load. Wallets that are + unloaded in the GUI get removed from the settings list so they won't load + again automatically next startup. (#19754) + +- The GUI Peers window no longer displays a "Ban Score" field. This is part of + changes in 0.20.1 and in this release to the handling of misbehaving + peers. Refer to "Changes regarding misbehaving peers" in the 0.20.1 release + notes for details. (#19512) + +Low-level changes +================= + +RPC +--- + +- To make RPC `sendtoaddress` more consistent with `sendmany` the following error + `sendtoaddress` codes were changed from `-4` to `-6`: + - Insufficient funds + - Fee estimation failed + - Transaction has too long of a mempool chain + +- The `sendrawtransaction` error code for exceeding `maxfeerate` has been changed from + `-26` to `-25`. The error string has been changed from "absurdly-high-fee" to + "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)." The + `testmempoolaccept` RPC returns `max-fee-exceeded` rather than `absurdly-high-fee` + as the `reject-reason`. (#19339) + +- To make wallet and rawtransaction RPCs more consistent, the error message for + exceeding maximum feerate has been changed to "Fee exceeds maximum configured by user + (e.g. -maxtxfee, maxfeerate)." (#19339) + +Tests +----- + +- The BIP 325 default signet can be enabled by the `-chain=signet` or `-signet` + setting. The settings `-signetchallenge` and `-signetseednode` allow + enabling a custom signet. + +- The `generateblock` RPC allows testers using regtest mode to + generate blocks that consist of a custom set of transactions. (#17693) + +0.21.0 change log +================= + +### Consensus +- #18267 BIP-325: Signet (kallewoof) +- #20016 uint256: 1 is a constant (ajtowns) +- #20006 Fix misleading error message: Clean stack rule (sanket1729) +- #19953 Implement BIP 340-342 validation (Schnorr/taproot/tapscript) (sipa) +- #20169 Taproot follow-up: Make ComputeEntrySchnorr and ComputeEntryECDSA const to clarify contract (practicalswift) + +### Policy +- #18766 Disable fee estimation in blocksonly mode (darosior) +- #19630 Cleanup fee estimation code (darosior) +- #20165 Only relay Taproot spends if next block has it active (sipa) + +### Mining +- #17946 Fix GBT: Restore "!segwit" and "csv" to "rules" key (luke-jr) + +### Privacy +- #16432 Add privacy to the Overview page (hebasto) +- #18861 Do not answer GETDATA for to-be-announced tx (sipa) +- #18038 Mempool tracks locally submitted transactions to improve wallet privacy (amitiuttarwar) +- #19109 Only allow getdata of recently announced invs (sipa) + +### Block and transaction handling +- #17737 Add ChainstateManager, remove BlockManager global (jamesob) +- #18960 indexes: Add compact block filter headers cache (jnewbery) +- #13204 Faster sigcache nonce (JeremyRubin) +- #19088 Use std::chrono throughout some validation functions (fanquake) +- #19142 Make VerifyDB level 4 interruptible (MarcoFalke) +- #17994 Flush undo files after last block write (kallewoof) +- #18990 log: Properly log txs rejected from mempool (MarcoFalke) +- #18984 Remove unnecessary input blockfile SetPos (dgenr8) +- #19526 log: Avoid treating remote misbehvior as local system error (MarcoFalke) +- #18044 Use wtxid for transaction relay (sdaftuar) +- #18637 coins: allow cache resize after init (jamesob) +- #19854 Avoid locking CTxMemPool::cs recursively in simple cases (hebasto) +- #19478 Remove CTxMempool::mapLinks data structure member (JeremyRubin) +- #19927 Reduce direct `g_chainman` usage (dongcarl) +- #19898 log: print unexpected version warning in validation log category (n-thumann) +- #20036 signet: Add assumed values for default signet (MarcoFalke) +- #20048 chainparams: do not log signet startup messages for other chains (jonatack) +- #19339 re-delegate absurd fee checking from mempool to clients (glozow) +- #20035 signet: Fix uninitialized read in validation (MarcoFalke) +- #20157 Bugfix: chainparams: Add missing (always enabled) Taproot deployment for Signet (luke-jr) +- #20263 Update assumed chain params (MarcoFalke) +- #20372 Avoid signed integer overflow when loading a mempool.dat file with a malformed time field (practicalswift) +- #18621 script: Disallow silent bool -> cscript conversion (MarcoFalke) +- #18612, #18732 script: Remove undocumented and unused operator+ (MarcoFalke) +- #19317 Add a left-justified width field to `log2_work` component for a uniform debug.log output (jamesgmorgan) + +### P2P protocol and network code +- #18544 Limit BIP37 filter lifespan (active between `filterload`..`filterclear`) (theStack) +- #18806 Remove is{Empty,Full} flags from CBloomFilter, clarify CVE fix (theStack) +- #18512 Improve asmap checks and add sanity check (sipa) +- #18877 Serve cfcheckpt requests (jnewbery) +- #18895 Unbroadcast followups: rpcs, nLastResend, mempool sanity check (gzhao408) +- #19010 net processing: Add support for `getcfheaders` (jnewbery) +- #16939 Delay querying DNS seeds (ajtowns) +- #18807 Unbroadcast follow-ups (amitiuttarwar) +- #19044 Add support for getcfilters (jnewbery) +- #19084 improve code documentation for dns seed behaviour (ajtowns) +- #19260 disconnect peers that send filterclear + update existing filter msg disconnect logic (gzhao408) +- #19284 Add seed.bitcoin.wiz.biz to DNS seeds (wiz) +- #19322 split PushInventory() (jnewbery) +- #19204 Reduce inv traffic during IBD (MarcoFalke) +- #19470 banlist: log post-swept banlist size at startup (fanquake) +- #19191 Extract download permission from noban (MarcoFalke) +- #14033 Drop `CADDR_TIME_VERSION` checks now that `MIN_PEER_PROTO_VERSION` is greater (Empact) +- #19464 net, rpc: remove -banscore option, deprecate banscore in getpeerinfo (jonatack) +- #19514 [net/net processing] check banman pointer before dereferencing (jnewbery) +- #19512 banscore updates to gui, tests, release notes (jonatack) +- #19360 improve encapsulation of CNetAddr (vasild) +- #19217 disambiguate block-relay-only variable names from blocksonly variables (glowang) +- #19473 Add -networkactive option (hebasto) +- #19472 [net processing] Reduce `cs_main` scope in MaybeDiscourageAndDisconnect() (jnewbery) +- #19583 clean up Misbehaving() (jnewbery) +- #19534 save the network type explicitly in CNetAddr (vasild) +- #19569 Enable fetching of orphan parents from wtxid peers (sipa) +- #18991 Cache responses to GETADDR to prevent topology leaks (naumenkogs) +- #19596 Deduplicate parent txid loop of requested transactions and missing parents of orphan transactions (sdaftuar) +- #19316 Cleanup logic around connection types (amitiuttarwar) +- #19070 Signal support for compact block filters with `NODE_COMPACT_FILTERS` (jnewbery) +- #19705 Shrink CAddress from 48 to 40 bytes on x64 (vasild) +- #19704 Move ProcessMessage() to PeerLogicValidation (jnewbery) +- #19628 Change CNetAddr::ip to have flexible size (vasild) +- #19797 Remove old check for 3-byte shifted IP addresses from pre-0.2.9 nodes (#19797) +- #19607 Add Peer struct for per-peer data in net processing (jnewbery) +- #19857 improve nLastBlockTime and nLastTXTime documentation (jonatack) +- #19724 Cleanup connection types- followups (amitiuttarwar) +- #19670 Protect localhost and block-relay-only peers from eviction (sdaftuar) +- #19728 Increase the ip address relay branching factor for unreachable networks (sipa) +- #19879 Miscellaneous wtxid followups (amitiuttarwar) +- #19697 Improvements on ADDR caching (naumenkogs) +- #17785 Unify Send and Receive protocol versions (hebasto) +- #19845 CNetAddr: add support to (un)serialize as ADDRv2 (vasild) +- #19107 Move all header verification into the network layer, extend logging (troygiorshev) +- #20003 Exit with error message if -proxy is specified without arguments (instead of continuing without proxy server) (practicalswift) +- #19991 Use alternative port for incoming Tor connections (hebasto) +- #19723 Ignore unknown messages before VERACK (sdaftuar) +- #19954 Complete the BIP155 implementation and upgrade to TORv3 (vasild) +- #20119 BIP155 follow-ups (sipa) +- #19988 Overhaul transaction request logic (sipa) +- #17428 Try to preserve outbound block-relay-only connections during restart (hebasto) +- #19911 Guard `vRecvGetData` with `cs_vRecv` and `orphan_work_set` with `g_cs_orphans` (narula) +- #19753 Don't add AlreadyHave transactions to recentRejects (troygiorshev) +- #20187 Test-before-evict bugfix and improvements for block-relay-only peers (sdaftuar) +- #20237 Hardcoded seeds update for 0.21 (laanwj) +- #20212 Fix output of peer address in version message (vasild) +- #20284 Ensure old versions don't parse peers.dat (vasild) +- #20405 Avoid calculating onion address checksum when version is not 3 (lontivero) +- #20564 Don't send 'sendaddrv2' to pre-70016 software, and send before 'verack' (sipa) +- #20660 Move signet onion seed from v2 to v3 (Sjors) + +### Wallet +- #18262 Exit selection when `best_waste` is 0 (achow101) +- #17824 Prefer full destination groups in coin selection (fjahr) +- #17219 Allow transaction without change if keypool is empty (Sjors) +- #15761 Replace -upgradewallet startup option with upgradewallet RPC (achow101) +- #18671 Add BlockUntilSyncedToCurrentChain to dumpwallet (MarcoFalke) +- #16528 Native Descriptor Wallets using DescriptorScriptPubKeyMan (achow101) +- #18777 Recommend absolute path for dumpwallet (MarcoFalke) +- #16426 Reverse `cs_main`, `cs_wallet` lock order and reduce `cs_main` locking (ariard) +- #18699 Avoid translating RPC errors (MarcoFalke) +- #18782 Make sure no DescriptorScriptPubKeyMan or WalletDescriptor members are left uninitialized after construction (practicalswift) +- #9381 Remove CWalletTx merging logic from AddToWallet (ryanofsky) +- #16946 Include a checksum of encrypted private keys (achow101) +- #17681 Keep inactive seeds after sethdseed and derive keys from them as needed (achow101) +- #18918 Move salvagewallet into wallettool (achow101) +- #14988 Fix for confirmed column in csv export for payment to self transactions (benthecarman) +- #18275 Error if an explicit fee rate was given but the needed fee rate differed (kallewoof) +- #19054 Skip hdKeypath of 'm' when determining inactive hd seeds (achow101) +- #17938 Disallow automatic conversion between disparate hash types (Empact) +- #19237 Check size after unserializing a pubkey (elichai) +- #11413 sendtoaddress/sendmany: Add explicit feerate option (kallewoof) +- #18850 Fix ZapSelectTx to sync wallet spends (bvbfan) +- #18923 Never schedule MaybeCompactWalletDB when `-flushwallet` is off (MarcoFalke) +- #19441 walletdb: Don't reinitialize desc cache with multiple cache entries (achow101) +- #18907 walletdb: Don't remove database transaction logs and instead error (achow101) +- #19334 Introduce WalletDatabase abstract class (achow101) +- #19335 Cleanup and separate BerkeleyDatabase and BerkeleyBatch (achow101) +- #19102 Introduce and use DummyDatabase instead of dummy BerkeleyDatabase (achow101) +- #19568 Wallet should not override signing errors (fjahr) +- #17204 Do not turn `OP_1NEGATE` in scriptSig into `0x0181` in signing code (sipa) (meshcollider) +- #19457 Cleanup wallettool salvage and walletdb extraneous declarations (achow101) +- #15937 Add loadwallet and createwallet `load_on_startup` options (ryanofsky) +- #16841 Replace GetScriptForWitness with GetScriptForDestination (meshcollider) +- #14582 always do avoid partial spends if fees are within a specified range (kallewoof) +- #19743 -maxapsfee follow-up (kallewoof) +- #19289 GetWalletTx and IsMine require `cs_wallet` lock (promag) +- #19671 Remove -zapwallettxes (achow101) +- #19805 Avoid deserializing unused records when salvaging (achow101) +- #19754 wallet, gui: Reload previously loaded wallets on startup (achow101) +- #19738 Avoid multiple BerkeleyBatch in DelAddressBook (promag) +- #19919 bugfix: make LoadWallet assigns status always (AkioNak) +- #16378 The ultimate send RPC (Sjors) +- #15454 Remove the automatic creation and loading of the default wallet (achow101) +- #19501 `send*` RPCs in the wallet returns the "fee reason" (stackman27) +- #20130 Remove db mode string (S3RK) +- #19077 Add sqlite as an alternative wallet database and use it for new descriptor wallets (achow101) +- #20125 Expose database format in getwalletinfo (promag) +- #20198 Show name, format and if uses descriptors in bitcoin-wallet tool (jonasschnelli) +- #20216 Fix buffer over-read in SQLite file magic check (theStack) +- #20186 Make -wallet setting not create wallets (ryanofsky) +- #20230 Fix bug when just created encrypted wallet cannot get address (hebasto) +- #20282 Change `upgradewallet` return type to be an object (jnewbery) +- #20220 Explicit fee rate follow-ups/fixes for 0.21 (jonatack) +- #20199 Ignore (but warn) on duplicate -wallet parameters (jonasschnelli) +- #20324 Set DatabaseStatus::SUCCESS in MakeSQLiteDatabase (MarcoFalke) +- #20266 Fix change detection of imported internal descriptors (achow101) +- #20153 Do not import a descriptor with hardened derivations into a watch-only wallet (S3RK) +- #20344 Fix scanning progress calculation for single block range (theStack) +- #19502 Bugfix: Wallet: Soft-fail exceptions within ListWalletDir file checks (luke-jr) +- #20378 Fix potential division by 0 in WalletLogPrintf (jonasschnelli) +- #18836 Upgradewallet fixes and additional tests (achow101) +- #20139 Do not return warnings from UpgradeWallet() (stackman27) +- #20305 Introduce `fee_rate` sat/vB param/option (jonatack) +- #20426 Allow zero-fee fundrawtransaction/walletcreatefundedpsbt and other fixes (jonatack) +- #20573 wallet, bugfix: allow send with string `fee_rate` amounts (jonatack) + +### RPC and other APIs +- #18574 cli: Call getbalances.ismine.trusted instead of getwalletinfo.balance (jonatack) +- #17693 Add `generateblock` to mine a custom set of transactions (andrewtoth) +- #18495 Remove deprecated migration code (vasild) +- #18493 Remove deprecated "size" from mempool txs (vasild) +- #18467 Improve documentation and return value of settxfee (fjahr) +- #18607 Fix named arguments in documentation (MarcoFalke) +- #17831 doc: Fix and extend getblockstats examples (asoltys) +- #18785 Prevent valgrind false positive in `rest_blockhash_by_height` (ryanofsky) +- #18999 log: Remove "No rpcpassword set" from logs (MarcoFalke) +- #19006 Avoid crash when `g_thread_http` was never started (MarcoFalke) +- #18594 cli: Display multiwallet balances in -getinfo (jonatack) +- #19056 Make gettxoutsetinfo/GetUTXOStats interruptible (MarcoFalke) +- #19112 Remove special case for unknown service flags (MarcoFalke) +- #18826 Expose txinwitness for coinbase in JSON form from RPC (rvagg) +- #19282 Rephrase generatetoaddress help, and use `PACKAGE_NAME` (luke-jr) +- #16377 don't automatically append inputs in walletcreatefundedpsbt (Sjors) +- #19200 Remove deprecated getaddressinfo fields (jonatack) +- #19133 rpc, cli, test: add bitcoin-cli -generate command (jonatack) +- #19469 Deprecate banscore field in getpeerinfo (jonatack) +- #16525 Dump transaction version as an unsigned integer in RPC/TxToUniv (TheBlueMatt) +- #19555 Deduplicate WriteHDKeypath() used in decodepsbt (theStack) +- #19589 Avoid useless mempool query in gettxoutproof (MarcoFalke) +- #19585 RPCResult Type of MempoolEntryDescription should be OBJ (stylesuxx) +- #19634 Document getwalletinfo's `unlocked_until` field as optional (justinmoon) +- #19658 Allow RPC to fetch all addrman records and add records to addrman (jnewbery) +- #19696 Fix addnode remove command error (fjahr) +- #18654 Separate bumpfee's psbt creation function into psbtbumpfee (achow101) +- #19655 Catch listsinceblock `target_confirmations` exceeding block count (adaminsky) +- #19644 Document returned error fields as optional if applicable (theStack) +- #19455 rpc generate: print useful help and error message (jonatack) +- #19550 Add listindices RPC (fjahr) +- #19169 Validate provided keys for `query_options` parameter in listunspent (PastaPastaPasta) +- #18244 fundrawtransaction and walletcreatefundedpsbt also lock manually selected coins (Sjors) +- #14687 zmq: Enable TCP keepalive (mruddy) +- #19405 Add network in/out connections to `getnetworkinfo` and `-getinfo` (jonatack) +- #19878 rawtransaction: Fix argument in combinerawtransaction help message (pinheadmz) +- #19940 Return fee and vsize from testmempoolaccept (gzhao408) +- #13686 zmq: Small cleanups in the ZMQ code (domob1812) +- #19386, #19528, #19717, #19849, #19994 Assert that RPCArg names are equal to CRPCCommand ones (MarcoFalke) +- #19725 Add connection type to getpeerinfo, improve logs (amitiuttarwar) +- #19969 Send RPC bug fix and touch-ups (Sjors) +- #18309 zmq: Add support to listen on multiple interfaces (n-thumann) +- #20055 Set HTTP Content-Type in bitcoin-cli (laanwj) +- #19956 Improve invalid vout value rpc error message (n1rna) +- #20101 Change no wallet loaded message to be clearer (achow101) +- #19998 Add `via_tor` to `getpeerinfo` output (hebasto) +- #19770 getpeerinfo: Deprecate "whitelisted" field (replaced by "permissions") (luke-jr) +- #20120 net, rpc, test, bugfix: update GetNetworkName, GetNetworksInfo, regression tests (jonatack) +- #20595 Improve heuristic hex transaction decoding (sipa) +- #20731 Add missing description of vout in getrawtransaction help text (benthecarman) +- #19328 Add gettxoutsetinfo `hash_type` option (fjahr) +- #19731 Expose nLastBlockTime/nLastTXTime as last `block/last_transaction` in getpeerinfo (jonatack) +- #19572 zmq: Create "sequence" notifier, enabling client-side mempool tracking (instagibbs) +- #20002 Expose peer network in getpeerinfo; simplify/improve -netinfo (jonatack) + +### GUI +- #17905 Avoid redundant tx status updates (ryanofsky) +- #18646 Use `PACKAGE_NAME` in exception message (fanquake) +- #17509 Save and load PSBT (Sjors) +- #18769 Remove bug fix for Qt < 5.5 (10xcryptodev) +- #15768 Add close window shortcut (IPGlider) +- #16224 Bilingual GUI error messages (hebasto) +- #18922 Do not translate InitWarning messages in debug.log (hebasto) +- #18152 Use NotificationStatus enum for signals to GUI (hebasto) +- #18587 Avoid wallet tryGetBalances calls in WalletModel::pollBalanceChanged (ryanofsky) +- #17597 Fix height of QR-less ReceiveRequestDialog (hebasto) +- #17918 Hide non PKHash-Addresses in signing address book (emilengler) +- #17956 Disable unavailable context menu items in transactions tab (kristapsk) +- #17968 Ensure that ModalOverlay is resized properly (hebasto) +- #17993 Balance/TxStatus polling update based on last block hash (furszy) +- #18424 Use parent-child relation to manage lifetime of OptionsModel object (hebasto) +- #18452 Fix shutdown when `waitfor*` cmds are called from RPC console (hebasto) +- #15202 Add Close All Wallets action (promag) +- #19132 lock `cs_main`, `m_cached_tip_mutex` in that order (vasild) +- #18898 Display warnings as rich text (hebasto) +- #19231 add missing translation.h include to fix build (fanquake) +- #18027 "PSBT Operations" dialog (gwillen) +- #19256 Change combiner for signals to `optional_last_value` (fanquake) +- #18896 Reset toolbar after all wallets are closed (hebasto) +- #18993 increase console command max length (10xcryptodev) +- #19323 Fix regression in *txoutset* in GUI console (hebasto) +- #19210 Get rid of cursor in out-of-focus labels (hebasto) +- #19011 Reduce `cs_main` lock accumulation during GUI startup (jonasschnelli) +- #19844 Remove usage of boost::bind (fanquake) +- #20479 Fix QPainter non-determinism on macOS (0.21 backport) (laanwj) +- gui#6 Do not truncate node flag strings in debugwindow peers details tab (Saibato) +- gui#8 Fix regression in TransactionTableModel (hebasto) +- gui#17 doc: Remove outdated comment in TransactionTablePriv (MarcoFalke) +- gui#20 Wrap tooltips in the intro window (hebasto) +- gui#30 Disable the main window toolbar when the modal overlay is shown (hebasto) +- gui#34 Show permissions instead of whitelisted (laanwj) +- gui#35 Parse params directly instead of through node (ryanofsky) +- gui#39 Add visual accenting for the 'Create new receiving address' button (hebasto) +- gui#40 Clarify block height label (hebasto) +- gui#43 bugfix: Call setWalletActionsEnabled(true) only for the first wallet (hebasto) +- gui#97 Relax GUI freezes during IBD (jonasschnelli) +- gui#71 Fix visual quality of text in QR image (hebasto) +- gui#96 Slight improve create wallet dialog (Sjors) +- gui#102 Fix SplashScreen crash when run with -disablewallet (hebasto) +- gui#116 Fix unreasonable default size of the main window without loaded wallets (hebasto) +- gui#120 Fix multiwallet transaction notifications (promag) + +### Build system +- #18504 Drop bitcoin-tx and bitcoin-wallet dependencies on libevent (ryanofsky) +- #18586 Bump gitian descriptors to 0.21 (laanwj) +- #17595 guix: Enable building for `x86_64-w64-mingw32` target (dongcarl) +- #17929 add linker optimisation flags to gitian & guix (Linux) (fanquake) +- #18556 Drop make dist in gitian builds (hebasto) +- #18088 ensure we aren't using GNU extensions (fanquake) +- #18741 guix: Make source tarball using git-archive (dongcarl) +- #18843 warn on potentially uninitialized reads (vasild) +- #17874 make linker checks more robust (fanquake) +- #18535 remove -Qunused-arguments workaround for clang + ccache (fanquake) +- #18743 Add --sysroot option to mac os native compile flags (ryanofsky) +- #18216 test, build: Enable -Werror=sign-compare (Empact) +- #18928 don't pass -w when building for Windows (fanquake) +- #16710 Enable -Wsuggest-override if available (hebasto) +- #18738 Suppress -Wdeprecated-copy warnings (hebasto) +- #18862 Remove fdelt_chk back-compat code and sanity check (fanquake) +- #18887 enable -Werror=gnu (vasild) +- #18956 enforce minimum required Windows version (7) (fanquake) +- #18958 guix: Make V=1 more powerful for debugging (dongcarl) +- #18677 Multiprocess build support (ryanofsky) +- #19094 Only allow ASCII identifiers (laanwj) +- #18820 Propagate well-known vars into depends (dongcarl) +- #19173 turn on --enable-c++17 by --enable-fuzz (vasild) +- #18297 Use pkg-config in BITCOIN_QT_CONFIGURE for all hosts including Windows (hebasto) +- #19301 don't warn when doxygen isn't found (fanquake) +- #19240 macOS toolchain simplification and bump (dongcarl) +- #19356 Fix search for brew-installed BDB 4 on OS X (gwillen) +- #19394 Remove unused `RES_IMAGES` (Bushstar) +- #19403 improve `__builtin_clz*` detection (fanquake) +- #19375 target Windows 7 when building libevent and fix ipv6 usage (fanquake) +- #19331 Do not include server symbols in wallet (MarcoFalke) +- #19257 remove BIP70 configure option (fanquake) +- #18288 Add MemorySanitizer (MSan) in Travis to detect use of uninitialized memory (practicalswift) +- #18307 Require pkg-config for all of the hosts (hebasto) +- #19445 Update msvc build to use ISO standard C++17 (sipsorcery) +- #18882 fix -Wformat-security check when compiling with GCC (fanquake) +- #17919 Allow building with system clang (dongcarl) +- #19553 pass -fcommon when building genisoimage (fanquake) +- #19565 call `AC_PATH_TOOL` for dsymutil in macOS cross-compile (fanquake) +- #19530 build LTO support into Apple's ld64 (theuni) +- #19525 add -Wl,-z,separate-code to hardening flags (fanquake) +- #19667 set minimum required Boost to 1.58.0 (fanquake) +- #19672 make clean removes .gcda and .gcno files from fuzz directory (Crypt-iQ) +- #19622 Drop ancient hack in gitian-linux descriptor (hebasto) +- #19688 Add support for llvm-cov (hebasto) +- #19718 Add missed gcov files to 'make clean' (hebasto) +- #19719 Add Werror=range-loop-analysis (MarcoFalke) +- #19015 Enable some commonly enabled compiler diagnostics (practicalswift) +- #19689 build, qt: Add Qt version checking (hebasto) +- #17396 modest Android improvements (icota) +- #18405 Drop all of the ZeroMQ patches (hebasto) +- #15704 Move Win32 defines to configure.ac to ensure they are globally defined (luke-jr) +- #19761 improve sed robustness by not using sed (fanquake) +- #19758 Drop deprecated and unused `GUARDED_VAR` and `PT_GUARDED_VAR` annotations (hebasto) +- #18921 add stack-clash and control-flow protection options to hardening flags (fanquake) +- #19803 Bugfix: Define and use `HAVE_FDATASYNC` correctly outside LevelDB (luke-jr) +- #19685 CMake invocation cleanup (dongcarl) +- #19861 add /usr/local/ to `LCOV_FILTER_PATTERN` for macOS builds (Crypt-iQ) +- #19916 allow user to specify `DIR_FUZZ_SEED_CORPUS` for `cov_fuzz` (Crypt-iQ) +- #19944 Update secp256k1 subtree (including BIP340 support) (sipa) +- #19558 Split pthread flags out of ldflags and dont use when building libconsensus (fanquake) +- #19959 patch qt libpng to fix powerpc build (fanquake) +- #19868 Fix target name (hebasto) +- #19960 The vcpkg tool has introduced a proper way to use manifests (sipsorcery) +- #20065 fuzz: Configure check for main function (MarcoFalke) +- #18750 Optionally skip external warnings (vasild) +- #20147 Update libsecp256k1 (endomorphism, test improvements) (sipa) +- #20156 Make sqlite support optional (compile-time) (luke-jr) +- #20318 Ensure source tarball has leading directory name (MarcoFalke) +- #20447 Patch `qt_intersect_spans` to avoid non-deterministic behavior in LLVM 8 (achow101) +- #20505 Avoid secp256k1.h include from system (dergoegge) +- #20527 Do not ignore Homebrew's SQLite on macOS (hebasto) +- #20478 Don't set BDB flags when configuring without (jonasschnelli) +- #20563 Check that Homebrew's berkeley-db4 package is actually installed (hebasto) +- #19493 Fix clang build on Mac (bvbfan) + +### Tests and QA +- #18593 Complete impl. of `msg_merkleblock` and `wait_for_merkleblock` (theStack) +- #18609 Remove REJECT message code (hebasto) +- #18584 Check that the version message does not leak the local address (MarcoFalke) +- #18597 Extend `wallet_dump` test to cover comments (MarcoFalke) +- #18596 Try once more when RPC connection fails on Windows (MarcoFalke) +- #18451 shift coverage from getunconfirmedbalance to getbalances (jonatack) +- #18631 appveyor: Disable functional tests for now (MarcoFalke) +- #18628 Add various low-level p2p tests (MarcoFalke) +- #18615 Avoid accessing free'd memory in `validation_chainstatemanager_tests` (MarcoFalke) +- #18571 fuzz: Disable debug log file (MarcoFalke) +- #18653 add coverage for bitcoin-cli -rpcwait (jonatack) +- #18660 Verify findCommonAncestor always initializes outputs (ryanofsky) +- #17669 Have coins simulation test also use CCoinsViewDB (jamesob) +- #18662 Replace gArgs with local argsman in bench (MarcoFalke) +- #18641 Create cached blocks not in the future (MarcoFalke) +- #18682 fuzz: `http_request` workaround for libevent < 2.1.1 (theStack) +- #18692 Bump timeout in `wallet_import_rescan` (MarcoFalke) +- #18695 Replace boost::mutex with std::mutex (hebasto) +- #18633 Properly raise FailedToStartError when rpc shutdown before warmup finished (MarcoFalke) +- #18675 Don't initialize PrecomputedTransactionData in txvalidationcache tests (jnewbery) +- #18691 Add `wait_for_cookie_credentials()` to framework for rpcwait tests (jonatack) +- #18672 Add further BIP37 size limit checks to `p2p_filter.py` (theStack) +- #18721 Fix linter issue (hebasto) +- #18384 More specific `feature_segwit` test error messages and fixing incorrect comments (gzhao408) +- #18575 bench: Remove requirement that all benches use same testing setup (MarcoFalke) +- #18690 Check object hashes in `wait_for_getdata` (robot-visions) +- #18712 display command line options passed to `send_cli()` in debug log (jonatack) +- #18745 Check submitblock return values (MarcoFalke) +- #18756 Use `wait_for_getdata()` in `p2p_compactblocks.py` (theStack) +- #18724 Add coverage for -rpcwallet cli option (jonatack) +- #18754 bench: Add caddrman benchmarks (vasild) +- #18585 Use zero-argument super() shortcut (Python 3.0+) (theStack) +- #18688 fuzz: Run in parallel (MarcoFalke) +- #18770 Remove raw-tx byte juggling in `mempool_reorg` (MarcoFalke) +- #18805 Add missing `sync_all` to `wallet_importdescriptors.py` (achow101) +- #18759 bench: Start nodes with -nodebuglogfile (MarcoFalke) +- #18774 Added test for upgradewallet RPC (brakmic) +- #18485 Add `mempool_updatefromblock.py` (hebasto) +- #18727 Add CreateWalletFromFile test (ryanofsky) +- #18726 Check misbehavior more independently in `p2p_filter.py` (robot-visions) +- #18825 Fix message for `ECC_InitSanityCheck` test (fanquake) +- #18576 Use unittest for `test_framework` unit testing (gzhao408) +- #18828 Strip down previous releases boilerplate (MarcoFalke) +- #18617 Add factor option to adjust test timeouts (brakmic) +- #18855 `feature_backwards_compatibility.py` test downgrade after upgrade (achow101) +- #18864 Add v0.16.3 backwards compatibility test, bump v0.19.0.1 to v0.19.1 (Sjors) +- #18917 fuzz: Fix vector size problem in system fuzzer (brakmic) +- #18901 fuzz: use std::optional for `sep_pos_opt` variable (brakmic) +- #18888 Remove RPCOverloadWrapper boilerplate (MarcoFalke) +- #18952 Avoid os-dependent path (fametrano) +- #18938 Fill fuzzing coverage gaps for functions in consensus/validation.h, primitives/block.h and util/translation.h (practicalswift) +- #18986 Add capability to disable RPC timeout in functional tests (rajarshimaitra) +- #18530 Add test for -blocksonly and -whitelistforcerelay param interaction (glowang) +- #19014 Replace `TEST_PREVIOUS_RELEASES` env var with `test_framework` option (MarcoFalke) +- #19052 Don't limit fuzzing inputs to 1 MB for afl-fuzz (now: ∞ ∀ fuzzers) (practicalswift) +- #19060 Remove global `wait_until` from `p2p_getdata` (MarcoFalke) +- #18926 Pass ArgsManager into `getarg_tests` (glowang) +- #19110 Explain that a bug should be filed when the tests fail (MarcoFalke) +- #18965 Implement `base58_decode` (10xcryptodev) +- #16564 Always define the `raii_event_tests` test suite (candrews) +- #19122 Add missing `sync_blocks` to `wallet_hd` (MarcoFalke) +- #18875 fuzz: Stop nodes in `process_message*` fuzzers (MarcoFalke) +- #18974 Check that invalid witness destinations can not be imported (MarcoFalke) +- #18210 Type hints in Python tests (kiminuo) +- #19159 Make valgrind.supp work on aarch64 (MarcoFalke) +- #19082 Moved the CScriptNum asserts into the unit test in script.py (gillichu) +- #19172 Do not swallow flake8 exit code (hebasto) +- #19188 Avoid overwriting the NodeContext member of the testing setup [-Wshadow-field] (MarcoFalke) +- #18890 `disconnect_nodes` should warn if nodes were already disconnected (robot-visions) +- #19227 change blacklist to blocklist (TrentZ) +- #19230 Move base58 to own module to break circular dependency (sipa) +- #19083 `msg_mempool`, `fRelay`, and other bloomfilter tests (gzhao408) +- #16756 Connection eviction logic tests (mzumsande) +- #19177 Fix and clean `p2p_invalid_messages` functional tests (troygiorshev) +- #19264 Don't import asyncio to test magic bytes (jnewbery) +- #19178 Make `mininode_lock` non-reentrant (jnewbery) +- #19153 Mempool compatibility test (S3RK) +- #18434 Add a test-security target and run it in CI (fanquake) +- #19252 Wait for disconnect in `disconnect_p2ps` + bloomfilter test followups (gzhao408) +- #19298 Add missing `sync_blocks` (MarcoFalke) +- #19304 Check that message sends successfully when header is split across two buffers (troygiorshev) +- #19208 move `sync_blocks` and `sync_mempool` functions to `test_framework.py` (ycshao) +- #19198 Check that peers with forcerelay permission are not asked to feefilter (MarcoFalke) +- #19351 add two edge case tests for CSubNet (vasild) +- #19272 net, test: invalid p2p messages and test framework improvements (jonatack) +- #19348 Bump linter versions (duncandean) +- #19366 Provide main(…) function in fuzzer. Allow building uninstrumented harnesses with --enable-fuzz (practicalswift) +- #19412 move `TEST_RUNNER_EXTRA` into native tsan setup (fanquake) +- #19368 Improve functional tests compatibility with BSD/macOS (S3RK) +- #19028 Set -logthreadnames in unit tests (MarcoFalke) +- #18649 Add std::locale::global to list of locale dependent functions (practicalswift) +- #19140 Avoid fuzzer-specific nullptr dereference in libevent when handling PROXY requests (practicalswift) +- #19214 Auto-detect SHA256 implementation in benchmarks (sipa) +- #19353 Fix mistakenly swapped "previous" and "current" lock orders (hebasto) +- #19533 Remove unnecessary `cs_mains` in `denialofservice_tests` (jnewbery) +- #19423 add functional test for txrelay during and after IBD (gzhao408) +- #16878 Fix non-deterministic coverage of test `DoS_mapOrphans` (davereikher) +- #19548 fuzz: add missing overrides to `signature_checker` (jonatack) +- #19562 Fix fuzzer compilation on macOS (freenancial) +- #19370 Static asserts for consistency of fee defaults (domob1812) +- #19599 clean `message_count` and `last_message` (troygiorshev) +- #19597 test decodepsbt fee calculation (count input value only once per UTXO) (theStack) +- #18011 Replace current benchmarking framework with nanobench (martinus) +- #19489 Fail `wait_until` early if connection is lost (MarcoFalke) +- #19340 Preserve the `LockData` initial state if "potential deadlock detected" exception thrown (hebasto) +- #19632 Catch decimal.InvalidOperation from `TestNodeCLI#send_cli` (Empact) +- #19098 Remove duplicate NodeContext hacks (ryanofsky) +- #19649 Restore test case for p2p transaction blinding (instagibbs) +- #19657 Wait until `is_connected` in `add_p2p_connection` (MarcoFalke) +- #19631 Wait for 'cmpctblock' in `p2p_compactblocks` when it is expected (Empact) +- #19674 use throwaway _ variable for unused loop counters (theStack) +- #19709 Fix 'make cov' with clang (hebasto) +- #19564 `p2p_feefilter` improvements (logging, refactoring, speedup) (theStack) +- #19756 add `sync_all` to fix race condition in wallet groups test (kallewoof) +- #19727 Removing unused classes from `p2p_leak.py` (dhruv) +- #19722 Add test for getblockheader verboseness (torhte) +- #19659 Add a seed corpus generation option to the fuzzing `test_runner` (darosior) +- #19775 Activate segwit in TestChain100Setup (MarcoFalke) +- #19760 Remove confusing mininode terminology (jnewbery) +- #19752 Update `wait_until` usage in tests not to use the one from utils (slmtpz) +- #19839 Set appveyor VM version to previous Visual Studio 2019 release (sipsorcery) +- #19830 Add tsan supp for leveldb::DBImpl::DeleteObsoleteFiles (MarcoFalke) +- #19710 bench: Prevent thread oversubscription and decreases the variance of result values (hebasto) +- #19842 Update the vcpkg checkout commit ID in appveyor config (sipsorcery) +- #19507 Expand functional zmq transaction tests (instagibbs) +- #19816 Rename wait until helper to `wait_until_helper` (MarcoFalke) +- #19859 Fixes failing functional test by changing version (n-thumann) +- #19887 Fix flaky `wallet_basic` test (fjahr) +- #19897 Change `FILE_CHAR_BLOCKLIST` to `FILE_CHARS_DISALLOWED` (verretor) +- #19800 Mockwallet (MarcoFalke) +- #19922 Run `rpc_txoutproof.py` even with wallet disabled (MarcoFalke) +- #19936 batch rpc with params (instagibbs) +- #19971 create default wallet in extended tests (Sjors) +- #19781 add parameterized constructor for `msg_sendcmpct()` (theStack) +- #19963 Clarify blocksonly whitelistforcerelay test (t-bast) +- #20022 Use explicit p2p objects where available (guggero) +- #20028 Check that invalid peer traffic is accounted for (MarcoFalke) +- #20004 Add signet witness commitment section parse tests (MarcoFalke) +- #20034 Get rid of default wallet hacks (ryanofsky) +- #20069 Mention commit id in scripted diff error (laanwj) +- #19947 Cover `change_type` option of "walletcreatefundedpsbt" RPC (guggero) +- #20126 `p2p_leak_tx.py` improvements (use MiniWallet, add `p2p_lock` acquires) (theStack) +- #20129 Don't export `in6addr_loopback` (vasild) +- #20131 Remove unused nVersion=1 in p2p tests (MarcoFalke) +- #20161 Minor Taproot follow-ups (sipa) +- #19401 Use GBT to get block versions correct (luke-jr) +- #20159 `mining_getblocktemplate_longpoll.py` improvements (use MiniWallet, add logging) (theStack) +- #20039 Convert amounts from float to decimal (prayank23) +- #20112 Speed up `wallet_resendwallettransactions` with mockscheduler RPC (MarcoFalke) +- #20247 fuzz: Check for addrv1 compatibility before using addrv1 serializer. Fuzz addrv2 serialization (practicalswift) +- #20167 Add test for -blockversion (MarcoFalke) +- #19877 Clarify `rpc_net` & `p2p_disconnect_ban functional` tests (amitiuttarwar) +- #20258 Remove getnettotals/getpeerinfo consistency test (jnewbery) +- #20242 fuzz: Properly initialize PrecomputedTransactionData (MarcoFalke) +- #20262 Skip --descriptor tests if sqlite is not compiled (achow101) +- #18788 Update more tests to work with descriptor wallets (achow101) +- #20289 fuzz: Check for addrv1 compatibility before using addrv1 serializer/deserializer on CService (practicalswift) +- #20290 fuzz: Fix DecodeHexTx fuzzing harness issue (practicalswift) +- #20245 Run `script_assets_test` even if built --with-libs=no (MarcoFalke) +- #20300 fuzz: Add missing `ECC_Start` to `descriptor_parse` test (S3RK) +- #20283 Only try witness deser when checking for witness deser failure (MarcoFalke) +- #20303 fuzz: Assert expected DecodeHexTx behaviour when using legacy decoding (practicalswift) +- #20316 Fix `wallet_multiwallet` test issue on Windows (MarcoFalke) +- #20326 Fix `ecdsa_verify` in test framework (stepansnigirev) +- #20328 cirrus: Skip tasks on the gui repo main branch (MarcoFalke) +- #20355 fuzz: Check for addrv1 compatibility before using addrv1 serializer/deserializer on CSubNet (practicalswift) +- #20332 Mock IBD in `net_processing` fuzzers (MarcoFalke) +- #20218 Suppress `epoll_ctl` data race (MarcoFalke) +- #20375 fuzz: Improve coverage for CPartialMerkleTree fuzzing harness (practicalswift) +- #19669 contrib: Fixup valgrind suppressions file (MarcoFalke) +- #18879 valgrind: remove outdated suppressions (fanquake) +- #19226 Add BerkeleyDatabase tsan suppression (MarcoFalke) +- #20379 Remove no longer needed UBSan suppression (float divide-by-zero in validation.cpp) (practicalswift) +- #18190, #18736, #18744, #18775, #18783, #18867, #18994, #19065, + #19067, #19143, #19222, #19247, #19286, #19296, #19379, #19934, + #20188, #20395 Add fuzzing harnessses (practicalswift) +- #18638 Use mockable time for ping/pong, add tests (MarcoFalke) +- #19951 CNetAddr scoped ipv6 test coverage, rename scopeId to `m_scope_id` (jonatack) +- #20027 Use mockable time everywhere in `net_processing` (sipa) +- #19105 Add Muhash3072 implementation in Python (fjahr) +- #18704, #18752, #18753, #18765, #18839, #18866, #18873, #19022, + #19023, #19429, #19552, #19778, #20176, #20179, #20214, #20292, + #20299, #20322 Fix intermittent test issues (MarcoFalke) +- #20390 CI/Cirrus: Skip `merge_base` step for non-PRs (luke-jr) +- #18634 ci: Add fuzzbuzz integration configuration file (practicalswift) +- #18591 Add C++17 build to Travis (sipa) +- #18581, #18667, #18798, #19495, #19519, #19538 CI improvements (hebasto) +- #18683, #18705, #18735, #18778, #18799, #18829, #18912, #18929, + #19008, #19041, #19164, #19201, #19267, #19276, #19321, #19371, + #19427, #19730, #19746, #19881, #20294, #20339, #20368 CI improvements (MarcoFalke) +- #20489, #20506 MSVC CI improvements (sipsorcery) + +### Miscellaneous +- #18713 scripts: Add macho stack canary check to security-check.py (fanquake) +- #18629 scripts: Add pe .reloc section check to security-check.py (fanquake) +- #18437 util: `Detect posix_fallocate()` instead of assuming (vasild) +- #18413 script: Prevent ub when computing abs value for num opcode serialize (pierreN) +- #18443 lockedpool: avoid sensitive data in core files (FreeBSD) (vasild) +- #18885 contrib: Move optimize-pngs.py script to the maintainer repo (MarcoFalke) +- #18317 Serialization improvements step 6 (all except wallet/gui) (sipa) +- #16127 More thread safety annotation coverage (ajtowns) +- #19228 Update libsecp256k1 subtree (sipa) +- #19277 util: Add assert identity function (MarcoFalke) +- #19491 util: Make assert work with any value (MarcoFalke) +- #19205 script: `previous_release.sh` rewritten in python (bliotti) +- #15935 Add <datadir>/settings.json persistent settings storage (ryanofsky) +- #19439 script: Linter to check commit message formatting (Ghorbanian) +- #19654 lint: Improve commit message linter in travis (fjahr) +- #15382 util: Add runcommandparsejson (Sjors) +- #19614 util: Use `have_fdatasync` to determine fdatasync() use (fanquake) +- #19813 util, ci: Hard code previous release tarball checksums (hebasto) +- #19841 Implement Keccak and `SHA3_256` (sipa) +- #19643 Add -netinfo peer connections dashboard (jonatack) +- #15367 feature: Added ability for users to add a startup command (benthecarman) +- #19984 log: Remove static log message "Initializing chainstate Chainstate [ibd] @ height -1 (null)" (practicalswift) +- #20092 util: Do not use gargs global in argsmanager member functions (hebasto) +- #20168 contrib: Fix `gen_key_io_test_vectors.py` imports (MarcoFalke) +- #19624 Warn on unknown `rw_settings` (MarcoFalke) +- #20257 Update secp256k1 subtree to latest master (sipa) +- #20346 script: Modify security-check.py to use "==" instead of "is" for literal comparison (tylerchambers) +- #18881 Prevent UB in DeleteLock() function (hebasto) +- #19180, #19189, #19190, #19220, #19399 Replace RecursiveMutex with Mutex (hebasto) +- #19347 Make `cs_inventory` nonrecursive (jnewbery) +- #19773 Avoid recursive lock in IsTrusted (promag) +- #18790 Improve thread naming (hebasto) +- #20140 Restore compatibility with old CSubNet serialization (sipa) +- #17775 DecodeHexTx: Try case where txn has inputs first (instagibbs) + +### Documentation +- #18502 Update docs for getbalance (default minconf should be 0) (uzyn) +- #18632 Fix macos comments in release-notes (MarcoFalke) +- #18645 Update thread information in developer docs (jnewbery) +- #18709 Note why we can't use `thread_local` with glibc back compat (fanquake) +- #18410 Improve commenting for coins.cpp|h (jnewbery) +- #18157 fixing init.md documentation to not require rpcpassword (jkcd) +- #18739 Document how to fuzz Bitcoin Core using Honggfuzz (practicalswift) +- #18779 Better explain GNU ld's dislike of ld64's options (fanquake) +- #18663 Mention build docs in README.md (saahilshangle) +- #18810 Update rest info on block size and json (chrisabrams) +- #18939 Add c++17-enable flag to fuzzing instructions (mzumsande) +- #18957 Add a link from ZMQ doc to ZMQ example in contrib/ (meeDamian) +- #19058 Drop protobuf stuff (hebasto) +- #19061 Add link to Visual Studio build readme (maitrebitcoin) +- #19072 Expand section on Getting Started (MarcoFalke) +- #18968 noban precludes maxuploadtarget disconnects (MarcoFalke) +- #19005 Add documentation for 'checklevel' argument in 'verifychain' RPC… (kcalvinalvin) +- #19192 Extract net permissions doc (MarcoFalke) +- #19071 Separate repository for the gui (MarcoFalke) +- #19018 fixing description of the field sequence in walletcreatefundedpsbt RPC method (limpbrains) +- #19367 Span pitfalls (sipa) +- #19408 Windows WSL build recommendation to temporarily disable Win32 PE support (sipsorcery) +- #19407 explain why passing -mlinker-version is required when cross-compiling (fanquake) +- #19452 afl fuzzing comment about afl-gcc and afl-g++ (Crypt-iQ) +- #19258 improve subtree check instructions (Sjors) +- #19474 Use precise permission flags where possible (MarcoFalke) +- #19494 CONTRIBUTING.md improvements (jonatack) +- #19268 Add non-thread-safe note to FeeFilterRounder::round() (hebasto) +- #19547 Update macOS cross compilation dependencies for Focal (hebasto) +- #19617 Clang 8 or later is required with `FORCE_USE_SYSTEM_CLANG` (fanquake) +- #19639 Remove Reference Links #19582 (RobertHosking) +- #19605 Set `CC_FOR_BUILD` when building on OpenBSD (fanquake) +- #19765 Fix getmempoolancestors RPC result doc (MarcoFalke) +- #19786 Remove label from good first issue template (MarcoFalke) +- #19646 Updated outdated help command for getblocktemplate (jakeleventhal) +- #18817 Document differences in bitcoind and bitcoin-qt locale handling (practicalswift) +- #19870 update PyZMQ install instructions, fix `zmq_sub.py` file permissions (jonatack) +- #19903 Update build-openbsd.md with GUI support (grubles) +- #19241 help: Generate checkpoint height from chainparams (luke-jr) +- #18949 Add CODEOWNERS file to automatically nominate PR reviewers (adamjonas) +- #20014 Mention signet in -help output (hebasto) +- #20015 Added default signet config for linearize script (gr0kchain) +- #19958 Better document features of feelers (naumenkogs) +- #19871 Clarify scope of eviction protection of outbound block-relay peers (ariard) +- #20076 Update and improve files.md (hebasto) +- #20107 Collect release-notes snippets (MarcoFalke) +- #20109 Release notes and followups from 19339 (glozow) +- #20090 Tiny followups to new getpeerinfo connection type field (amitiuttarwar) +- #20152 Update wallet files in files.md (hebasto) +- #19124 Document `ALLOW_HOST_PACKAGES` dependency option (skmcontrib) +- #20271 Document that wallet salvage is experimental (MarcoFalke) +- #20281 Correct getblockstats documentation for `(sw)total_weight` (shesek) +- #20279 release process updates/fixups (jonatack) +- #20238 Missing comments for signet parameters (decryp2kanon) +- #20756 Add missing field (permissions) to the getpeerinfo help (amitiuttarwar) +- #20668 warn that incoming conns are unlikely when not using default ports (adamjonas) +- #19961 tor.md updates (jonatack) +- #19050 Add warning for rest interface limitation (fjahr) +- #19390 doc/REST-interface: Remove stale info (luke-jr) +- #19344 docs: update testgen usage example (Bushstar) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- 10xcryptodev +- Aaron Clauson +- Aaron Hook +- Adam Jonas +- Adam Soltys +- Adam Stein +- Akio Nakamura +- Alex Willmer +- Amir Ghorbanian +- Amiti Uttarwar +- Andrew Chow +- Andrew Toth +- Anthony Fieroni +- Anthony Towns +- Antoine Poinsot +- Antoine Riard +- Ben Carman +- Ben Woosley +- Benoit Verret +- Brian Liotti +- Bushstar +- Calvin Kim +- Carl Dong +- Chris Abrams +- Chris L +- Christopher Coverdale +- codeShark149 +- Cory Fields +- Craig Andrews +- Damian Mee +- Daniel Kraft +- Danny Lee +- David Reikher +- DesWurstes +- Dhruv Mehta +- Duncan Dean +- Elichai Turkel +- Elliott Jin +- Emil Engler +- Ethan Heilman +- eugene +- Fabian Jahr +- fanquake +- Ferdinando M. Ametrano +- freenancial +- furszy +- Gillian Chu +- Gleb Naumenko +- Glenn Willen +- Gloria Zhao +- glowang +- gr0kchain +- Gregory Sanders +- grubles +- gzhao408 +- Harris +- Hennadii Stepanov +- Hugo Nguyen +- Igor Cota +- Ivan Metlushko +- Ivan Vershigora +- Jake Leventhal +- James O'Beirne +- Jeremy Rubin +- jgmorgan +- Jim Posen +- “jkcd” +- jmorgan +- John Newbery +- Johnson Lau +- Jon Atack +- Jonas Schnelli +- Jonathan Schoeller +- João Barbosa +- Justin Moon +- kanon +- Karl-Johan Alm +- Kiminuo +- Kristaps Kaupe +- lontivero +- Luke Dashjr +- Marcin Jachymiak +- MarcoFalke +- Martin Ankerl +- Martin Zumsande +- maskoficarus +- Matt Corallo +- Matthew Zipkin +- MeshCollider +- Miguel Herranz +- MIZUTA Takeshi +- mruddy +- Nadav Ivgi +- Neha Narula +- Nicolas Thumann +- Niklas Gögge +- Nima Yazdanmehr +- nsa +- nthumann +- Oliver Gugger +- pad +- pasta +- Peter Bushnell +- pierrenn +- Pieter Wuille +- practicalswift +- Prayank +- Raúl Martínez (RME) +- RandyMcMillan +- Rene Pickhardt +- Riccardo Masutti +- Robert +- Rod Vagg +- Roy Shao +- Russell Yanofsky +- Saahil Shangle +- sachinkm77 +- saibato +- Samuel Dobson +- sanket1729 +- Sebastian Falbesoner +- Seleme Topuz +- Sishir Giri +- Sjors Provoost +- skmcontrib +- Stepan Snigirev +- Stephan Oeste +- Suhas Daftuar +- t-bast +- Tom Harding +- Torhte Butler +- TrentZ +- Troy Giorshev +- tryphe +- Tyler Chambers +- U-Zyn Chua +- Vasil Dimov +- wiz +- Wladimir J. van der Laan + +As well as to everyone that helped with translations on +[Transifex](https://www.transifex.com/bitcoin/bitcoin/). diff --git a/share/genbuild.sh b/share/genbuild.sh index d8f3429f7a..fe89ae1fde 100755 --- a/share/genbuild.sh +++ b/share/genbuild.sh @@ -31,7 +31,7 @@ if [ "${BITCOIN_GENBUILD_NO_GIT}" != "1" ] && [ -e "$(command -v git)" ] && [ "$ fi # otherwise generate suffix from git, i.e. string like "59887e8-dirty" - GIT_COMMIT=$(git rev-parse --short HEAD) + GIT_COMMIT=$(git rev-parse --short=12 HEAD) git diff-index --quiet HEAD -- || GIT_COMMIT="$GIT_COMMIT-dirty" fi diff --git a/src/Makefile.am b/src/Makefile.am index 1a0791dccd..2871df124c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -92,15 +92,21 @@ endif if BUILD_BITCOIN_CLI bin_PROGRAMS += bitcoin-cli endif + if BUILD_BITCOIN_TX bin_PROGRAMS += bitcoin-tx endif + if ENABLE_WALLET if BUILD_BITCOIN_WALLET bin_PROGRAMS += bitcoin-wallet endif endif +if BUILD_BITCOIN_UTIL + bin_PROGRAMS += bitcoin-util +endif + .PHONY: FORCE check-symbols check-security # bitcoin core # BITCOIN_CORE_H = \ @@ -224,6 +230,7 @@ BITCOIN_CORE_H = \ util/error.h \ util/fees.h \ util/golombrice.h \ + util/hasher.h \ util/macros.h \ util/memory.h \ util/message.h \ @@ -544,6 +551,7 @@ libbitcoin_util_a_SOURCES = \ util/bytevectorhash.cpp \ util/error.cpp \ util/fees.cpp \ + util/hasher.cpp \ util/system.cpp \ util/message.cpp \ util/moneystr.cpp \ @@ -666,6 +674,27 @@ bitcoin_wallet_SOURCES += bitcoin-wallet-res.rc endif # +# bitcoin-util binary # +bitcoin_util_SOURCES = bitcoin-util.cpp +bitcoin_util_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +bitcoin_util_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +bitcoin_util_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) + +if TARGET_WINDOWS +bitcoin_util_SOURCES += bitcoin-util-res.rc +endif + +bitcoin_util_LDADD = \ + $(LIBBITCOIN_COMMON) \ + $(LIBBITCOIN_UTIL) \ + $(LIBUNIVALUE) \ + $(LIBBITCOIN_CONSENSUS) \ + $(LIBBITCOIN_CRYPTO) \ + $(LIBSECP256K1) + +bitcoin_util_LDADD += $(BOOST_LIBS) +# + # bitcoinconsensus library # if BUILD_BITCOIN_LIBS include_HEADERS = script/bitcoinconsensus.h diff --git a/src/bitcoin-util-res.rc b/src/bitcoin-util-res.rc new file mode 100644 index 0000000000..3f0fa8ab6d --- /dev/null +++ b/src/bitcoin-util-res.rc @@ -0,0 +1,35 @@ +#include <windows.h> // needed for VERSIONINFO +#include "clientversion.h" // holds the needed client version information + +#define VER_PRODUCTVERSION CLIENT_VERSION_MAJOR,CLIENT_VERSION_MINOR,CLIENT_VERSION_BUILD +#define VER_PRODUCTVERSION_STR STRINGIZE(CLIENT_VERSION_MAJOR) "." STRINGIZE(CLIENT_VERSION_MINOR) "." STRINGIZE(CLIENT_VERSION_BUILD) +#define VER_FILEVERSION VER_PRODUCTVERSION +#define VER_FILEVERSION_STR VER_PRODUCTVERSION_STR + +VS_VERSION_INFO VERSIONINFO +FILEVERSION VER_FILEVERSION +PRODUCTVERSION VER_PRODUCTVERSION +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" // U.S. English - multilingual (hex) + BEGIN + VALUE "CompanyName", "Bitcoin" + VALUE "FileDescription", "bitcoin-util (CLI Bitcoin utility)" + VALUE "FileVersion", VER_FILEVERSION_STR + VALUE "InternalName", "bitcoin-util" + VALUE "LegalCopyright", COPYRIGHT_STR + VALUE "LegalTrademarks1", "Distributed under the MIT software license, see the accompanying file COPYING or http://www.opensource.org/licenses/mit-license.php." + VALUE "OriginalFilename", "bitcoin-util.exe" + VALUE "ProductName", "bitcoin-util" + VALUE "ProductVersion", VER_PRODUCTVERSION_STR + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1252 // language neutral - multilingual (decimal) + END +END diff --git a/src/bitcoin-util.cpp b/src/bitcoin-util.cpp new file mode 100644 index 0000000000..b702a68bdf --- /dev/null +++ b/src/bitcoin-util.cpp @@ -0,0 +1,211 @@ +// Copyright (c) 2009-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. + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <arith_uint256.h> +#include <clientversion.h> +#include <coins.h> +#include <consensus/consensus.h> +#include <core_io.h> +#include <key_io.h> +#include <policy/rbf.h> +#include <primitives/transaction.h> +#include <script/script.h> +#include <script/sign.h> +#include <script/signingprovider.h> +#include <univalue.h> +#include <util/moneystr.h> +#include <util/rbf.h> +#include <util/strencodings.h> +#include <util/string.h> +#include <util/system.h> +#include <util/translation.h> + +#include <functional> +#include <memory> +#include <stdio.h> +#include <thread> + +#include <boost/algorithm/string.hpp> + +static const int CONTINUE_EXECUTION=-1; + +const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; + +static void SetupBitcoinUtilArgs(ArgsManager &argsman) +{ + SetupHelpOptions(argsman); + + argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + + SetupChainParamsBaseOptions(argsman); +} + +// This function returns either one of EXIT_ codes when it's expected to stop the process or +// CONTINUE_EXECUTION when it's expected to continue further. +static int AppInitUtil(int argc, char* argv[]) +{ + SetupBitcoinUtilArgs(gArgs); + std::string error; + if (!gArgs.ParseParameters(argc, argv, error)) { + tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error); + return EXIT_FAILURE; + } + + // Check for chain settings (Params() calls are only valid after this clause) + try { + SelectParams(gArgs.GetChainName()); + } catch (const std::exception& e) { + tfm::format(std::cerr, "Error: %s\n", e.what()); + return EXIT_FAILURE; + } + + if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { + // First part of help message is specific to this utility + std::string strUsage = PACKAGE_NAME " bitcoin-util utility version " + FormatFullVersion() + "\n"; + if (!gArgs.IsArgSet("-version")) { + strUsage += "\n" + "Usage: bitcoin-util [options] [commands] Do stuff\n"; + strUsage += "\n" + gArgs.GetHelpMessage(); + } + + tfm::format(std::cout, "%s", strUsage); + + if (argc < 2) { + tfm::format(std::cerr, "Error: too few parameters\n"); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } + return CONTINUE_EXECUTION; +} + +static void grind_task(uint32_t nBits, CBlockHeader& header_orig, uint32_t offset, uint32_t step, std::atomic<bool>& found) +{ + arith_uint256 target; + bool neg, over; + target.SetCompact(nBits, &neg, &over); + if (target == 0 || neg || over) return; + CBlockHeader header = header_orig; // working copy + header.nNonce = offset; + + uint32_t finish = std::numeric_limits<uint32_t>::max() - step; + finish = finish - (finish % step) + offset; + + while (!found && header.nNonce < finish) { + const uint32_t next = (finish - header.nNonce < 5000*step) ? finish : header.nNonce + 5000*step; + do { + if (UintToArith256(header.GetHash()) <= target) { + if (!found.exchange(true)) { + header_orig.nNonce = header.nNonce; + } + return; + } + header.nNonce += step; + } while(header.nNonce != next); + } +} + +static int Grind(int argc, char* argv[], std::string& strPrint) +{ + if (argc != 1) { + strPrint = "Must specify block header to grind"; + return 1; + } + + CBlockHeader header; + if (!DecodeHexBlockHeader(header, argv[0])) { + strPrint = "Could not decode block header"; + return 1; + } + + uint32_t nBits = header.nBits; + std::atomic<bool> found{false}; + + std::vector<std::thread> threads; + int n_tasks = std::max(1u, std::thread::hardware_concurrency()); + for (int i = 0; i < n_tasks; ++i) { + threads.emplace_back( grind_task, nBits, std::ref(header), i, n_tasks, std::ref(found) ); + } + for (auto& t : threads) { + t.join(); + } + if (!found) { + strPrint = "Could not satisfy difficulty target"; + return 1; + } + + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << header; + strPrint = HexStr(ss); + return 0; +} + +static int CommandLineUtil(int argc, char* argv[]) +{ + if (argc <= 1) return 1; + + std::string strPrint; + int nRet = 0; + + try { + while (argc > 1 && IsSwitchChar(argv[1][0]) && (argv[1][1] != 0)) { + --argc; + ++argv; + } + + char* command = argv[1]; + if (strcmp(command, "grind") == 0) { + nRet = Grind(argc-2, argv+2, strPrint); + } else { + strPrint = strprintf("Unknown command %s", command); + nRet = 1; + } + } + catch (const std::exception& e) { + strPrint = std::string("error: ") + e.what(); + nRet = EXIT_FAILURE; + } + catch (...) { + PrintExceptionContinue(nullptr, "CommandLineUtil()"); + throw; + } + + if (strPrint != "") { + tfm::format(nRet == 0 ? std::cout : std::cerr, "%s\n", strPrint); + } + return nRet; +} + +int main(int argc, char* argv[]) +{ + SetupEnvironment(); + + try { + int ret = AppInitUtil(argc, argv); + if (ret != CONTINUE_EXECUTION) + return ret; + } + catch (const std::exception& e) { + PrintExceptionContinue(&e, "AppInitUtil()"); + return EXIT_FAILURE; + } catch (...) { + PrintExceptionContinue(nullptr, "AppInitUtil()"); + return EXIT_FAILURE; + } + + int ret = EXIT_FAILURE; + try { + ret = CommandLineUtil(argc, argv); + } + catch (const std::exception& e) { + PrintExceptionContinue(&e, "CommandLineUtil()"); + } catch (...) { + PrintExceptionContinue(nullptr, "CommandLineUtil()"); + } + return ret; +} diff --git a/src/coins.cpp b/src/coins.cpp index 14f58e956c..dd84e720e7 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -31,8 +31,6 @@ bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); } size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); } -SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} - CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), cachedCoinsUsage(0) {} size_t CCoinsViewCache::DynamicMemoryUsage() const { diff --git a/src/coins.h b/src/coins.h index a3e241ac90..d2eb42d8cf 100644 --- a/src/coins.h +++ b/src/coins.h @@ -8,11 +8,11 @@ #include <compressor.h> #include <core_memusage.h> -#include <crypto/siphash.h> #include <memusage.h> #include <primitives/transaction.h> #include <serialize.h> #include <uint256.h> +#include <util/hasher.h> #include <assert.h> #include <stdint.h> @@ -82,33 +82,6 @@ public: } }; -class SaltedOutpointHasher -{ -private: - /** Salt */ - const uint64_t k0, k1; - -public: - SaltedOutpointHasher(); - - /** - * This *must* return size_t. With Boost 1.46 on 32-bit systems the - * unordered_map will behave unpredictably if the custom hasher returns a - * uint64_t, resulting in failures when syncing the chain (#4634). - * - * Having the hash noexcept allows libstdc++'s unordered_map to recalculate - * the hash during rehash, so it does not have to cache the value. This - * reduces node's memory by sizeof(size_t). The required recalculation has - * a slight performance penalty (around 1.6%), but this is compensated by - * memory savings of about 9% which allow for a larger dbcache setting. - * - * @see https://gcc.gnu.org/onlinedocs/gcc-9.2.0/libstdc++/manual/manual/unordered_associative.html - */ - size_t operator()(const COutPoint& id) const noexcept { - return SipHashUint256Extra(k0, k1, id.hash, id.n); - } -}; - /** * A Coin in one level of the coins database caching hierarchy. * diff --git a/src/index/blockfilterindex.h b/src/index/blockfilterindex.h index a9c8188bb6..221ac02c9e 100644 --- a/src/index/blockfilterindex.h +++ b/src/index/blockfilterindex.h @@ -9,15 +9,11 @@ #include <chain.h> #include <flatfile.h> #include <index/base.h> +#include <util/hasher.h> /** Interval between compact filter checkpoints. See BIP 157. */ static constexpr int CFCHECKPT_INTERVAL = 1000; -struct FilterHeaderHasher -{ - size_t operator()(const uint256& hash) const { return ReadLE64(hash.begin()); } -}; - /** * BlockFilterIndex is used to store and retrieve block filters, hashes, and headers for a range of * blocks by height. An index is constructed for each supported filter type with its own database diff --git a/src/init.cpp b/src/init.cpp index 9a63fe0e03..09eb76eaee 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1410,8 +1410,8 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA ChainstateManager& chainman = *Assert(node.chainman); assert(!node.peerman); - node.peerman = std::make_unique<PeerManager>(chainparams, *node.connman, node.banman.get(), - *node.scheduler, chainman, *node.mempool, ignores_incoming_txs); + node.peerman = PeerManager::make(chainparams, *node.connman, node.banman.get(), + *node.scheduler, chainman, *node.mempool, ignores_incoming_txs); RegisterValidationInterface(node.peerman.get()); // sanitize comments per BIP-0014, format user agent and check total size @@ -760,11 +760,30 @@ private: class NetEventsInterface { public: - virtual bool ProcessMessages(CNode* pnode, std::atomic<bool>& interrupt) = 0; - virtual bool SendMessages(CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(pnode->cs_sendProcessing) = 0; + /** Initialize a peer (setup state, queue any initial messages) */ virtual void InitializeNode(CNode* pnode) = 0; + + /** Handle removal of a peer (clear state) */ virtual void FinalizeNode(const CNode& node, bool& update_connection_time) = 0; + /** + * Process protocol messages received from a given node + * + * @param[in] pnode The node which we have received messages from. + * @param[in] interrupt Interrupt condition for processing threads + * @return True if there is more work to be done + */ + virtual bool ProcessMessages(CNode* pnode, std::atomic<bool>& interrupt) = 0; + + /** + * Send queued protocol messages to a given node. + * + * @param[in] pnode The node which we are sending messages to. + * @return True if there is more work to be done + */ + virtual bool SendMessages(CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(pnode->cs_sendProcessing) = 0; + + protected: /** * Protected destructor so that instances can only be deleted by derived classes. diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 8f1cb952f2..cf73b1dae2 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -26,6 +26,7 @@ #include <streams.h> #include <tinyformat.h> #include <txmempool.h> +#include <txrequest.h> #include <util/check.h> // For NDEBUG compile time check #include <util/strencodings.h> #include <util/system.h> @@ -168,6 +169,186 @@ void EraseOrphansFor(NodeId peer); // Internal stuff namespace { +/** + * Data structure for an individual peer. This struct is not protected by + * cs_main since it does not contain validation-critical data. + * + * Memory is owned by shared pointers and this object is destructed when + * the refcount drops to zero. + * + * Mutexes inside this struct must not be held when locking m_peer_mutex. + * + * TODO: move most members from CNodeState to this structure. + * TODO: move remaining application-layer data members from CNode to this structure. + */ +struct Peer { + /** Same id as the CNode object for this peer */ + const NodeId m_id{0}; + + /** Protects misbehavior data members */ + Mutex m_misbehavior_mutex; + /** Accumulated misbehavior score for this peer */ + int m_misbehavior_score GUARDED_BY(m_misbehavior_mutex){0}; + /** Whether this peer should be disconnected and marked as discouraged (unless it has the noban permission). */ + bool m_should_discourage GUARDED_BY(m_misbehavior_mutex){false}; + + /** Protects block inventory data members */ + Mutex m_block_inv_mutex; + /** List of blocks that we'll announce via an `inv` message. + * There is no final sorting before sending, as they are always sent + * immediately and in the order requested. */ + std::vector<uint256> m_blocks_for_inv_relay GUARDED_BY(m_block_inv_mutex); + /** Unfiltered list of blocks that we'd like to announce via a `headers` + * message. If we can't announce via a `headers` message, we'll fall back to + * announcing via `inv`. */ + std::vector<uint256> m_blocks_for_headers_relay GUARDED_BY(m_block_inv_mutex); + /** The final block hash that we sent in an `inv` message to this peer. + * When the peer requests this block, we send an `inv` message to trigger + * the peer to request the next sequence of block hashes. + * Most peers use headers-first syncing, which doesn't use this mechanism */ + uint256 m_continuation_block GUARDED_BY(m_block_inv_mutex) {}; + + /** This peer's reported block height when we connected */ + std::atomic<int> m_starting_height{-1}; + + /** Set of txids to reconsider once their parent transactions have been accepted **/ + std::set<uint256> m_orphan_work_set GUARDED_BY(g_cs_orphans); + + /** Protects m_getdata_requests **/ + Mutex m_getdata_requests_mutex; + /** Work queue of items requested by this peer **/ + std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex); + + explicit Peer(NodeId id) : m_id(id) {} +}; + +using PeerRef = std::shared_ptr<Peer>; + +class PeerManagerImpl final : public PeerManager +{ +public: + PeerManagerImpl(const CChainParams& chainparams, CConnman& connman, BanMan* banman, + CScheduler& scheduler, ChainstateManager& chainman, CTxMemPool& pool, + bool ignore_incoming_txs); + + /** Overridden from CValidationInterface. */ + void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected) override; + void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex) override; + void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; + void BlockChecked(const CBlock& block, const BlockValidationState& state) override; + void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) override; + + /** Implement NetEventsInterface */ + void InitializeNode(CNode* pnode) override; + void FinalizeNode(const CNode& node, bool& fUpdateConnectionTime) override; + bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override; + bool SendMessages(CNode* pto) override EXCLUSIVE_LOCKS_REQUIRED(pto->cs_sendProcessing); + + /** Implement PeerManager */ + void CheckForStaleTipAndEvictPeers() override; + bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) override; + bool IgnoresIncomingTxs() override { return m_ignore_incoming_txs; } + void SetBestHeight(int height) override { m_best_height = height; }; + void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) override; + void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, + const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override; + +private: + /** Consider evicting an outbound peer based on the amount of time they've been behind our tip */ + void ConsiderEviction(CNode& pto, int64_t time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** If we have extra outbound peers, try to disconnect the one with the oldest block announcement */ + void EvictExtraOutboundPeers(int64_t time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** Retrieve unbroadcast transactions from the mempool and reattempt sending to peers */ + void ReattemptInitialBroadcast(CScheduler& scheduler) const; + + /** Get a shared pointer to the Peer object. + * May return an empty shared_ptr if the Peer object can't be found. */ + PeerRef GetPeerRef(NodeId id) const; + + /** Get a shared pointer to the Peer object and remove it from m_peer_map. + * May return an empty shared_ptr if the Peer object can't be found. */ + PeerRef RemovePeer(NodeId id); + + /** + * Potentially mark a node discouraged based on the contents of a BlockValidationState object + * + * @param[in] via_compact_block this bool is passed in because net_processing should + * punish peers differently depending on whether the data was provided in a compact + * block message or not. If the compact block had a valid header, but contained invalid + * txs, the peer should not be punished. See BIP 152. + * + * @return Returns true if the peer was punished (probably disconnected) + */ + bool MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state, + bool via_compact_block, const std::string& message = ""); + + /** + * Potentially disconnect and discourage a node based on the contents of a TxValidationState object + * + * @return Returns true if the peer was punished (probably disconnected) + */ + bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message = ""); + + /** Maybe disconnect a peer and discourage future connections from its address. + * + * @param[in] pnode The node to check. + * @return True if the peer was marked for disconnection in this function + */ + bool MaybeDiscourageAndDisconnect(CNode& pnode); + + void ProcessOrphanTx(std::set<uint256>& orphan_work_set) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans); + /** Process a single headers message from a peer. */ + void ProcessHeadersMessage(CNode& pfrom, const Peer& peer, + const std::vector<CBlockHeader>& headers, + bool via_compact_block); + + void SendBlockTransactions(CNode& pfrom, const CBlock& block, const BlockTransactionsRequest& req); + + /** Register with TxRequestTracker that an INV has been received from a + * peer. The announcement parameters are decided in PeerManager and then + * passed to TxRequestTracker. */ + void AddTxAnnouncement(const CNode& node, const GenTxid& gtxid, std::chrono::microseconds current_time) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + + /** Send a version message to a peer */ + void PushNodeVersion(CNode& pnode, int64_t nTime); + + const CChainParams& m_chainparams; + CConnman& m_connman; + /** Pointer to this node's banman. May be nullptr - check existence before dereferencing. */ + BanMan* const m_banman; + ChainstateManager& m_chainman; + CTxMemPool& m_mempool; + TxRequestTracker m_txrequest GUARDED_BY(::cs_main); + + /** The height of the best chain */ + std::atomic<int> m_best_height{-1}; + + int64_t m_stale_tip_check_time; //!< Next time to check for stale tip + + /** Whether this node is running in blocks only mode */ + const bool m_ignore_incoming_txs; + + /** Whether we've completed initial sync yet, for determining when to turn + * on extra block-relay-only peers. */ + bool m_initial_sync_finished{false}; + + /** Protects m_peer_map. This mutex must not be locked while holding a lock + * on any of the mutexes inside a Peer object. */ + mutable Mutex m_peer_mutex; + /** + * Map of all Peer objects, keyed by peer id. This map is protected + * by the m_peer_mutex. Once a shared pointer reference is + * taken, the lock may be released. Individual fields are protected by + * their own locks. + */ + std::map<NodeId, PeerRef> m_peer_map GUARDED_BY(m_peer_mutex); +}; +} // namespace + +namespace { /** Number of nodes with fSyncStarted. */ int nSyncStarted GUARDED_BY(cs_main) = 0; @@ -683,7 +864,7 @@ static void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vec } // namespace -void PeerManager::PushNodeVersion(CNode& pnode, int64_t nTime) +void PeerManagerImpl::PushNodeVersion(CNode& pnode, int64_t nTime) { // Note that pnode->GetLocalServices() is a reflection of the local // services we were offering when the CNode object was created for this @@ -709,7 +890,7 @@ void PeerManager::PushNodeVersion(CNode& pnode, int64_t nTime) } } -void PeerManager::AddTxAnnouncement(const CNode& node, const GenTxid& gtxid, std::chrono::microseconds current_time) +void PeerManagerImpl::AddTxAnnouncement(const CNode& node, const GenTxid& gtxid, std::chrono::microseconds current_time) { AssertLockHeld(::cs_main); // For m_txrequest NodeId nodeid = node.GetId(); @@ -745,7 +926,8 @@ void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) if (state) state->m_last_block_announcement = time_in_seconds; } -void PeerManager::InitializeNode(CNode *pnode) { +void PeerManagerImpl::InitializeNode(CNode *pnode) +{ CAddress addr = pnode->addr; std::string addrName = pnode->GetAddrName(); NodeId nodeid = pnode->GetId(); @@ -764,7 +946,7 @@ void PeerManager::InitializeNode(CNode *pnode) { } } -void PeerManager::ReattemptInitialBroadcast(CScheduler& scheduler) const +void PeerManagerImpl::ReattemptInitialBroadcast(CScheduler& scheduler) const { std::set<uint256> unbroadcast_txids = m_mempool.GetUnbroadcastTxs(); @@ -785,7 +967,8 @@ void PeerManager::ReattemptInitialBroadcast(CScheduler& scheduler) const scheduler.scheduleFromNow([&] { ReattemptInitialBroadcast(scheduler); }, delta); } -void PeerManager::FinalizeNode(const CNode& node, bool& fUpdateConnectionTime) { +void PeerManagerImpl::FinalizeNode(const CNode& node, bool& fUpdateConnectionTime) +{ NodeId nodeid = node.GetId(); fUpdateConnectionTime = false; LOCK(cs_main); @@ -839,14 +1022,14 @@ void PeerManager::FinalizeNode(const CNode& node, bool& fUpdateConnectionTime) { LogPrint(BCLog::NET, "Cleared nodestate for peer=%d\n", nodeid); } -PeerRef PeerManager::GetPeerRef(NodeId id) const +PeerRef PeerManagerImpl::GetPeerRef(NodeId id) const { LOCK(m_peer_mutex); auto it = m_peer_map.find(id); return it != m_peer_map.end() ? it->second : nullptr; } -PeerRef PeerManager::RemovePeer(NodeId id) +PeerRef PeerManagerImpl::RemovePeer(NodeId id) { PeerRef ret; LOCK(m_peer_mutex); @@ -858,7 +1041,8 @@ PeerRef PeerManager::RemovePeer(NodeId id) return ret; } -bool PeerManager::GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats) { +bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats) +{ { LOCK(cs_main); CNodeState* state = State(nodeid); @@ -1015,7 +1199,7 @@ unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) return nEvicted; } -void PeerManager::Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) +void PeerManagerImpl::Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) { assert(howmuch > 0); @@ -1033,8 +1217,8 @@ void PeerManager::Misbehaving(const NodeId pnode, const int howmuch, const std:: } } -bool PeerManager::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state, - bool via_compact_block, const std::string& message) +bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state, + bool via_compact_block, const std::string& message) { switch (state.GetResult()) { case BlockValidationResult::BLOCK_RESULT_UNSET: @@ -1083,7 +1267,7 @@ bool PeerManager::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationSt return false; } -bool PeerManager::MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message) +bool PeerManagerImpl::MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message) { switch (state.GetResult()) { case TxValidationResult::TX_RESULT_UNSET: @@ -1129,9 +1313,16 @@ static bool BlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Para (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, consensusParams) < STALE_RELAY_AGE_LIMIT); } -PeerManager::PeerManager(const CChainParams& chainparams, CConnman& connman, BanMan* banman, - CScheduler& scheduler, ChainstateManager& chainman, CTxMemPool& pool, - bool ignore_incoming_txs) +std::unique_ptr<PeerManager> PeerManager::make(const CChainParams& chainparams, CConnman& connman, BanMan* banman, + CScheduler& scheduler, ChainstateManager& chainman, CTxMemPool& pool, + bool ignore_incoming_txs) +{ + return std::make_unique<PeerManagerImpl>(chainparams, connman, banman, scheduler, chainman, pool, ignore_incoming_txs); +} + +PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& connman, BanMan* banman, + CScheduler& scheduler, ChainstateManager& chainman, CTxMemPool& pool, + bool ignore_incoming_txs) : m_chainparams(chainparams), m_connman(connman), m_banman(banman), @@ -1171,7 +1362,7 @@ PeerManager::PeerManager(const CChainParams& chainparams, CConnman& connman, Ban * block, remember the recently confirmed transactions, and delete tracked * announcements for them. Also save the time of the last tip update. */ -void PeerManager::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex) +void PeerManagerImpl::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex) { { LOCK(g_cs_orphans); @@ -1222,7 +1413,7 @@ void PeerManager::BlockConnected(const std::shared_ptr<const CBlock>& pblock, co } } -void PeerManager::BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex) +void PeerManagerImpl::BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex) { // To avoid relay problems with transactions that were previously // confirmed, clear our filter of recently confirmed transactions whenever @@ -1247,7 +1438,8 @@ static bool fWitnessesPresentInMostRecentCompactBlock GUARDED_BY(cs_most_recent_ * Maintain state about the best-seen block and fast-announce a compact block * to compatible peers. */ -void PeerManager::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) { +void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) +{ std::shared_ptr<const CBlockHeaderAndShortTxIDs> pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs> (*pblock, true); const CNetMsgMaker msgMaker(PROTOCOL_VERSION); @@ -1294,9 +1486,9 @@ void PeerManager::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ * Update our best height and announce any block hashes which weren't previously * in ::ChainActive() to our peers. */ -void PeerManager::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) +void PeerManagerImpl::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) { - m_best_height = pindexNew->nHeight; + SetBestHeight(pindexNew->nHeight); SetServiceFlagsIBDCache(!fInitialDownload); // Don't relay inventory during initial block download. @@ -1333,7 +1525,8 @@ void PeerManager::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockInde * Handle invalid block rejection and consequent peer discouragement, maintain which * peers announce compact blocks. */ -void PeerManager::BlockChecked(const CBlock& block, const BlockValidationState& state) { +void PeerManagerImpl::BlockChecked(const CBlock& block, const BlockValidationState& state) +{ LOCK(cs_main); const uint256 hash(block.GetHash()); @@ -1761,7 +1954,8 @@ static uint32_t GetFetchFlags(const CNode& pfrom) EXCLUSIVE_LOCKS_REQUIRED(cs_ma return nFetchFlags; } -void PeerManager::SendBlockTransactions(CNode& pfrom, const CBlock& block, const BlockTransactionsRequest& req) { +void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, const CBlock& block, const BlockTransactionsRequest& req) +{ BlockTransactions resp(req); for (size_t i = 0; i < req.indexes.size(); i++) { if (req.indexes[i] >= block.vtx.size()) { @@ -1776,9 +1970,9 @@ void PeerManager::SendBlockTransactions(CNode& pfrom, const CBlock& block, const m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp)); } -void PeerManager::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, - const std::vector<CBlockHeader>& headers, - bool via_compact_block) +void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, + const std::vector<CBlockHeader>& headers, + bool via_compact_block) { const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); size_t nCount = headers.size(); @@ -1970,7 +2164,7 @@ void PeerManager::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, * may be added to if accepting an orphan causes its children to be * reconsidered. */ -void PeerManager::ProcessOrphanTx(std::set<uint256>& orphan_work_set) +void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set) { AssertLockHeld(cs_main); AssertLockHeld(g_cs_orphans); @@ -2267,9 +2461,9 @@ static void ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv, const CChainPar connman.PushMessage(&peer, std::move(msg)); } -void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, - const std::chrono::microseconds time_received, - const std::atomic<bool>& interruptMsgProc) +void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, + const std::chrono::microseconds time_received, + const std::atomic<bool>& interruptMsgProc) { LogPrint(BCLog::NET, "received: %s (%u bytes) peer=%d\n", SanitizeString(msg_type), vRecv.size(), pfrom.GetId()); @@ -3755,7 +3949,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat return; } -bool PeerManager::MaybeDiscourageAndDisconnect(CNode& pnode) +bool PeerManagerImpl::MaybeDiscourageAndDisconnect(CNode& pnode) { const NodeId peer_id{pnode.GetId()}; PeerRef peer = GetPeerRef(peer_id); @@ -3797,7 +3991,7 @@ bool PeerManager::MaybeDiscourageAndDisconnect(CNode& pnode) return true; } -bool PeerManager::ProcessMessages(CNode* pfrom, std::atomic<bool>& interruptMsgProc) +bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interruptMsgProc) { bool fMoreWork = false; @@ -3872,7 +4066,7 @@ bool PeerManager::ProcessMessages(CNode* pfrom, std::atomic<bool>& interruptMsgP return fMoreWork; } -void PeerManager::ConsiderEviction(CNode& pto, int64_t time_in_seconds) +void PeerManagerImpl::ConsiderEviction(CNode& pto, int64_t time_in_seconds) { AssertLockHeld(cs_main); @@ -3925,7 +4119,7 @@ void PeerManager::ConsiderEviction(CNode& pto, int64_t time_in_seconds) } } -void PeerManager::EvictExtraOutboundPeers(int64_t time_in_seconds) +void PeerManagerImpl::EvictExtraOutboundPeers(int64_t time_in_seconds) { // If we have any extra block-relay-only peers, disconnect the youngest unless // it's given us a block -- in which case, compare with the second-youngest, and @@ -4026,7 +4220,7 @@ void PeerManager::EvictExtraOutboundPeers(int64_t time_in_seconds) } } -void PeerManager::CheckForStaleTipAndEvictPeers() +void PeerManagerImpl::CheckForStaleTipAndEvictPeers() { LOCK(cs_main); @@ -4073,7 +4267,7 @@ public: }; } -bool PeerManager::SendMessages(CNode* pto) +bool PeerManagerImpl::SendMessages(CNode* pto) { PeerRef peer = GetPeerRef(pto->GetId()); const Consensus::Params& consensusParams = m_chainparams.GetConsensus(); diff --git a/src/net_processing.h b/src/net_processing.h index 322fabbe11..eaa3b142a8 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -6,19 +6,13 @@ #ifndef BITCOIN_NET_PROCESSING_H #define BITCOIN_NET_PROCESSING_H -#include <consensus/params.h> #include <net.h> #include <sync.h> -#include <txrequest.h> #include <validationinterface.h> -class BlockTransactionsRequest; -class BlockValidationState; -class CBlockHeader; class CChainParams; class CTxMemPool; class ChainstateManager; -class TxValidationState; extern RecursiveMutex cs_main; extern RecursiveMutex g_cs_orphans; @@ -39,216 +33,39 @@ struct CNodeStateStats { std::vector<int> vHeightInFlight; }; -/** - * Data structure for an individual peer. This struct is not protected by - * cs_main since it does not contain validation-critical data. - * - * Memory is owned by shared pointers and this object is destructed when - * the refcount drops to zero. - * - * Mutexes inside this struct must not be held when locking m_peer_mutex. - * - * TODO: move most members from CNodeState to this structure. - * TODO: move remaining application-layer data members from CNode to this structure. - */ -struct Peer { - /** Same id as the CNode object for this peer */ - const NodeId m_id{0}; - - /** Protects misbehavior data members */ - Mutex m_misbehavior_mutex; - /** Accumulated misbehavior score for this peer */ - int m_misbehavior_score GUARDED_BY(m_misbehavior_mutex){0}; - /** Whether this peer should be disconnected and marked as discouraged (unless it has the noban permission). */ - bool m_should_discourage GUARDED_BY(m_misbehavior_mutex){false}; - - /** Protects block inventory data members */ - Mutex m_block_inv_mutex; - /** List of blocks that we'll announce via an `inv` message. - * There is no final sorting before sending, as they are always sent - * immediately and in the order requested. */ - std::vector<uint256> m_blocks_for_inv_relay GUARDED_BY(m_block_inv_mutex); - /** Unfiltered list of blocks that we'd like to announce via a `headers` - * message. If we can't announce via a `headers` message, we'll fall back to - * announcing via `inv`. */ - std::vector<uint256> m_blocks_for_headers_relay GUARDED_BY(m_block_inv_mutex); - /** The final block hash that we sent in an `inv` message to this peer. - * When the peer requests this block, we send an `inv` message to trigger - * the peer to request the next sequence of block hashes. - * Most peers use headers-first syncing, which doesn't use this mechanism */ - uint256 m_continuation_block GUARDED_BY(m_block_inv_mutex) {}; - - /** This peer's reported block height when we connected */ - std::atomic<int> m_starting_height{-1}; - - /** Set of txids to reconsider once their parent transactions have been accepted **/ - std::set<uint256> m_orphan_work_set GUARDED_BY(g_cs_orphans); - - /** Protects m_getdata_requests **/ - Mutex m_getdata_requests_mutex; - /** Work queue of items requested by this peer **/ - std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex); - - explicit Peer(NodeId id) : m_id(id) {} -}; - -using PeerRef = std::shared_ptr<Peer>; - -class PeerManager final : public CValidationInterface, public NetEventsInterface { +class PeerManager : public CValidationInterface, public NetEventsInterface +{ public: - PeerManager(const CChainParams& chainparams, CConnman& connman, BanMan* banman, - CScheduler& scheduler, ChainstateManager& chainman, CTxMemPool& pool, - bool ignore_incoming_txs); + static std::unique_ptr<PeerManager> make(const CChainParams& chainparams, CConnman& connman, BanMan* banman, + CScheduler& scheduler, ChainstateManager& chainman, CTxMemPool& pool, + bool ignore_incoming_txs); + virtual ~PeerManager() { } - /** - * Overridden from CValidationInterface. - */ - void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected) override; - void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex) override; - /** - * Overridden from CValidationInterface. - */ - void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; - /** - * Overridden from CValidationInterface. - */ - void BlockChecked(const CBlock& block, const BlockValidationState& state) override; - /** - * Overridden from CValidationInterface. - */ - void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) override; - - /** Initialize a peer by adding it to mapNodeState and pushing a message requesting its version */ - void InitializeNode(CNode* pnode) override; - /** Handle removal of a peer by updating various state and removing it from mapNodeState */ - void FinalizeNode(const CNode& node, bool& fUpdateConnectionTime) override; - /** - * Process protocol messages received from a given node - * - * @param[in] pfrom The node which we have received messages from. - * @param[in] interrupt Interrupt condition for processing threads - */ - bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override; - /** - * Send queued protocol messages to be sent to a give node. - * - * @param[in] pto The node which we are sending messages to. - * @return True if there is more work to be done - */ - bool SendMessages(CNode* pto) override EXCLUSIVE_LOCKS_REQUIRED(pto->cs_sendProcessing); + /** Get statistics from node state */ + virtual bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) = 0; - /** Consider evicting an outbound peer based on the amount of time they've been behind our tip */ - void ConsiderEviction(CNode& pto, int64_t time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - /** Evict extra outbound peers. If we think our tip may be stale, connect to an extra outbound */ - void CheckForStaleTipAndEvictPeers(); - /** If we have extra outbound peers, try to disconnect the one with the oldest block announcement */ - void EvictExtraOutboundPeers(int64_t time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - /** Retrieve unbroadcast transactions from the mempool and reattempt sending to peers */ - void ReattemptInitialBroadcast(CScheduler& scheduler) const; + /** Whether this node ignores txs received over p2p. */ + virtual bool IgnoresIncomingTxs() = 0; - /** Process a single message from a peer. Public for fuzz testing */ - void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, - const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc); + /** Set the best height */ + virtual void SetBestHeight(int height) = 0; /** * Increment peer's misbehavior score. If the new value >= DISCOURAGEMENT_THRESHOLD, mark the node * to be discouraged, meaning the peer might be disconnected and added to the discouragement filter. * Public for unit testing. */ - void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message); - - /** Get statistics from node state */ - bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats); - - /** Set the best height */ - void SetBestHeight(int height) { m_best_height = height; }; - - /** Whether this node ignores txs received over p2p. */ - bool IgnoresIncomingTxs() { return m_ignore_incoming_txs; }; - -private: - /** Get a shared pointer to the Peer object. - * May return an empty shared_ptr if the Peer object can't be found. */ - PeerRef GetPeerRef(NodeId id) const; - - /** Get a shared pointer to the Peer object and remove it from m_peer_map. - * May return an empty shared_ptr if the Peer object can't be found. */ - PeerRef RemovePeer(NodeId id); - - /** - * Potentially mark a node discouraged based on the contents of a BlockValidationState object - * - * @param[in] via_compact_block this bool is passed in because net_processing should - * punish peers differently depending on whether the data was provided in a compact - * block message or not. If the compact block had a valid header, but contained invalid - * txs, the peer should not be punished. See BIP 152. - * - * @return Returns true if the peer was punished (probably disconnected) - */ - bool MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state, - bool via_compact_block, const std::string& message = ""); + virtual void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) = 0; /** - * Potentially disconnect and discourage a node based on the contents of a TxValidationState object - * - * @return Returns true if the peer was punished (probably disconnected) - */ - bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message = ""); - - /** Maybe disconnect a peer and discourage future connections from its address. - * - * @param[in] pnode The node to check. - * @return True if the peer was marked for disconnection in this function + * Evict extra outbound peers. If we think our tip may be stale, connect to an extra outbound. + * Public for unit testing. */ - bool MaybeDiscourageAndDisconnect(CNode& pnode); + virtual void CheckForStaleTipAndEvictPeers() = 0; - void ProcessOrphanTx(std::set<uint256>& orphan_work_set) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans); - /** Process a single headers message from a peer. */ - void ProcessHeadersMessage(CNode& pfrom, const Peer& peer, - const std::vector<CBlockHeader>& headers, - bool via_compact_block); - - void SendBlockTransactions(CNode& pfrom, const CBlock& block, const BlockTransactionsRequest& req); - - /** Register with TxRequestTracker that an INV has been received from a - * peer. The announcement parameters are decided in PeerManager and then - * passed to TxRequestTracker. */ - void AddTxAnnouncement(const CNode& node, const GenTxid& gtxid, std::chrono::microseconds current_time) - EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - - /** Send a version message to a peer */ - void PushNodeVersion(CNode& pnode, int64_t nTime); - - const CChainParams& m_chainparams; - CConnman& m_connman; - /** Pointer to this node's banman. May be nullptr - check existence before dereferencing. */ - BanMan* const m_banman; - ChainstateManager& m_chainman; - CTxMemPool& m_mempool; - TxRequestTracker m_txrequest GUARDED_BY(::cs_main); - - /** The height of the best chain */ - std::atomic<int> m_best_height{-1}; - - int64_t m_stale_tip_check_time; //!< Next time to check for stale tip - - /** Whether this node is running in blocks only mode */ - const bool m_ignore_incoming_txs; - - /** Whether we've completed initial sync yet, for determining when to turn - * on extra block-relay-only peers. */ - bool m_initial_sync_finished{false}; - - /** Protects m_peer_map. This mutex must not be locked while holding a lock - * on any of the mutexes inside a Peer object. */ - mutable Mutex m_peer_mutex; - /** - * Map of all Peer objects, keyed by peer id. This map is protected - * by the m_peer_mutex. Once a shared pointer reference is - * taken, the lock may be released. Individual fields are protected by - * their own locks. - */ - std::map<NodeId, PeerRef> m_peer_map GUARDED_BY(m_peer_mutex); + /** Process a single message from a peer. Public for fuzz testing */ + virtual void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, + const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) = 0; }; /** Relay transaction to every node */ diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index e765e643a3..9eedd8cf75 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -973,6 +973,9 @@ SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QStri setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); setDefaultButton(QMessageBox::Cancel); yesButton = button(QMessageBox::Yes); + if (confirmButtonText.isEmpty()) { + confirmButtonText = yesButton->text(); + } updateYesButton(); connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown); } diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 8e241d3901..4fc2f57cd6 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -115,7 +115,7 @@ class SendConfirmationDialog : public QMessageBox Q_OBJECT public: - SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, const QString& confirmText = "Send", QWidget* parent = nullptr); + SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, const QString& confirmText = "", QWidget* parent = nullptr); int exec() override; private Q_SLOTS: diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 689a8165ab..3f97f6f3d9 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1283,7 +1283,7 @@ RPCHelpMan getblockchaininfo() RPCResult{ RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::STR, "chain", "current network name (main, test, regtest)"}, + {RPCResult::Type::STR, "chain", "current network name (main, test, signet, regtest)"}, {RPCResult::Type::NUM, "blocks", "the height of the most-work fully-validated chain. The genesis block has height 0"}, {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"}, {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"}, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 52e033d0cc..0e96beb6db 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -409,7 +409,7 @@ static RPCHelpMan getmininginfo() {RPCResult::Type::NUM, "difficulty", "The current difficulty"}, {RPCResult::Type::NUM, "networkhashps", "The network hashes per second"}, {RPCResult::Type::NUM, "pooledtx", "The size of the mempool"}, - {RPCResult::Type::STR, "chain", "current network name (main, test, regtest)"}, + {RPCResult::Type::STR, "chain", "current network name (main, test, signet, regtest)"}, {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, }}, RPCExamples{ @@ -658,11 +658,15 @@ static RPCHelpMan getblocktemplate() if(!node.connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - if (node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) - throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, PACKAGE_NAME " is not connected!"); + if (!Params().IsTestChain()) { + if (node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) { + throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, PACKAGE_NAME " is not connected!"); + } - if (::ChainstateActive().IsInitialBlockDownload()) - throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, PACKAGE_NAME " is in initial sync and waiting for blocks..."); + if (::ChainstateActive().IsInitialBlockDownload()) { + throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, PACKAGE_NAME " is in initial sync and waiting for blocks..."); + } + } static unsigned int nTransactionsUpdatedLast; const CTxMemPool& mempool = EnsureMemPool(request.context); @@ -714,6 +718,13 @@ static RPCHelpMan getblocktemplate() // TODO: Maybe recheck connections/IBD and (if something wrong) send an expires-immediately template to stop miners? } + const Consensus::Params& consensusParams = Params().GetConsensus(); + + // GBT must be called with 'signet' set in the rules for signet chains + if (consensusParams.signet_blocks && setClientRules.count("signet") != 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "getblocktemplate must be called with the signet rule set (call with {\"rules\": [\"segwit\", \"signet\"]})"); + } + // GBT must be called with 'segwit' set in the rules if (setClientRules.count("segwit") != 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "getblocktemplate must be called with the segwit rule set (call with {\"rules\": [\"segwit\"]})"); @@ -745,7 +756,6 @@ static RPCHelpMan getblocktemplate() } CHECK_NONFATAL(pindexPrev); CBlock* pblock = &pblocktemplate->block; // pointer for convenience - const Consensus::Params& consensusParams = Params().GetConsensus(); // Update nTime UpdateTime(pblock, consensusParams, pindexPrev); @@ -809,6 +819,12 @@ static RPCHelpMan getblocktemplate() UniValue aRules(UniValue::VARR); aRules.push_back("csv"); if (!fPreSegWit) aRules.push_back("!segwit"); + if (consensusParams.signet_blocks) { + // indicate to miner that they must understand signet rules + // when attempting to mine with this template + aRules.push_back("!signet"); + } + UniValue vbavailable(UniValue::VOBJ); for (int j = 0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) { Consensus::DeploymentPos pos = Consensus::DeploymentPos(j); @@ -889,6 +905,10 @@ static RPCHelpMan getblocktemplate() result.pushKV("bits", strprintf("%08x", pblock->nBits)); result.pushKV("height", (int64_t)(pindexPrev->nHeight+1)); + if (consensusParams.signet_blocks) { + result.pushKV("signet_challenge", HexStr(consensusParams.signet_challenge)); + } + if (!pblocktemplate->vchCoinbaseCommitment.empty()) { result.pushKV("default_witness_commitment", HexStr(pblocktemplate->vchCoinbaseCommitment)); } diff --git a/src/script/sigcache.h b/src/script/sigcache.h index 512f61f2bf..bf0ba38c2d 100644 --- a/src/script/sigcache.h +++ b/src/script/sigcache.h @@ -8,6 +8,7 @@ #include <script/interpreter.h> #include <span.h> +#include <util/hasher.h> #include <vector> @@ -20,27 +21,6 @@ static const int64_t MAX_MAX_SIG_CACHE_SIZE = 16384; class CPubKey; -/** - * We're hashing a nonce into the entries themselves, so we don't need extra - * blinding in the set hash computation. - * - * This may exhibit platform endian dependent behavior but because these are - * nonced hashes (random) and this state is only ever used locally it is safe. - * All that matters is local consistency. - */ -class SignatureCacheHasher -{ -public: - template <uint8_t hash_select> - uint32_t operator()(const uint256& key) const - { - static_assert(hash_select <8, "SignatureCacheHasher only has 8 hashes available."); - uint32_t u; - std::memcpy(&u, key.begin()+4*hash_select, 4); - return u; - } -}; - class CachingTransactionSignatureChecker : public TransactionSignatureChecker { private: diff --git a/src/sync.h b/src/sync.h index 749bf5575c..53213c2089 100644 --- a/src/sync.h +++ b/src/sync.h @@ -258,7 +258,22 @@ using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove //! //! int val = WITH_LOCK(cs, return shared_val); //! -#define WITH_LOCK(cs, code) [&] { LOCK(cs); code; }() +//! Note: +//! +//! Since the return type deduction follows that of decltype(auto), while the +//! deduced type of: +//! +//! WITH_LOCK(cs, return {int i = 1; return i;}); +//! +//! is int, the deduced type of: +//! +//! WITH_LOCK(cs, return {int j = 1; return (j);}); +//! +//! is &int, a reference to a local variable +//! +//! The above is detectable at compile-time with the -Wreturn-local-addr flag in +//! gcc and the -Wreturn-stack-address flag in clang, both enabled by default. +#define WITH_LOCK(cs, code) [&]() -> decltype(auto) { LOCK(cs); code; }() class CSemaphore { diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index d926f8d767..cf6009d591 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -80,8 +80,8 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) { const CChainParams& chainparams = Params(); auto connman = MakeUnique<CConnman>(0x1337, 0x1337); - auto peerLogic = std::make_unique<PeerManager>(chainparams, *connman, nullptr, *m_node.scheduler, - *m_node.chainman, *m_node.mempool, false); + auto peerLogic = PeerManager::make(chainparams, *connman, nullptr, *m_node.scheduler, + *m_node.chainman, *m_node.mempool, false); // Mock an outbound peer CAddress addr1(ip(0xa0b0c001), NODE_NONE); @@ -150,8 +150,8 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) { const CChainParams& chainparams = Params(); auto connman = MakeUnique<CConnmanTest>(0x1337, 0x1337); - auto peerLogic = std::make_unique<PeerManager>(chainparams, *connman, nullptr, *m_node.scheduler, - *m_node.chainman, *m_node.mempool, false); + auto peerLogic = PeerManager::make(chainparams, *connman, nullptr, *m_node.scheduler, + *m_node.chainman, *m_node.mempool, false); constexpr int max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS; CConnman::Options options; @@ -224,8 +224,8 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) const CChainParams& chainparams = Params(); auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); auto connman = MakeUnique<CConnman>(0x1337, 0x1337); - auto peerLogic = std::make_unique<PeerManager>(chainparams, *connman, banman.get(), *m_node.scheduler, - *m_node.chainman, *m_node.mempool, false); + auto peerLogic = PeerManager::make(chainparams, *connman, banman.get(), *m_node.scheduler, + *m_node.chainman, *m_node.mempool, false); banman->ClearBanned(); CAddress addr1(ip(0xa0b0c001), NODE_NONE); @@ -271,8 +271,8 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) const CChainParams& chainparams = Params(); auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); auto connman = MakeUnique<CConnman>(0x1337, 0x1337); - auto peerLogic = std::make_unique<PeerManager>(chainparams, *connman, banman.get(), *m_node.scheduler, - *m_node.chainman, *m_node.mempool, false); + auto peerLogic = PeerManager::make(chainparams, *connman, banman.get(), *m_node.scheduler, + *m_node.chainman, *m_node.mempool, false); banman->ClearBanned(); int64_t nStartTime = GetTime(); diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index af9080b5e9..1ea6b3d01d 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -45,83 +45,71 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman) } } while (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 11)) { - case 0: { - addr_man.Clear(); - break; - } - case 1: { - addr_man.ResolveCollisions(); - break; - } - case 2: { - (void)addr_man.SelectTriedCollision(); - break; - } - case 3: { - (void)addr_man.Select(fuzzed_data_provider.ConsumeBool()); - break; - } - case 4: { - (void)addr_man.GetAddr(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); - break; - } - case 5: { - const std::optional<CAddress> opt_address = ConsumeDeserializable<CAddress>(fuzzed_data_provider); - const std::optional<CNetAddr> opt_net_addr = ConsumeDeserializable<CNetAddr>(fuzzed_data_provider); - if (opt_address && opt_net_addr) { - addr_man.Add(*opt_address, *opt_net_addr, fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, 100000000)); - } - break; - } - case 6: { - std::vector<CAddress> addresses; - while (fuzzed_data_provider.ConsumeBool()) { + CallOneOf( + fuzzed_data_provider, + [&] { + addr_man.Clear(); + }, + [&] { + addr_man.ResolveCollisions(); + }, + [&] { + (void)addr_man.SelectTriedCollision(); + }, + [&] { + (void)addr_man.Select(fuzzed_data_provider.ConsumeBool()); + }, + [&] { + (void)addr_man.GetAddr(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); + }, + [&] { const std::optional<CAddress> opt_address = ConsumeDeserializable<CAddress>(fuzzed_data_provider); - if (!opt_address) { - break; + const std::optional<CNetAddr> opt_net_addr = ConsumeDeserializable<CNetAddr>(fuzzed_data_provider); + if (opt_address && opt_net_addr) { + addr_man.Add(*opt_address, *opt_net_addr, fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, 100000000)); } - addresses.push_back(*opt_address); - } - const std::optional<CNetAddr> opt_net_addr = ConsumeDeserializable<CNetAddr>(fuzzed_data_provider); - if (opt_net_addr) { - addr_man.Add(addresses, *opt_net_addr, fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, 100000000)); - } - break; - } - case 7: { - const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider); - if (opt_service) { - addr_man.Good(*opt_service, fuzzed_data_provider.ConsumeBool(), ConsumeTime(fuzzed_data_provider)); - } - break; - } - case 8: { - const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider); - if (opt_service) { - addr_man.Attempt(*opt_service, fuzzed_data_provider.ConsumeBool(), ConsumeTime(fuzzed_data_provider)); - } - break; - } - case 9: { - const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider); - if (opt_service) { - addr_man.Connected(*opt_service, ConsumeTime(fuzzed_data_provider)); - } - break; - } - case 10: { - const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider); - if (opt_service) { - addr_man.SetServices(*opt_service, ServiceFlags{fuzzed_data_provider.ConsumeIntegral<uint64_t>()}); - } - break; - } - case 11: { - (void)addr_man.Check(); - break; - } - } + }, + [&] { + std::vector<CAddress> addresses; + while (fuzzed_data_provider.ConsumeBool()) { + const std::optional<CAddress> opt_address = ConsumeDeserializable<CAddress>(fuzzed_data_provider); + if (!opt_address) { + break; + } + addresses.push_back(*opt_address); + } + const std::optional<CNetAddr> opt_net_addr = ConsumeDeserializable<CNetAddr>(fuzzed_data_provider); + if (opt_net_addr) { + addr_man.Add(addresses, *opt_net_addr, fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, 100000000)); + } + }, + [&] { + const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider); + if (opt_service) { + addr_man.Good(*opt_service, fuzzed_data_provider.ConsumeBool(), ConsumeTime(fuzzed_data_provider)); + } + }, + [&] { + const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider); + if (opt_service) { + addr_man.Attempt(*opt_service, fuzzed_data_provider.ConsumeBool(), ConsumeTime(fuzzed_data_provider)); + } + }, + [&] { + const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider); + if (opt_service) { + addr_man.Connected(*opt_service, ConsumeTime(fuzzed_data_provider)); + } + }, + [&] { + const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider); + if (opt_service) { + addr_man.SetServices(*opt_service, ServiceFlags{fuzzed_data_provider.ConsumeIntegral<uint64_t>()}); + } + }, + [&] { + (void)addr_man.Check(); + }); } (void)addr_man.size(); CDataStream data_stream(SER_NETWORK, PROTOCOL_VERSION); diff --git a/src/test/fuzz/autofile.cpp b/src/test/fuzz/autofile.cpp index eb3424ef28..9ecd172e19 100644 --- a/src/test/fuzz/autofile.cpp +++ b/src/test/fuzz/autofile.cpp @@ -21,43 +21,37 @@ FUZZ_TARGET(autofile) FuzzedAutoFileProvider fuzzed_auto_file_provider = ConsumeAutoFile(fuzzed_data_provider); CAutoFile auto_file = fuzzed_auto_file_provider.open(); while (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 5)) { - case 0: { - std::array<uint8_t, 4096> arr{}; - try { - auto_file.read((char*)arr.data(), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); - } catch (const std::ios_base::failure&) { - } - break; - } - case 1: { - const std::array<uint8_t, 4096> arr{}; - try { - auto_file.write((const char*)arr.data(), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); - } catch (const std::ios_base::failure&) { - } - break; - } - case 2: { - try { - auto_file.ignore(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); - } catch (const std::ios_base::failure&) { - } - break; - } - case 3: { - auto_file.fclose(); - break; - } - case 4: { - ReadFromStream(fuzzed_data_provider, auto_file); - break; - } - case 5: { - WriteToStream(fuzzed_data_provider, auto_file); - break; - } - } + CallOneOf( + fuzzed_data_provider, + [&] { + std::array<uint8_t, 4096> arr{}; + try { + auto_file.read((char*)arr.data(), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); + } catch (const std::ios_base::failure&) { + } + }, + [&] { + const std::array<uint8_t, 4096> arr{}; + try { + auto_file.write((const char*)arr.data(), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); + } catch (const std::ios_base::failure&) { + } + }, + [&] { + try { + auto_file.ignore(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); + } catch (const std::ios_base::failure&) { + } + }, + [&] { + auto_file.fclose(); + }, + [&] { + ReadFromStream(fuzzed_data_provider, auto_file); + }, + [&] { + WriteToStream(fuzzed_data_provider, auto_file); + }); } (void)auto_file.Get(); (void)auto_file.GetType(); diff --git a/src/test/fuzz/banman.cpp b/src/test/fuzz/banman.cpp index cf69fa0722..e703fa39c1 100644 --- a/src/test/fuzz/banman.cpp +++ b/src/test/fuzz/banman.cpp @@ -38,51 +38,43 @@ FUZZ_TARGET_INIT(banman, initialize_banman) { BanMan ban_man{banlist_file, nullptr, ConsumeBanTimeOffset(fuzzed_data_provider)}; while (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 11)) { - case 0: { - ban_man.Ban(ConsumeNetAddr(fuzzed_data_provider), - ConsumeBanTimeOffset(fuzzed_data_provider), fuzzed_data_provider.ConsumeBool()); - break; - } - case 1: { - ban_man.Ban(ConsumeSubNet(fuzzed_data_provider), - ConsumeBanTimeOffset(fuzzed_data_provider), fuzzed_data_provider.ConsumeBool()); - break; - } - case 2: { - ban_man.ClearBanned(); - break; - } - case 4: { - ban_man.IsBanned(ConsumeNetAddr(fuzzed_data_provider)); - break; - } - case 5: { - ban_man.IsBanned(ConsumeSubNet(fuzzed_data_provider)); - break; - } - case 6: { - ban_man.Unban(ConsumeNetAddr(fuzzed_data_provider)); - break; - } - case 7: { - ban_man.Unban(ConsumeSubNet(fuzzed_data_provider)); - break; - } - case 8: { - banmap_t banmap; - ban_man.GetBanned(banmap); - break; - } - case 9: { - ban_man.DumpBanlist(); - break; - } - case 11: { - ban_man.Discourage(ConsumeNetAddr(fuzzed_data_provider)); - break; - } - } + CallOneOf( + fuzzed_data_provider, + [&] { + ban_man.Ban(ConsumeNetAddr(fuzzed_data_provider), + ConsumeBanTimeOffset(fuzzed_data_provider), fuzzed_data_provider.ConsumeBool()); + }, + [&] { + ban_man.Ban(ConsumeSubNet(fuzzed_data_provider), + ConsumeBanTimeOffset(fuzzed_data_provider), fuzzed_data_provider.ConsumeBool()); + }, + [&] { + ban_man.ClearBanned(); + }, + [] {}, + [&] { + ban_man.IsBanned(ConsumeNetAddr(fuzzed_data_provider)); + }, + [&] { + ban_man.IsBanned(ConsumeSubNet(fuzzed_data_provider)); + }, + [&] { + ban_man.Unban(ConsumeNetAddr(fuzzed_data_provider)); + }, + [&] { + ban_man.Unban(ConsumeSubNet(fuzzed_data_provider)); + }, + [&] { + banmap_t banmap; + ban_man.GetBanned(banmap); + }, + [&] { + ban_man.DumpBanlist(); + }, + [] {}, + [&] { + ban_man.Discourage(ConsumeNetAddr(fuzzed_data_provider)); + }); } } fs::remove(banlist_file); diff --git a/src/test/fuzz/bloom_filter.cpp b/src/test/fuzz/bloom_filter.cpp index c0c66c564b..d43c182644 100644 --- a/src/test/fuzz/bloom_filter.cpp +++ b/src/test/fuzz/bloom_filter.cpp @@ -25,47 +25,43 @@ FUZZ_TARGET(bloom_filter) fuzzed_data_provider.ConsumeIntegral<unsigned int>(), static_cast<unsigned char>(fuzzed_data_provider.PickValueInArray({BLOOM_UPDATE_NONE, BLOOM_UPDATE_ALL, BLOOM_UPDATE_P2PUBKEY_ONLY, BLOOM_UPDATE_MASK}))}; while (fuzzed_data_provider.remaining_bytes() > 0) { - switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 3)) { - case 0: { - const std::vector<unsigned char> b = ConsumeRandomLengthByteVector(fuzzed_data_provider); - (void)bloom_filter.contains(b); - bloom_filter.insert(b); - const bool present = bloom_filter.contains(b); - assert(present); - break; - } - case 1: { - const std::optional<COutPoint> out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider); - if (!out_point) { - break; - } - (void)bloom_filter.contains(*out_point); - bloom_filter.insert(*out_point); - const bool present = bloom_filter.contains(*out_point); - assert(present); - break; - } - case 2: { - const std::optional<uint256> u256 = ConsumeDeserializable<uint256>(fuzzed_data_provider); - if (!u256) { - break; - } - (void)bloom_filter.contains(*u256); - bloom_filter.insert(*u256); - const bool present = bloom_filter.contains(*u256); - assert(present); - break; - } - case 3: { - const std::optional<CMutableTransaction> mut_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); - if (!mut_tx) { - break; - } - const CTransaction tx{*mut_tx}; - (void)bloom_filter.IsRelevantAndUpdate(tx); - break; - } - } + CallOneOf( + fuzzed_data_provider, + [&] { + const std::vector<unsigned char> b = ConsumeRandomLengthByteVector(fuzzed_data_provider); + (void)bloom_filter.contains(b); + bloom_filter.insert(b); + const bool present = bloom_filter.contains(b); + assert(present); + }, + [&] { + const std::optional<COutPoint> out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider); + if (!out_point) { + return; + } + (void)bloom_filter.contains(*out_point); + bloom_filter.insert(*out_point); + const bool present = bloom_filter.contains(*out_point); + assert(present); + }, + [&] { + const std::optional<uint256> u256 = ConsumeDeserializable<uint256>(fuzzed_data_provider); + if (!u256) { + return; + } + (void)bloom_filter.contains(*u256); + bloom_filter.insert(*u256); + const bool present = bloom_filter.contains(*u256); + assert(present); + }, + [&] { + const std::optional<CMutableTransaction> mut_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); + if (!mut_tx) { + return; + } + const CTransaction tx{*mut_tx}; + (void)bloom_filter.IsRelevantAndUpdate(tx); + }); (void)bloom_filter.IsWithinSizeConstraints(); } } diff --git a/src/test/fuzz/buffered_file.cpp b/src/test/fuzz/buffered_file.cpp index 23e197456a..3a1b2dbbe7 100644 --- a/src/test/fuzz/buffered_file.cpp +++ b/src/test/fuzz/buffered_file.cpp @@ -31,41 +31,36 @@ FUZZ_TARGET(buffered_file) if (opt_buffered_file && fuzzed_file != nullptr) { bool setpos_fail = false; while (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 4)) { - case 0: { - std::array<uint8_t, 4096> arr{}; - try { - opt_buffered_file->read((char*)arr.data(), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); - } catch (const std::ios_base::failure&) { - } - break; - } - case 1: { - opt_buffered_file->SetLimit(fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(0, 4096)); - break; - } - case 2: { - if (!opt_buffered_file->SetPos(fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(0, 4096))) { - setpos_fail = true; - } - break; - } - case 3: { - if (setpos_fail) { - // Calling FindByte(...) after a failed SetPos(...) call may result in an infinite loop. - break; - } - try { - opt_buffered_file->FindByte(fuzzed_data_provider.ConsumeIntegral<char>()); - } catch (const std::ios_base::failure&) { - } - break; - } - case 4: { - ReadFromStream(fuzzed_data_provider, *opt_buffered_file); - break; - } - } + CallOneOf( + fuzzed_data_provider, + [&] { + std::array<uint8_t, 4096> arr{}; + try { + opt_buffered_file->read((char*)arr.data(), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); + } catch (const std::ios_base::failure&) { + } + }, + [&] { + opt_buffered_file->SetLimit(fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(0, 4096)); + }, + [&] { + if (!opt_buffered_file->SetPos(fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(0, 4096))) { + setpos_fail = true; + } + }, + [&] { + if (setpos_fail) { + // Calling FindByte(...) after a failed SetPos(...) call may result in an infinite loop. + return; + } + try { + opt_buffered_file->FindByte(fuzzed_data_provider.ConsumeIntegral<char>()); + } catch (const std::ios_base::failure&) { + } + }, + [&] { + ReadFromStream(fuzzed_data_provider, *opt_buffered_file); + }); } opt_buffered_file->GetPos(); opt_buffered_file->GetType(); diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index 1ae421493e..12ef4b203f 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -50,103 +50,93 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view) Coin random_coin; CMutableTransaction random_mutable_transaction; while (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 9)) { - case 0: { - if (random_coin.IsSpent()) { - break; - } - Coin coin = random_coin; - bool expected_code_path = false; - const bool possible_overwrite = fuzzed_data_provider.ConsumeBool(); - try { - coins_view_cache.AddCoin(random_out_point, std::move(coin), possible_overwrite); - expected_code_path = true; - } catch (const std::logic_error& e) { - if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) { - assert(!possible_overwrite); + CallOneOf( + fuzzed_data_provider, + [&] { + if (random_coin.IsSpent()) { + return; + } + Coin coin = random_coin; + bool expected_code_path = false; + const bool possible_overwrite = fuzzed_data_provider.ConsumeBool(); + try { + coins_view_cache.AddCoin(random_out_point, std::move(coin), possible_overwrite); expected_code_path = true; + } catch (const std::logic_error& e) { + if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) { + assert(!possible_overwrite); + expected_code_path = true; + } } - } - assert(expected_code_path); - break; - } - case 1: { - (void)coins_view_cache.Flush(); - break; - } - case 2: { - coins_view_cache.SetBestBlock(ConsumeUInt256(fuzzed_data_provider)); - break; - } - case 3: { - Coin move_to; - (void)coins_view_cache.SpendCoin(random_out_point, fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr); - break; - } - case 4: { - coins_view_cache.Uncache(random_out_point); - break; - } - case 5: { - if (fuzzed_data_provider.ConsumeBool()) { - backend_coins_view = CCoinsView{}; - } - coins_view_cache.SetBackend(backend_coins_view); - break; - } - case 6: { - const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider); - if (!opt_out_point) { - break; - } - random_out_point = *opt_out_point; - break; - } - case 7: { - const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider); - if (!opt_coin) { - break; - } - random_coin = *opt_coin; - break; - } - case 8: { - const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); - if (!opt_mutable_transaction) { - break; - } - random_mutable_transaction = *opt_mutable_transaction; - break; - } - case 9: { - CCoinsMap coins_map; - while (fuzzed_data_provider.ConsumeBool()) { - CCoinsCacheEntry coins_cache_entry; - coins_cache_entry.flags = fuzzed_data_provider.ConsumeIntegral<unsigned char>(); + assert(expected_code_path); + }, + [&] { + (void)coins_view_cache.Flush(); + }, + [&] { + coins_view_cache.SetBestBlock(ConsumeUInt256(fuzzed_data_provider)); + }, + [&] { + Coin move_to; + (void)coins_view_cache.SpendCoin(random_out_point, fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr); + }, + [&] { + coins_view_cache.Uncache(random_out_point); + }, + [&] { if (fuzzed_data_provider.ConsumeBool()) { - coins_cache_entry.coin = random_coin; - } else { - const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider); - if (!opt_coin) { - break; + backend_coins_view = CCoinsView{}; + } + coins_view_cache.SetBackend(backend_coins_view); + }, + [&] { + const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider); + if (!opt_out_point) { + return; + } + random_out_point = *opt_out_point; + }, + [&] { + const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider); + if (!opt_coin) { + return; + } + random_coin = *opt_coin; + }, + [&] { + const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); + if (!opt_mutable_transaction) { + return; + } + random_mutable_transaction = *opt_mutable_transaction; + }, + [&] { + CCoinsMap coins_map; + while (fuzzed_data_provider.ConsumeBool()) { + CCoinsCacheEntry coins_cache_entry; + coins_cache_entry.flags = fuzzed_data_provider.ConsumeIntegral<unsigned char>(); + if (fuzzed_data_provider.ConsumeBool()) { + coins_cache_entry.coin = random_coin; + } else { + const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider); + if (!opt_coin) { + return; + } + coins_cache_entry.coin = *opt_coin; } - coins_cache_entry.coin = *opt_coin; + coins_map.emplace(random_out_point, std::move(coins_cache_entry)); } - coins_map.emplace(random_out_point, std::move(coins_cache_entry)); - } - bool expected_code_path = false; - try { - coins_view_cache.BatchWrite(coins_map, fuzzed_data_provider.ConsumeBool() ? ConsumeUInt256(fuzzed_data_provider) : coins_view_cache.GetBestBlock()); - expected_code_path = true; - } catch (const std::logic_error& e) { - if (e.what() == std::string{"FRESH flag misapplied to coin that exists in parent cache"}) { + bool expected_code_path = false; + try { + coins_view_cache.BatchWrite(coins_map, fuzzed_data_provider.ConsumeBool() ? ConsumeUInt256(fuzzed_data_provider) : coins_view_cache.GetBestBlock()); expected_code_path = true; + } catch (const std::logic_error& e) { + if (e.what() == std::string{"FRESH flag misapplied to coin that exists in parent cache"}) { + expected_code_path = true; + } } - } - assert(expected_code_path); - break; - } - } + assert(expected_code_path); + }); } { @@ -199,97 +189,90 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view) } if (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 6)) { - case 0: { - const CTransaction transaction{random_mutable_transaction}; - bool is_spent = false; - for (const CTxOut& tx_out : transaction.vout) { - if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) { - is_spent = true; + CallOneOf( + fuzzed_data_provider, + [&] { + const CTransaction transaction{random_mutable_transaction}; + bool is_spent = false; + for (const CTxOut& tx_out : transaction.vout) { + if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) { + is_spent = true; + } + } + if (is_spent) { + // Avoid: + // coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed. + return; } - } - if (is_spent) { - // Avoid: - // coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed. - break; - } - bool expected_code_path = false; - const int height = fuzzed_data_provider.ConsumeIntegral<int>(); - const bool possible_overwrite = fuzzed_data_provider.ConsumeBool(); - try { - AddCoins(coins_view_cache, transaction, height, possible_overwrite); - expected_code_path = true; - } catch (const std::logic_error& e) { - if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) { - assert(!possible_overwrite); + bool expected_code_path = false; + const int height = fuzzed_data_provider.ConsumeIntegral<int>(); + const bool possible_overwrite = fuzzed_data_provider.ConsumeBool(); + try { + AddCoins(coins_view_cache, transaction, height, possible_overwrite); expected_code_path = true; + } catch (const std::logic_error& e) { + if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) { + assert(!possible_overwrite); + expected_code_path = true; + } } - } - assert(expected_code_path); - break; - } - case 1: { - (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache, false); - (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache, true); - break; - } - case 2: { - TxValidationState state; - CAmount tx_fee_out; - const CTransaction transaction{random_mutable_transaction}; - if (ContainsSpentInput(transaction, coins_view_cache)) { - // Avoid: - // consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed. - break; - } - try { - (void)Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out); - assert(MoneyRange(tx_fee_out)); - } catch (const std::runtime_error&) { - } - break; - } - case 3: { - const CTransaction transaction{random_mutable_transaction}; - if (ContainsSpentInput(transaction, coins_view_cache)) { - // Avoid: - // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed. - break; - } - (void)GetP2SHSigOpCount(transaction, coins_view_cache); - break; - } - case 4: { - const CTransaction transaction{random_mutable_transaction}; - if (ContainsSpentInput(transaction, coins_view_cache)) { - // Avoid: - // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed. - break; - } - const int flags = fuzzed_data_provider.ConsumeIntegral<int>(); - if (!transaction.vin.empty() && (flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) { - // Avoid: - // script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness *, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed. - break; - } - (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags); - break; - } - case 5: { - CCoinsStats stats; - bool expected_code_path = false; - try { - (void)GetUTXOStats(&coins_view_cache, stats, CoinStatsHashType::HASH_SERIALIZED); - } catch (const std::logic_error&) { - expected_code_path = true; - } - assert(expected_code_path); - break; - } - case 6: { - (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache); - break; - } - } + assert(expected_code_path); + }, + [&] { + (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache, false); + (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache, true); + }, + [&] { + TxValidationState state; + CAmount tx_fee_out; + const CTransaction transaction{random_mutable_transaction}; + if (ContainsSpentInput(transaction, coins_view_cache)) { + // Avoid: + // consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed. + return; + } + try { + (void)Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out); + assert(MoneyRange(tx_fee_out)); + } catch (const std::runtime_error&) { + } + }, + [&] { + const CTransaction transaction{random_mutable_transaction}; + if (ContainsSpentInput(transaction, coins_view_cache)) { + // Avoid: + // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed. + return; + } + (void)GetP2SHSigOpCount(transaction, coins_view_cache); + }, + [&] { + const CTransaction transaction{random_mutable_transaction}; + if (ContainsSpentInput(transaction, coins_view_cache)) { + // Avoid: + // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed. + return; + } + const int flags = fuzzed_data_provider.ConsumeIntegral<int>(); + if (!transaction.vin.empty() && (flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) { + // Avoid: + // script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness *, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed. + return; + } + (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags); + }, + [&] { + CCoinsStats stats; + bool expected_code_path = false; + try { + (void)GetUTXOStats(&coins_view_cache, stats, CoinStatsHashType::HASH_SERIALIZED); + } catch (const std::logic_error&) { + expected_code_path = true; + } + assert(expected_code_path); + }, + [&] { + (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache); + }); } } diff --git a/src/test/fuzz/connman.cpp b/src/test/fuzz/connman.cpp index fdf51d8558..7950213bd5 100644 --- a/src/test/fuzz/connman.cpp +++ b/src/test/fuzz/connman.cpp @@ -32,108 +32,104 @@ FUZZ_TARGET_INIT(connman, initialize_connman) CSubNet random_subnet; std::string random_string; while (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 27)) { - case 0: - random_address = ConsumeAddress(fuzzed_data_provider); - break; - case 1: - random_netaddr = ConsumeNetAddr(fuzzed_data_provider); - break; - case 2: - random_service = ConsumeService(fuzzed_data_provider); - break; - case 3: - random_subnet = ConsumeSubNet(fuzzed_data_provider); - break; - case 4: - random_string = fuzzed_data_provider.ConsumeRandomLengthString(64); - break; - case 5: { - std::vector<CAddress> addresses; - while (fuzzed_data_provider.ConsumeBool()) { - addresses.push_back(ConsumeAddress(fuzzed_data_provider)); - } - // Limit nTimePenalty to int32_t to avoid signed integer overflow - (void)connman.AddNewAddresses(addresses, ConsumeAddress(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<int32_t>()); - break; - } - case 6: - connman.AddNode(random_string); - break; - case 7: - connman.CheckIncomingNonce(fuzzed_data_provider.ConsumeIntegral<uint64_t>()); - break; - case 8: - connman.DisconnectNode(fuzzed_data_provider.ConsumeIntegral<NodeId>()); - break; - case 9: - connman.DisconnectNode(random_netaddr); - break; - case 10: - connman.DisconnectNode(random_string); - break; - case 11: - connman.DisconnectNode(random_subnet); - break; - case 12: - connman.ForEachNode([](auto) {}); - break; - case 13: - connman.ForEachNodeThen([](auto) {}, []() {}); - break; - case 14: - (void)connman.ForNode(fuzzed_data_provider.ConsumeIntegral<NodeId>(), [&](auto) { return fuzzed_data_provider.ConsumeBool(); }); - break; - case 15: - (void)connman.GetAddresses(fuzzed_data_provider.ConsumeIntegral<size_t>(), fuzzed_data_provider.ConsumeIntegral<size_t>()); - break; - case 16: { - (void)connman.GetAddresses(random_node, fuzzed_data_provider.ConsumeIntegral<size_t>(), fuzzed_data_provider.ConsumeIntegral<size_t>()); - break; - } - case 17: - (void)connman.GetDeterministicRandomizer(fuzzed_data_provider.ConsumeIntegral<uint64_t>()); - break; - case 18: - (void)connman.GetNodeCount(fuzzed_data_provider.PickValueInArray({CConnman::CONNECTIONS_NONE, CConnman::CONNECTIONS_IN, CConnman::CONNECTIONS_OUT, CConnman::CONNECTIONS_ALL})); - break; - case 19: - connman.MarkAddressGood(random_address); - break; - case 20: - (void)connman.OutboundTargetReached(fuzzed_data_provider.ConsumeBool()); - break; - case 21: - // Limit now to int32_t to avoid signed integer overflow - (void)connman.PoissonNextSendInbound(fuzzed_data_provider.ConsumeIntegral<int32_t>(), fuzzed_data_provider.ConsumeIntegral<int>()); - break; - case 22: { - CSerializedNetMsg serialized_net_msg; - serialized_net_msg.m_type = fuzzed_data_provider.ConsumeRandomLengthString(CMessageHeader::COMMAND_SIZE); - serialized_net_msg.data = ConsumeRandomLengthByteVector(fuzzed_data_provider); - connman.PushMessage(&random_node, std::move(serialized_net_msg)); - break; - } - case 23: - connman.RemoveAddedNode(random_string); - break; - case 24: { - const std::vector<bool> asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider); - if (SanityCheckASMap(asmap)) { - connman.SetAsmap(asmap); - } - break; - } - case 25: - connman.SetNetworkActive(fuzzed_data_provider.ConsumeBool()); - break; - case 26: - connman.SetServices(random_service, ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS)); - break; - case 27: - connman.SetTryNewOutboundPeer(fuzzed_data_provider.ConsumeBool()); - break; - } + CallOneOf( + fuzzed_data_provider, + [&] { + random_address = ConsumeAddress(fuzzed_data_provider); + }, + [&] { + random_netaddr = ConsumeNetAddr(fuzzed_data_provider); + }, + [&] { + random_service = ConsumeService(fuzzed_data_provider); + }, + [&] { + random_subnet = ConsumeSubNet(fuzzed_data_provider); + }, + [&] { + random_string = fuzzed_data_provider.ConsumeRandomLengthString(64); + }, + [&] { + std::vector<CAddress> addresses; + while (fuzzed_data_provider.ConsumeBool()) { + addresses.push_back(ConsumeAddress(fuzzed_data_provider)); + } + // Limit nTimePenalty to int32_t to avoid signed integer overflow + (void)connman.AddNewAddresses(addresses, ConsumeAddress(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<int32_t>()); + }, + [&] { + connman.AddNode(random_string); + }, + [&] { + connman.CheckIncomingNonce(fuzzed_data_provider.ConsumeIntegral<uint64_t>()); + }, + [&] { + connman.DisconnectNode(fuzzed_data_provider.ConsumeIntegral<NodeId>()); + }, + [&] { + connman.DisconnectNode(random_netaddr); + }, + [&] { + connman.DisconnectNode(random_string); + }, + [&] { + connman.DisconnectNode(random_subnet); + }, + [&] { + connman.ForEachNode([](auto) {}); + }, + [&] { + connman.ForEachNodeThen([](auto) {}, []() {}); + }, + [&] { + (void)connman.ForNode(fuzzed_data_provider.ConsumeIntegral<NodeId>(), [&](auto) { return fuzzed_data_provider.ConsumeBool(); }); + }, + [&] { + (void)connman.GetAddresses(fuzzed_data_provider.ConsumeIntegral<size_t>(), fuzzed_data_provider.ConsumeIntegral<size_t>()); + }, + [&] { + (void)connman.GetAddresses(random_node, fuzzed_data_provider.ConsumeIntegral<size_t>(), fuzzed_data_provider.ConsumeIntegral<size_t>()); + }, + [&] { + (void)connman.GetDeterministicRandomizer(fuzzed_data_provider.ConsumeIntegral<uint64_t>()); + }, + [&] { + (void)connman.GetNodeCount(fuzzed_data_provider.PickValueInArray({CConnman::CONNECTIONS_NONE, CConnman::CONNECTIONS_IN, CConnman::CONNECTIONS_OUT, CConnman::CONNECTIONS_ALL})); + }, + [&] { + connman.MarkAddressGood(random_address); + }, + [&] { + (void)connman.OutboundTargetReached(fuzzed_data_provider.ConsumeBool()); + }, + [&] { + // Limit now to int32_t to avoid signed integer overflow + (void)connman.PoissonNextSendInbound(fuzzed_data_provider.ConsumeIntegral<int32_t>(), fuzzed_data_provider.ConsumeIntegral<int>()); + }, + [&] { + CSerializedNetMsg serialized_net_msg; + serialized_net_msg.m_type = fuzzed_data_provider.ConsumeRandomLengthString(CMessageHeader::COMMAND_SIZE); + serialized_net_msg.data = ConsumeRandomLengthByteVector(fuzzed_data_provider); + connman.PushMessage(&random_node, std::move(serialized_net_msg)); + }, + [&] { + connman.RemoveAddedNode(random_string); + }, + [&] { + const std::vector<bool> asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider); + if (SanityCheckASMap(asmap)) { + connman.SetAsmap(asmap); + } + }, + [&] { + connman.SetNetworkActive(fuzzed_data_provider.ConsumeBool()); + }, + [&] { + connman.SetServices(random_service, ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS)); + }, + [&] { + connman.SetTryNewOutboundPeer(fuzzed_data_provider.ConsumeBool()); + }); } (void)connman.GetAddedNodeInfo(); (void)connman.GetExtraFullOutboundCount(); diff --git a/src/test/fuzz/crypto.cpp b/src/test/fuzz/crypto.cpp index 9668b84e7b..c2bb3a1a4e 100644 --- a/src/test/fuzz/crypto.cpp +++ b/src/test/fuzz/crypto.cpp @@ -39,109 +39,95 @@ FUZZ_TARGET(crypto) MuHash3072 muhash; while (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 2)) { - case 0: { - if (fuzzed_data_provider.ConsumeBool()) { - data = ConsumeRandomLengthByteVector(fuzzed_data_provider); - if (data.empty()) { - data.resize(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 4096), fuzzed_data_provider.ConsumeIntegral<uint8_t>()); + CallOneOf( + fuzzed_data_provider, + [&] { + if (fuzzed_data_provider.ConsumeBool()) { + data = ConsumeRandomLengthByteVector(fuzzed_data_provider); + if (data.empty()) { + data.resize(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 4096), fuzzed_data_provider.ConsumeIntegral<uint8_t>()); + } } - } - (void)hash160.Write(data); - (void)hash256.Write(data); - (void)hmac_sha256.Write(data.data(), data.size()); - (void)hmac_sha512.Write(data.data(), data.size()); - (void)ripemd160.Write(data.data(), data.size()); - (void)sha1.Write(data.data(), data.size()); - (void)sha256.Write(data.data(), data.size()); - (void)sha3.Write(data); - (void)sha512.Write(data.data(), data.size()); - (void)sip_hasher.Write(data.data(), data.size()); + (void)hash160.Write(data); + (void)hash256.Write(data); + (void)hmac_sha256.Write(data.data(), data.size()); + (void)hmac_sha512.Write(data.data(), data.size()); + (void)ripemd160.Write(data.data(), data.size()); + (void)sha1.Write(data.data(), data.size()); + (void)sha256.Write(data.data(), data.size()); + (void)sha3.Write(data); + (void)sha512.Write(data.data(), data.size()); + (void)sip_hasher.Write(data.data(), data.size()); - (void)Hash(data); - (void)Hash160(data); - (void)sha512.Size(); + (void)Hash(data); + (void)Hash160(data); + (void)sha512.Size(); - if (fuzzed_data_provider.ConsumeBool()) { - muhash *= MuHash3072(data); - } else { - muhash /= MuHash3072(data); - } - break; - } - case 1: { - (void)hash160.Reset(); - (void)hash256.Reset(); - (void)ripemd160.Reset(); - (void)sha1.Reset(); - (void)sha256.Reset(); - (void)sha3.Reset(); - (void)sha512.Reset(); - muhash = MuHash3072(); - break; - } - case 2: { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 10)) { - case 0: { - data.resize(CHash160::OUTPUT_SIZE); - hash160.Finalize(data); - break; - } - case 1: { - data.resize(CHash256::OUTPUT_SIZE); - hash256.Finalize(data); - break; - } - case 2: { - data.resize(CHMAC_SHA256::OUTPUT_SIZE); - hmac_sha256.Finalize(data.data()); - break; - } - case 3: { - data.resize(CHMAC_SHA512::OUTPUT_SIZE); - hmac_sha512.Finalize(data.data()); - break; - } - case 4: { - data.resize(CRIPEMD160::OUTPUT_SIZE); - ripemd160.Finalize(data.data()); - break; - } - case 5: { - data.resize(CSHA1::OUTPUT_SIZE); - sha1.Finalize(data.data()); - break; - } - case 6: { - data.resize(CSHA256::OUTPUT_SIZE); - sha256.Finalize(data.data()); - break; - } - case 7: { - data.resize(CSHA512::OUTPUT_SIZE); - sha512.Finalize(data.data()); - break; - } - case 8: { - data.resize(1); - data[0] = sip_hasher.Finalize() % 256; - break; - } - case 9: { - data.resize(SHA3_256::OUTPUT_SIZE); - sha3.Finalize(data); - break; - } - case 10: { - uint256 out; - muhash.Finalize(out); - break; - } - } - break; - } - } + if (fuzzed_data_provider.ConsumeBool()) { + muhash *= MuHash3072(data); + } else { + muhash /= MuHash3072(data); + } + }, + [&] { + (void)hash160.Reset(); + (void)hash256.Reset(); + (void)ripemd160.Reset(); + (void)sha1.Reset(); + (void)sha256.Reset(); + (void)sha3.Reset(); + (void)sha512.Reset(); + muhash = MuHash3072(); + }, + [&] { + CallOneOf( + fuzzed_data_provider, + [&] { + data.resize(CHash160::OUTPUT_SIZE); + hash160.Finalize(data); + }, + [&] { + data.resize(CHash256::OUTPUT_SIZE); + hash256.Finalize(data); + }, + [&] { + data.resize(CHMAC_SHA256::OUTPUT_SIZE); + hmac_sha256.Finalize(data.data()); + }, + [&] { + data.resize(CHMAC_SHA512::OUTPUT_SIZE); + hmac_sha512.Finalize(data.data()); + }, + [&] { + data.resize(CRIPEMD160::OUTPUT_SIZE); + ripemd160.Finalize(data.data()); + }, + [&] { + data.resize(CSHA1::OUTPUT_SIZE); + sha1.Finalize(data.data()); + }, + [&] { + data.resize(CSHA256::OUTPUT_SIZE); + sha256.Finalize(data.data()); + }, + [&] { + data.resize(CSHA512::OUTPUT_SIZE); + sha512.Finalize(data.data()); + }, + [&] { + data.resize(1); + data[0] = sip_hasher.Finalize() % 256; + }, + [&] { + data.resize(SHA3_256::OUTPUT_SIZE); + sha3.Finalize(data); + }, + [&] { + uint256 out; + muhash.Finalize(out); + }); + }); } if (fuzzed_data_provider.ConsumeBool()) { uint64_t state[25]; diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp index d751466f11..bb8dd4594f 100644 --- a/src/test/fuzz/crypto_chacha20.cpp +++ b/src/test/fuzz/crypto_chacha20.cpp @@ -20,31 +20,26 @@ FUZZ_TARGET(crypto_chacha20) chacha20 = ChaCha20{key.data(), key.size()}; } while (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 4)) { - case 0: { - const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(16, 32)); - chacha20.SetKey(key.data(), key.size()); - break; - } - case 1: { - chacha20.SetIV(fuzzed_data_provider.ConsumeIntegral<uint64_t>()); - break; - } - case 2: { - chacha20.Seek(fuzzed_data_provider.ConsumeIntegral<uint64_t>()); - break; - } - case 3: { - std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); - chacha20.Keystream(output.data(), output.size()); - break; - } - case 4: { - std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); - const std::vector<uint8_t> input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size()); - chacha20.Crypt(input.data(), output.data(), input.size()); - break; - } - } + CallOneOf( + fuzzed_data_provider, + [&] { + const std::vector<unsigned char> key = ConsumeFixedLengthByteVector(fuzzed_data_provider, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(16, 32)); + chacha20.SetKey(key.data(), key.size()); + }, + [&] { + chacha20.SetIV(fuzzed_data_provider.ConsumeIntegral<uint64_t>()); + }, + [&] { + chacha20.Seek(fuzzed_data_provider.ConsumeIntegral<uint64_t>()); + }, + [&] { + std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); + chacha20.Keystream(output.data(), output.size()); + }, + [&] { + std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); + const std::vector<uint8_t> input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size()); + chacha20.Crypt(input.data(), output.data(), input.size()); + }); } } diff --git a/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp b/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp index 631af9c70d..1f122082b2 100644 --- a/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp +++ b/src/test/fuzz/crypto_chacha20_poly1305_aead.cpp @@ -29,44 +29,37 @@ FUZZ_TARGET(crypto_chacha20_poly1305_aead) std::vector<uint8_t> out(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); bool is_encrypt = fuzzed_data_provider.ConsumeBool(); while (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 6)) { - case 0: { - buffer_size = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(64, 4096); - in = std::vector<uint8_t>(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); - out = std::vector<uint8_t>(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); - break; - } - case 1: { - (void)aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffer_size, is_encrypt); - break; - } - case 2: { - uint32_t len = 0; - const bool ok = aead.GetLength(&len, seqnr_aad, aad_pos, in.data()); - assert(ok); - break; - } - case 3: { - seqnr_payload += 1; - aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN; - if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) { - aad_pos = 0; - seqnr_aad += 1; - } - break; - } - case 4: { - seqnr_payload = fuzzed_data_provider.ConsumeIntegral<int>(); - break; - } - case 5: { - seqnr_aad = fuzzed_data_provider.ConsumeIntegral<int>(); - break; - } - case 6: { - is_encrypt = fuzzed_data_provider.ConsumeBool(); - break; - } - } + CallOneOf( + fuzzed_data_provider, + [&] { + buffer_size = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(64, 4096); + in = std::vector<uint8_t>(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); + out = std::vector<uint8_t>(buffer_size + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); + }, + [&] { + (void)aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffer_size, is_encrypt); + }, + [&] { + uint32_t len = 0; + const bool ok = aead.GetLength(&len, seqnr_aad, aad_pos, in.data()); + assert(ok); + }, + [&] { + seqnr_payload += 1; + aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN; + if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) { + aad_pos = 0; + seqnr_aad += 1; + } + }, + [&] { + seqnr_payload = fuzzed_data_provider.ConsumeIntegral<int>(); + }, + [&] { + seqnr_aad = fuzzed_data_provider.ConsumeIntegral<int>(); + }, + [&] { + is_encrypt = fuzzed_data_provider.ConsumeBool(); + }); } } diff --git a/src/test/fuzz/merkleblock.cpp b/src/test/fuzz/merkleblock.cpp index 15bcfab3ad..23e0baa564 100644 --- a/src/test/fuzz/merkleblock.cpp +++ b/src/test/fuzz/merkleblock.cpp @@ -17,33 +17,31 @@ FUZZ_TARGET(merkleblock) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); CPartialMerkleTree partial_merkle_tree; - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 1)) { - case 0: { - const std::optional<CPartialMerkleTree> opt_partial_merkle_tree = ConsumeDeserializable<CPartialMerkleTree>(fuzzed_data_provider); - if (opt_partial_merkle_tree) { - partial_merkle_tree = *opt_partial_merkle_tree; - } - break; - } - case 1: { - CMerkleBlock merkle_block; - const std::optional<CBlock> opt_block = ConsumeDeserializable<CBlock>(fuzzed_data_provider); - CBloomFilter bloom_filter; - std::set<uint256> txids; - if (opt_block && !opt_block->vtx.empty()) { - if (fuzzed_data_provider.ConsumeBool()) { - merkle_block = CMerkleBlock{*opt_block, bloom_filter}; - } else if (fuzzed_data_provider.ConsumeBool()) { - while (fuzzed_data_provider.ConsumeBool()) { - txids.insert(ConsumeUInt256(fuzzed_data_provider)); + CallOneOf( + fuzzed_data_provider, + [&] { + const std::optional<CPartialMerkleTree> opt_partial_merkle_tree = ConsumeDeserializable<CPartialMerkleTree>(fuzzed_data_provider); + if (opt_partial_merkle_tree) { + partial_merkle_tree = *opt_partial_merkle_tree; + } + }, + [&] { + CMerkleBlock merkle_block; + const std::optional<CBlock> opt_block = ConsumeDeserializable<CBlock>(fuzzed_data_provider); + CBloomFilter bloom_filter; + std::set<uint256> txids; + if (opt_block && !opt_block->vtx.empty()) { + if (fuzzed_data_provider.ConsumeBool()) { + merkle_block = CMerkleBlock{*opt_block, bloom_filter}; + } else if (fuzzed_data_provider.ConsumeBool()) { + while (fuzzed_data_provider.ConsumeBool()) { + txids.insert(ConsumeUInt256(fuzzed_data_provider)); + } + merkle_block = CMerkleBlock{*opt_block, txids}; } - merkle_block = CMerkleBlock{*opt_block, txids}; } - } - partial_merkle_tree = merkle_block.txn; - break; - } - } + partial_merkle_tree = merkle_block.txn; + }); (void)partial_merkle_tree.GetNumTransactions(); std::vector<uint256> matches; std::vector<unsigned int> indices; diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp index 3ca921b5cf..31b99600ef 100644 --- a/src/test/fuzz/net.cpp +++ b/src/test/fuzz/net.cpp @@ -32,85 +32,74 @@ FUZZ_TARGET_INIT(net, initialize_net) CNode node{ConsumeNode(fuzzed_data_provider)}; node.SetCommonVersion(fuzzed_data_provider.ConsumeIntegral<int>()); while (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 10)) { - case 0: { - node.CloseSocketDisconnect(); - break; - } - case 1: { - node.MaybeSetAddrName(fuzzed_data_provider.ConsumeRandomLengthString(32)); - break; - } - case 2: { - const std::vector<bool> asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider); - if (!SanityCheckASMap(asmap)) { - break; - } - CNodeStats stats; - node.copyStats(stats, asmap); - break; - } - case 3: { - const CNode* add_ref_node = node.AddRef(); - assert(add_ref_node == &node); - break; - } - case 4: { - if (node.GetRefCount() > 0) { - node.Release(); - } - break; - } - case 5: { - if (node.m_addr_known == nullptr) { - break; - } - const std::optional<CAddress> addr_opt = ConsumeDeserializable<CAddress>(fuzzed_data_provider); - if (!addr_opt) { - break; - } - node.AddAddressKnown(*addr_opt); - break; - } - case 6: { - if (node.m_addr_known == nullptr) { - break; - } - const std::optional<CAddress> addr_opt = ConsumeDeserializable<CAddress>(fuzzed_data_provider); - if (!addr_opt) { - break; - } - FastRandomContext fast_random_context{ConsumeUInt256(fuzzed_data_provider)}; - node.PushAddress(*addr_opt, fast_random_context); - break; - } - case 7: { - const std::optional<CInv> inv_opt = ConsumeDeserializable<CInv>(fuzzed_data_provider); - if (!inv_opt) { - break; - } - node.AddKnownTx(inv_opt->hash); - break; - } - case 8: { - node.PushTxInventory(ConsumeUInt256(fuzzed_data_provider)); - break; - } - case 9: { - const std::optional<CService> service_opt = ConsumeDeserializable<CService>(fuzzed_data_provider); - if (!service_opt) { - break; - } - node.SetAddrLocal(*service_opt); - break; - } - case 10: { - const std::vector<uint8_t> b = ConsumeRandomLengthByteVector(fuzzed_data_provider); - bool complete; - node.ReceiveMsgBytes(b, complete); - break; - } - } + CallOneOf( + fuzzed_data_provider, + [&] { + node.CloseSocketDisconnect(); + }, + [&] { + node.MaybeSetAddrName(fuzzed_data_provider.ConsumeRandomLengthString(32)); + }, + [&] { + const std::vector<bool> asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider); + if (!SanityCheckASMap(asmap)) { + return; + } + CNodeStats stats; + node.copyStats(stats, asmap); + }, + [&] { + const CNode* add_ref_node = node.AddRef(); + assert(add_ref_node == &node); + }, + [&] { + if (node.GetRefCount() > 0) { + node.Release(); + } + }, + [&] { + if (node.m_addr_known == nullptr) { + return; + } + const std::optional<CAddress> addr_opt = ConsumeDeserializable<CAddress>(fuzzed_data_provider); + if (!addr_opt) { + return; + } + node.AddAddressKnown(*addr_opt); + }, + [&] { + if (node.m_addr_known == nullptr) { + return; + } + const std::optional<CAddress> addr_opt = ConsumeDeserializable<CAddress>(fuzzed_data_provider); + if (!addr_opt) { + return; + } + FastRandomContext fast_random_context{ConsumeUInt256(fuzzed_data_provider)}; + node.PushAddress(*addr_opt, fast_random_context); + }, + [&] { + const std::optional<CInv> inv_opt = ConsumeDeserializable<CInv>(fuzzed_data_provider); + if (!inv_opt) { + return; + } + node.AddKnownTx(inv_opt->hash); + }, + [&] { + node.PushTxInventory(ConsumeUInt256(fuzzed_data_provider)); + }, + [&] { + const std::optional<CService> service_opt = ConsumeDeserializable<CService>(fuzzed_data_provider); + if (!service_opt) { + return; + } + node.SetAddrLocal(*service_opt); + }, + [&] { + const std::vector<uint8_t> b = ConsumeRandomLengthByteVector(fuzzed_data_provider); + bool complete; + node.ReceiveMsgBytes(b, complete); + }); } (void)node.GetAddrLocal(); diff --git a/src/test/fuzz/policy_estimator.cpp b/src/test/fuzz/policy_estimator.cpp index 35cfc5230b..0393491e4b 100644 --- a/src/test/fuzz/policy_estimator.cpp +++ b/src/test/fuzz/policy_estimator.cpp @@ -24,46 +24,42 @@ FUZZ_TARGET_INIT(policy_estimator, initialize_policy_estimator) FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); CBlockPolicyEstimator block_policy_estimator; while (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 3)) { - case 0: { - const std::optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); - if (!mtx) { - break; - } - const CTransaction tx{*mtx}; - block_policy_estimator.processTransaction(ConsumeTxMemPoolEntry(fuzzed_data_provider, tx), fuzzed_data_provider.ConsumeBool()); - if (fuzzed_data_provider.ConsumeBool()) { - (void)block_policy_estimator.removeTx(tx.GetHash(), /* inBlock */ fuzzed_data_provider.ConsumeBool()); - } - break; - } - case 1: { - std::vector<CTxMemPoolEntry> mempool_entries; - while (fuzzed_data_provider.ConsumeBool()) { + CallOneOf( + fuzzed_data_provider, + [&] { const std::optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); if (!mtx) { - break; + return; } const CTransaction tx{*mtx}; - mempool_entries.push_back(ConsumeTxMemPoolEntry(fuzzed_data_provider, tx)); - } - std::vector<const CTxMemPoolEntry*> ptrs; - ptrs.reserve(mempool_entries.size()); - for (const CTxMemPoolEntry& mempool_entry : mempool_entries) { - ptrs.push_back(&mempool_entry); - } - block_policy_estimator.processBlock(fuzzed_data_provider.ConsumeIntegral<unsigned int>(), ptrs); - break; - } - case 2: { - (void)block_policy_estimator.removeTx(ConsumeUInt256(fuzzed_data_provider), /* inBlock */ fuzzed_data_provider.ConsumeBool()); - break; - } - case 3: { - block_policy_estimator.FlushUnconfirmed(); - break; - } - } + block_policy_estimator.processTransaction(ConsumeTxMemPoolEntry(fuzzed_data_provider, tx), fuzzed_data_provider.ConsumeBool()); + if (fuzzed_data_provider.ConsumeBool()) { + (void)block_policy_estimator.removeTx(tx.GetHash(), /* inBlock */ fuzzed_data_provider.ConsumeBool()); + } + }, + [&] { + std::vector<CTxMemPoolEntry> mempool_entries; + while (fuzzed_data_provider.ConsumeBool()) { + const std::optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); + if (!mtx) { + break; + } + const CTransaction tx{*mtx}; + mempool_entries.push_back(ConsumeTxMemPoolEntry(fuzzed_data_provider, tx)); + } + std::vector<const CTxMemPoolEntry*> ptrs; + ptrs.reserve(mempool_entries.size()); + for (const CTxMemPoolEntry& mempool_entry : mempool_entries) { + ptrs.push_back(&mempool_entry); + } + block_policy_estimator.processBlock(fuzzed_data_provider.ConsumeIntegral<unsigned int>(), ptrs); + }, + [&] { + (void)block_policy_estimator.removeTx(ConsumeUInt256(fuzzed_data_provider), /* inBlock */ fuzzed_data_provider.ConsumeBool()); + }, + [&] { + block_policy_estimator.FlushUnconfirmed(); + }); (void)block_policy_estimator.estimateFee(fuzzed_data_provider.ConsumeIntegral<int>()); EstimationResult result; (void)block_policy_estimator.estimateRawFee(fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeFloatingPoint<double>(), fuzzed_data_provider.PickValueInArray(ALL_FEE_ESTIMATE_HORIZONS), fuzzed_data_provider.ConsumeBool() ? &result : nullptr); diff --git a/src/test/fuzz/rolling_bloom_filter.cpp b/src/test/fuzz/rolling_bloom_filter.cpp index 6087ee964a..2a08b45aa3 100644 --- a/src/test/fuzz/rolling_bloom_filter.cpp +++ b/src/test/fuzz/rolling_bloom_filter.cpp @@ -22,29 +22,27 @@ FUZZ_TARGET(rolling_bloom_filter) fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, 1000), 0.999 / fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, std::numeric_limits<unsigned int>::max())}; while (fuzzed_data_provider.remaining_bytes() > 0) { - switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 2)) { - case 0: { - const std::vector<unsigned char> b = ConsumeRandomLengthByteVector(fuzzed_data_provider); - (void)rolling_bloom_filter.contains(b); - rolling_bloom_filter.insert(b); - const bool present = rolling_bloom_filter.contains(b); - assert(present); - break; - } - case 1: { - const std::optional<uint256> u256 = ConsumeDeserializable<uint256>(fuzzed_data_provider); - if (!u256) { - break; - } - (void)rolling_bloom_filter.contains(*u256); - rolling_bloom_filter.insert(*u256); - const bool present = rolling_bloom_filter.contains(*u256); - assert(present); - break; - } - case 2: - rolling_bloom_filter.reset(); - break; - } + CallOneOf( + fuzzed_data_provider, + [&] { + const std::vector<unsigned char> b = ConsumeRandomLengthByteVector(fuzzed_data_provider); + (void)rolling_bloom_filter.contains(b); + rolling_bloom_filter.insert(b); + const bool present = rolling_bloom_filter.contains(b); + assert(present); + }, + [&] { + const std::optional<uint256> u256 = ConsumeDeserializable<uint256>(fuzzed_data_provider); + if (!u256) { + return; + } + (void)rolling_bloom_filter.contains(*u256); + rolling_bloom_filter.insert(*u256); + const bool present = rolling_bloom_filter.contains(*u256); + assert(present); + }, + [&] { + rolling_bloom_filter.reset(); + }); } } diff --git a/src/test/fuzz/script_ops.cpp b/src/test/fuzz/script_ops.cpp index d232e984bc..bdbfe817ff 100644 --- a/src/test/fuzz/script_ops.cpp +++ b/src/test/fuzz/script_ops.cpp @@ -16,56 +16,53 @@ FUZZ_TARGET(script_ops) FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); CScript script = ConsumeScript(fuzzed_data_provider); while (fuzzed_data_provider.remaining_bytes() > 0) { - switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 7)) { - case 0: { - CScript s = ConsumeScript(fuzzed_data_provider); - script = std::move(s); - break; - } - case 1: { - const CScript& s = ConsumeScript(fuzzed_data_provider); - script = s; - break; - } - case 2: - script << fuzzed_data_provider.ConsumeIntegral<int64_t>(); - break; - case 3: - script << ConsumeOpcodeType(fuzzed_data_provider); - break; - case 4: - script << ConsumeScriptNum(fuzzed_data_provider); - break; - case 5: - script << ConsumeRandomLengthByteVector(fuzzed_data_provider); - break; - case 6: - script.clear(); - break; - case 7: { - (void)script.GetSigOpCount(false); - (void)script.GetSigOpCount(true); - (void)script.GetSigOpCount(script); - (void)script.HasValidOps(); - (void)script.IsPayToScriptHash(); - (void)script.IsPayToWitnessScriptHash(); - (void)script.IsPushOnly(); - (void)script.IsUnspendable(); - { - CScript::const_iterator pc = script.begin(); - opcodetype opcode; - (void)script.GetOp(pc, opcode); - std::vector<uint8_t> data; - (void)script.GetOp(pc, opcode, data); - (void)script.IsPushOnly(pc); - } - { - int version; - std::vector<uint8_t> program; - (void)script.IsWitnessProgram(version, program); - } - break; - } - } + CallOneOf( + fuzzed_data_provider, + [&] { + CScript s = ConsumeScript(fuzzed_data_provider); + script = std::move(s); + }, + [&] { + const CScript& s = ConsumeScript(fuzzed_data_provider); + script = s; + }, + [&] { + script << fuzzed_data_provider.ConsumeIntegral<int64_t>(); + }, + [&] { + script << ConsumeOpcodeType(fuzzed_data_provider); + }, + [&] { + script << ConsumeScriptNum(fuzzed_data_provider); + }, + [&] { + script << ConsumeRandomLengthByteVector(fuzzed_data_provider); + }, + [&] { + script.clear(); + }, + [&] { + (void)script.GetSigOpCount(false); + (void)script.GetSigOpCount(true); + (void)script.GetSigOpCount(script); + (void)script.HasValidOps(); + (void)script.IsPayToScriptHash(); + (void)script.IsPayToWitnessScriptHash(); + (void)script.IsPushOnly(); + (void)script.IsUnspendable(); + { + CScript::const_iterator pc = script.begin(); + opcodetype opcode; + (void)script.GetOp(pc, opcode); + std::vector<uint8_t> data; + (void)script.GetOp(pc, opcode, data); + (void)script.IsPushOnly(pc); + } + { + int version; + std::vector<uint8_t> program; + (void)script.IsWitnessProgram(version, program); + } + }); } } diff --git a/src/test/fuzz/scriptnum_ops.cpp b/src/test/fuzz/scriptnum_ops.cpp index 650318f13c..bc4867839c 100644 --- a/src/test/fuzz/scriptnum_ops.cpp +++ b/src/test/fuzz/scriptnum_ops.cpp @@ -29,105 +29,99 @@ FUZZ_TARGET(scriptnum_ops) FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); CScriptNum script_num = ConsumeScriptNum(fuzzed_data_provider); while (fuzzed_data_provider.remaining_bytes() > 0) { - switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 11)) { - case 0: { - const int64_t i = fuzzed_data_provider.ConsumeIntegral<int64_t>(); - assert((script_num == i) != (script_num != i)); - assert((script_num <= i) != (script_num > i)); - assert((script_num >= i) != (script_num < i)); - // Avoid signed integer overflow: - // script/script.h:264:93: runtime error: signed integer overflow: -2261405121394637306 + -9223372036854775802 cannot be represented in type 'long' - if (IsValidAddition(script_num, CScriptNum{i})) { - assert((script_num + i) - i == script_num); - } - // Avoid signed integer overflow: - // script/script.h:265:93: runtime error: signed integer overflow: 9223371895120855039 - -9223372036854710486 cannot be represented in type 'long' - if (IsValidSubtraction(script_num, CScriptNum{i})) { - assert((script_num - i) + i == script_num); - } - break; - } - case 1: { - const CScriptNum random_script_num = ConsumeScriptNum(fuzzed_data_provider); - assert((script_num == random_script_num) != (script_num != random_script_num)); - assert((script_num <= random_script_num) != (script_num > random_script_num)); - assert((script_num >= random_script_num) != (script_num < random_script_num)); - // Avoid signed integer overflow: - // script/script.h:264:93: runtime error: signed integer overflow: -9223126527765971126 + -9223372036854756825 cannot be represented in type 'long' - if (IsValidAddition(script_num, random_script_num)) { - assert((script_num + random_script_num) - random_script_num == script_num); - } - // Avoid signed integer overflow: - // script/script.h:265:93: runtime error: signed integer overflow: 6052837899185946624 - -9223372036854775808 cannot be represented in type 'long' - if (IsValidSubtraction(script_num, random_script_num)) { - assert((script_num - random_script_num) + random_script_num == script_num); - } - break; - } - case 2: { - const CScriptNum random_script_num = ConsumeScriptNum(fuzzed_data_provider); - if (!IsValidAddition(script_num, random_script_num)) { - // Avoid assertion failure: - // ./script/script.h:292: CScriptNum &CScriptNum::operator+=(const int64_t &): Assertion `rhs == 0 || (rhs > 0 && m_value <= std::numeric_limits<int64_t>::max() - rhs) || (rhs < 0 && m_value >= std::numeric_limits<int64_t>::min() - rhs)' failed. - break; - } - script_num += random_script_num; - break; - } - case 3: { - const CScriptNum random_script_num = ConsumeScriptNum(fuzzed_data_provider); - if (!IsValidSubtraction(script_num, random_script_num)) { - // Avoid assertion failure: - // ./script/script.h:300: CScriptNum &CScriptNum::operator-=(const int64_t &): Assertion `rhs == 0 || (rhs > 0 && m_value >= std::numeric_limits<int64_t>::min() + rhs) || (rhs < 0 && m_value <= std::numeric_limits<int64_t>::max() + rhs)' failed. - break; - } - script_num -= random_script_num; - break; - } - case 4: - script_num = script_num & fuzzed_data_provider.ConsumeIntegral<int64_t>(); - break; - case 5: - script_num = script_num & ConsumeScriptNum(fuzzed_data_provider); - break; - case 6: - script_num &= ConsumeScriptNum(fuzzed_data_provider); - break; - case 7: - if (script_num == CScriptNum{std::numeric_limits<int64_t>::min()}) { - // Avoid assertion failure: - // ./script/script.h:279: CScriptNum CScriptNum::operator-() const: Assertion `m_value != std::numeric_limits<int64_t>::min()' failed. - break; - } - script_num = -script_num; - break; - case 8: - script_num = fuzzed_data_provider.ConsumeIntegral<int64_t>(); - break; - case 9: { - const int64_t random_integer = fuzzed_data_provider.ConsumeIntegral<int64_t>(); - if (!IsValidAddition(script_num, CScriptNum{random_integer})) { - // Avoid assertion failure: - // ./script/script.h:292: CScriptNum &CScriptNum::operator+=(const int64_t &): Assertion `rhs == 0 || (rhs > 0 && m_value <= std::numeric_limits<int64_t>::max() - rhs) || (rhs < 0 && m_value >= std::numeric_limits<int64_t>::min() - rhs)' failed. - break; - } - script_num += random_integer; - break; - } - case 10: { - const int64_t random_integer = fuzzed_data_provider.ConsumeIntegral<int64_t>(); - if (!IsValidSubtraction(script_num, CScriptNum{random_integer})) { - // Avoid assertion failure: - // ./script/script.h:300: CScriptNum &CScriptNum::operator-=(const int64_t &): Assertion `rhs == 0 || (rhs > 0 && m_value >= std::numeric_limits<int64_t>::min() + rhs) || (rhs < 0 && m_value <= std::numeric_limits<int64_t>::max() + rhs)' failed. - break; - } - script_num -= random_integer; - break; - } - case 11: - script_num &= fuzzed_data_provider.ConsumeIntegral<int64_t>(); - break; - } + CallOneOf( + fuzzed_data_provider, + [&] { + const int64_t i = fuzzed_data_provider.ConsumeIntegral<int64_t>(); + assert((script_num == i) != (script_num != i)); + assert((script_num <= i) != (script_num > i)); + assert((script_num >= i) != (script_num < i)); + // Avoid signed integer overflow: + // script/script.h:264:93: runtime error: signed integer overflow: -2261405121394637306 + -9223372036854775802 cannot be represented in type 'long' + if (IsValidAddition(script_num, CScriptNum{i})) { + assert((script_num + i) - i == script_num); + } + // Avoid signed integer overflow: + // script/script.h:265:93: runtime error: signed integer overflow: 9223371895120855039 - -9223372036854710486 cannot be represented in type 'long' + if (IsValidSubtraction(script_num, CScriptNum{i})) { + assert((script_num - i) + i == script_num); + } + }, + [&] { + const CScriptNum random_script_num = ConsumeScriptNum(fuzzed_data_provider); + assert((script_num == random_script_num) != (script_num != random_script_num)); + assert((script_num <= random_script_num) != (script_num > random_script_num)); + assert((script_num >= random_script_num) != (script_num < random_script_num)); + // Avoid signed integer overflow: + // script/script.h:264:93: runtime error: signed integer overflow: -9223126527765971126 + -9223372036854756825 cannot be represented in type 'long' + if (IsValidAddition(script_num, random_script_num)) { + assert((script_num + random_script_num) - random_script_num == script_num); + } + // Avoid signed integer overflow: + // script/script.h:265:93: runtime error: signed integer overflow: 6052837899185946624 - -9223372036854775808 cannot be represented in type 'long' + if (IsValidSubtraction(script_num, random_script_num)) { + assert((script_num - random_script_num) + random_script_num == script_num); + } + }, + [&] { + const CScriptNum random_script_num = ConsumeScriptNum(fuzzed_data_provider); + if (!IsValidAddition(script_num, random_script_num)) { + // Avoid assertion failure: + // ./script/script.h:292: CScriptNum &CScriptNum::operator+=(const int64_t &): Assertion `rhs == 0 || (rhs > 0 && m_value <= std::numeric_limits<int64_t>::max() - rhs) || (rhs < 0 && m_value >= std::numeric_limits<int64_t>::min() - rhs)' failed. + return; + } + script_num += random_script_num; + }, + [&] { + const CScriptNum random_script_num = ConsumeScriptNum(fuzzed_data_provider); + if (!IsValidSubtraction(script_num, random_script_num)) { + // Avoid assertion failure: + // ./script/script.h:300: CScriptNum &CScriptNum::operator-=(const int64_t &): Assertion `rhs == 0 || (rhs > 0 && m_value >= std::numeric_limits<int64_t>::min() + rhs) || (rhs < 0 && m_value <= std::numeric_limits<int64_t>::max() + rhs)' failed. + return; + } + script_num -= random_script_num; + }, + [&] { + script_num = script_num & fuzzed_data_provider.ConsumeIntegral<int64_t>(); + }, + [&] { + script_num = script_num & ConsumeScriptNum(fuzzed_data_provider); + }, + [&] { + script_num &= ConsumeScriptNum(fuzzed_data_provider); + }, + [&] { + if (script_num == CScriptNum{std::numeric_limits<int64_t>::min()}) { + // Avoid assertion failure: + // ./script/script.h:279: CScriptNum CScriptNum::operator-() const: Assertion `m_value != std::numeric_limits<int64_t>::min()' failed. + return; + } + script_num = -script_num; + }, + [&] { + script_num = fuzzed_data_provider.ConsumeIntegral<int64_t>(); + }, + [&] { + const int64_t random_integer = fuzzed_data_provider.ConsumeIntegral<int64_t>(); + if (!IsValidAddition(script_num, CScriptNum{random_integer})) { + // Avoid assertion failure: + // ./script/script.h:292: CScriptNum &CScriptNum::operator+=(const int64_t &): Assertion `rhs == 0 || (rhs > 0 && m_value <= std::numeric_limits<int64_t>::max() - rhs) || (rhs < 0 && m_value >= std::numeric_limits<int64_t>::min() - rhs)' failed. + return; + } + script_num += random_integer; + }, + [&] { + const int64_t random_integer = fuzzed_data_provider.ConsumeIntegral<int64_t>(); + if (!IsValidSubtraction(script_num, CScriptNum{random_integer})) { + // Avoid assertion failure: + // ./script/script.h:300: CScriptNum &CScriptNum::operator-=(const int64_t &): Assertion `rhs == 0 || (rhs > 0 && m_value >= std::numeric_limits<int64_t>::min() + rhs) || (rhs < 0 && m_value <= std::numeric_limits<int64_t>::max() + rhs)' failed. + return; + } + script_num -= random_integer; + }, + [&] { + script_num &= fuzzed_data_provider.ConsumeIntegral<int64_t>(); + }); (void)script_num.getint(); (void)script_num.getvch(); } diff --git a/src/test/fuzz/strprintf.cpp b/src/test/fuzz/strprintf.cpp index 4af0e750ce..b66a7abfb3 100644 --- a/src/test/fuzz/strprintf.cpp +++ b/src/test/fuzz/strprintf.cpp @@ -4,6 +4,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> #include <tinyformat.h> #include <util/strencodings.h> #include <util/translation.h> @@ -109,32 +110,32 @@ FUZZ_TARGET(str_printf) } try { - switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 5)) { - case 0: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeRandomLengthString(32)); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeRandomLengthString(32)); - break; - case 1: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeRandomLengthString(32).c_str()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeRandomLengthString(32).c_str()); - break; - case 2: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<signed char>()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<signed char>()); - break; - case 3: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<unsigned char>()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<unsigned char>()); - break; - case 4: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<char>()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<char>()); - break; - case 5: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeBool()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeBool()); - break; - } + CallOneOf( + fuzzed_data_provider, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeRandomLengthString(32)); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeRandomLengthString(32)); + }, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeRandomLengthString(32).c_str()); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeRandomLengthString(32).c_str()); + }, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<signed char>()); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<signed char>()); + }, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<unsigned char>()); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<unsigned char>()); + }, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<char>()); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<char>()); + }, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeBool()); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeBool()); + }); } catch (const tinyformat::format_error&) { } @@ -155,40 +156,40 @@ FUZZ_TARGET(str_printf) } try { - switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 7)) { - case 0: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeFloatingPoint<float>()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeFloatingPoint<float>()); - break; - case 1: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeFloatingPoint<double>()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeFloatingPoint<double>()); - break; - case 2: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<int16_t>()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<int16_t>()); - break; - case 3: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<uint16_t>()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<uint16_t>()); - break; - case 4: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<int32_t>()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<int32_t>()); - break; - case 5: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<uint32_t>()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<uint32_t>()); - break; - case 6: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<int64_t>()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<int64_t>()); - break; - case 7: - (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<uint64_t>()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<uint64_t>()); - break; - } + CallOneOf( + fuzzed_data_provider, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeFloatingPoint<float>()); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeFloatingPoint<float>()); + }, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeFloatingPoint<double>()); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeFloatingPoint<double>()); + }, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<int16_t>()); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<int16_t>()); + }, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<uint16_t>()); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<uint16_t>()); + }, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<int32_t>()); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<int32_t>()); + }, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<uint32_t>()); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<uint32_t>()); + }, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<int64_t>()); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<int64_t>()); + }, + [&] { + (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<uint64_t>()); + (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral<uint64_t>()); + }); } catch (const tinyformat::format_error&) { } } diff --git a/src/test/fuzz/system.cpp b/src/test/fuzz/system.cpp index 375a8c1ed0..47b38b6d23 100644 --- a/src/test/fuzz/system.cpp +++ b/src/test/fuzz/system.cpp @@ -32,71 +32,63 @@ FUZZ_TARGET(system) } while (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 7)) { - case 0: { - args_manager.SelectConfigNetwork(fuzzed_data_provider.ConsumeRandomLengthString(16)); - break; - } - case 1: { - args_manager.SoftSetArg(fuzzed_data_provider.ConsumeRandomLengthString(16), fuzzed_data_provider.ConsumeRandomLengthString(16)); - break; - } - case 2: { - args_manager.ForceSetArg(fuzzed_data_provider.ConsumeRandomLengthString(16), fuzzed_data_provider.ConsumeRandomLengthString(16)); - break; - } - case 3: { - args_manager.SoftSetBoolArg(fuzzed_data_provider.ConsumeRandomLengthString(16), fuzzed_data_provider.ConsumeBool()); - break; - } - case 4: { - const OptionsCategory options_category = fuzzed_data_provider.PickValueInArray<OptionsCategory>({OptionsCategory::OPTIONS, OptionsCategory::CONNECTION, OptionsCategory::WALLET, OptionsCategory::WALLET_DEBUG_TEST, OptionsCategory::ZMQ, OptionsCategory::DEBUG_TEST, OptionsCategory::CHAINPARAMS, OptionsCategory::NODE_RELAY, OptionsCategory::BLOCK_CREATION, OptionsCategory::RPC, OptionsCategory::GUI, OptionsCategory::COMMANDS, OptionsCategory::REGISTER_COMMANDS, OptionsCategory::HIDDEN}); - // Avoid hitting: - // util/system.cpp:425: void ArgsManager::AddArg(const std::string &, const std::string &, unsigned int, const OptionsCategory &): Assertion `ret.second' failed. - const std::string argument_name = GetArgumentName(fuzzed_data_provider.ConsumeRandomLengthString(16)); - if (args_manager.GetArgFlags(argument_name) != nullopt) { - break; - } - args_manager.AddArg(argument_name, fuzzed_data_provider.ConsumeRandomLengthString(16), fuzzed_data_provider.ConsumeIntegral<unsigned int>(), options_category); - break; - } - case 5: { - // Avoid hitting: - // util/system.cpp:425: void ArgsManager::AddArg(const std::string &, const std::string &, unsigned int, const OptionsCategory &): Assertion `ret.second' failed. - const std::vector<std::string> names = ConsumeRandomLengthStringVector(fuzzed_data_provider); - std::vector<std::string> hidden_arguments; - for (const std::string& name : names) { - const std::string hidden_argument = GetArgumentName(name); - if (args_manager.GetArgFlags(hidden_argument) != nullopt) { - continue; + CallOneOf( + fuzzed_data_provider, + [&] { + args_manager.SelectConfigNetwork(fuzzed_data_provider.ConsumeRandomLengthString(16)); + }, + [&] { + args_manager.SoftSetArg(fuzzed_data_provider.ConsumeRandomLengthString(16), fuzzed_data_provider.ConsumeRandomLengthString(16)); + }, + [&] { + args_manager.ForceSetArg(fuzzed_data_provider.ConsumeRandomLengthString(16), fuzzed_data_provider.ConsumeRandomLengthString(16)); + }, + [&] { + args_manager.SoftSetBoolArg(fuzzed_data_provider.ConsumeRandomLengthString(16), fuzzed_data_provider.ConsumeBool()); + }, + [&] { + const OptionsCategory options_category = fuzzed_data_provider.PickValueInArray<OptionsCategory>({OptionsCategory::OPTIONS, OptionsCategory::CONNECTION, OptionsCategory::WALLET, OptionsCategory::WALLET_DEBUG_TEST, OptionsCategory::ZMQ, OptionsCategory::DEBUG_TEST, OptionsCategory::CHAINPARAMS, OptionsCategory::NODE_RELAY, OptionsCategory::BLOCK_CREATION, OptionsCategory::RPC, OptionsCategory::GUI, OptionsCategory::COMMANDS, OptionsCategory::REGISTER_COMMANDS, OptionsCategory::HIDDEN}); + // Avoid hitting: + // util/system.cpp:425: void ArgsManager::AddArg(const std::string &, const std::string &, unsigned int, const OptionsCategory &): Assertion `ret.second' failed. + const std::string argument_name = GetArgumentName(fuzzed_data_provider.ConsumeRandomLengthString(16)); + if (args_manager.GetArgFlags(argument_name) != nullopt) { + return; } - if (std::find(hidden_arguments.begin(), hidden_arguments.end(), hidden_argument) != hidden_arguments.end()) { - continue; + args_manager.AddArg(argument_name, fuzzed_data_provider.ConsumeRandomLengthString(16), fuzzed_data_provider.ConsumeIntegral<unsigned int>(), options_category); + }, + [&] { + // Avoid hitting: + // util/system.cpp:425: void ArgsManager::AddArg(const std::string &, const std::string &, unsigned int, const OptionsCategory &): Assertion `ret.second' failed. + const std::vector<std::string> names = ConsumeRandomLengthStringVector(fuzzed_data_provider); + std::vector<std::string> hidden_arguments; + for (const std::string& name : names) { + const std::string hidden_argument = GetArgumentName(name); + if (args_manager.GetArgFlags(hidden_argument) != nullopt) { + continue; + } + if (std::find(hidden_arguments.begin(), hidden_arguments.end(), hidden_argument) != hidden_arguments.end()) { + continue; + } + hidden_arguments.push_back(hidden_argument); } - hidden_arguments.push_back(hidden_argument); - } - args_manager.AddHiddenArgs(hidden_arguments); - break; - } - case 6: { - args_manager.ClearArgs(); - break; - } - case 7: { - const std::vector<std::string> random_arguments = ConsumeRandomLengthStringVector(fuzzed_data_provider); - std::vector<const char*> argv; - argv.reserve(random_arguments.size()); - for (const std::string& random_argument : random_arguments) { - argv.push_back(random_argument.c_str()); - } - try { - std::string error; - (void)args_manager.ParseParameters(argv.size(), argv.data(), error); - } catch (const std::logic_error&) { - } - break; - } - } + args_manager.AddHiddenArgs(hidden_arguments); + }, + [&] { + args_manager.ClearArgs(); + }, + [&] { + const std::vector<std::string> random_arguments = ConsumeRandomLengthStringVector(fuzzed_data_provider); + std::vector<const char*> argv; + argv.reserve(random_arguments.size()); + for (const std::string& random_argument : random_arguments) { + argv.push_back(random_argument.c_str()); + } + try { + std::string error; + (void)args_manager.ParseParameters(argv.size(), argv.data(), error); + } catch (const std::logic_error&) { + } + }); } const std::string s1 = fuzzed_data_provider.ConsumeRandomLengthString(16); diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index 4e8e501895..b7cf395e76 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -36,6 +36,17 @@ #include <string> #include <vector> +template <typename... Callables> +void CallOneOf(FuzzedDataProvider& fuzzed_data_provider, Callables... callables) +{ + constexpr size_t call_size{sizeof...(callables)}; + static_assert(call_size >= 1); + const size_t call_index{fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, call_size - 1)}; + + size_t i{0}; + return ((i++ == call_index ? callables() : void()), ...); +} + [[nodiscard]] inline std::vector<uint8_t> ConsumeRandomLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const size_t max_length = 4096) noexcept { const std::string s = fuzzed_data_provider.ConsumeRandomLengthString(max_length); @@ -165,37 +176,31 @@ template <typename WeakEnumType, size_t size> [[nodiscard]] inline CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) noexcept { CTxDestination tx_destination; - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 5)) { - case 0: { - tx_destination = CNoDestination{}; - break; - } - case 1: { - tx_destination = PKHash{ConsumeUInt160(fuzzed_data_provider)}; - break; - } - case 2: { - tx_destination = ScriptHash{ConsumeUInt160(fuzzed_data_provider)}; - break; - } - case 3: { - tx_destination = WitnessV0ScriptHash{ConsumeUInt256(fuzzed_data_provider)}; - break; - } - case 4: { - tx_destination = WitnessV0KeyHash{ConsumeUInt160(fuzzed_data_provider)}; - break; - } - case 5: { - WitnessUnknown witness_unknown{}; - witness_unknown.version = fuzzed_data_provider.ConsumeIntegral<int>(); - const std::vector<uint8_t> witness_unknown_program_1 = fuzzed_data_provider.ConsumeBytes<uint8_t>(40); - witness_unknown.length = witness_unknown_program_1.size(); - std::copy(witness_unknown_program_1.begin(), witness_unknown_program_1.end(), witness_unknown.program); - tx_destination = witness_unknown; - break; - } - } + CallOneOf( + fuzzed_data_provider, + [&] { + tx_destination = CNoDestination{}; + }, + [&] { + tx_destination = PKHash{ConsumeUInt160(fuzzed_data_provider)}; + }, + [&] { + tx_destination = ScriptHash{ConsumeUInt160(fuzzed_data_provider)}; + }, + [&] { + tx_destination = WitnessV0ScriptHash{ConsumeUInt256(fuzzed_data_provider)}; + }, + [&] { + tx_destination = WitnessV0KeyHash{ConsumeUInt160(fuzzed_data_provider)}; + }, + [&] { + WitnessUnknown witness_unknown{}; + witness_unknown.version = fuzzed_data_provider.ConsumeIntegral<int>(); + const std::vector<uint8_t> witness_unknown_program_1 = fuzzed_data_provider.ConsumeBytes<uint8_t>(40); + witness_unknown.length = witness_unknown_program_1.size(); + std::copy(witness_unknown_program_1.begin(), witness_unknown_program_1.end(), witness_unknown.program); + tx_destination = witness_unknown; + }); return tx_destination; } @@ -354,32 +359,26 @@ public: return nullptr; } std::string mode; - switch (m_fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 5)) { - case 0: { - mode = "r"; - break; - } - case 1: { - mode = "r+"; - break; - } - case 2: { - mode = "w"; - break; - } - case 3: { - mode = "w+"; - break; - } - case 4: { - mode = "a"; - break; - } - case 5: { - mode = "a+"; - break; - } - } + CallOneOf( + m_fuzzed_data_provider, + [&] { + mode = "r"; + }, + [&] { + mode = "r+"; + }, + [&] { + mode = "w"; + }, + [&] { + mode = "w+"; + }, + [&] { + mode = "a"; + }, + [&] { + mode = "a+"; + }); #ifdef _GNU_SOURCE const cookie_io_functions_t io_hooks = { FuzzedFileProvider::read, @@ -477,66 +476,64 @@ public: return {fuzzed_data_provider}; } -#define WRITE_TO_STREAM_CASE(id, type, consume) \ - case id: { \ - type o = consume; \ - stream << o; \ - break; \ +#define WRITE_TO_STREAM_CASE(type, consume) \ + [&] { \ + type o = consume; \ + stream << o; \ } template <typename Stream> void WriteToStream(FuzzedDataProvider& fuzzed_data_provider, Stream& stream) noexcept { while (fuzzed_data_provider.ConsumeBool()) { try { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 13)) { - WRITE_TO_STREAM_CASE(0, bool, fuzzed_data_provider.ConsumeBool()) - WRITE_TO_STREAM_CASE(1, char, fuzzed_data_provider.ConsumeIntegral<char>()) - WRITE_TO_STREAM_CASE(2, int8_t, fuzzed_data_provider.ConsumeIntegral<int8_t>()) - WRITE_TO_STREAM_CASE(3, uint8_t, fuzzed_data_provider.ConsumeIntegral<uint8_t>()) - WRITE_TO_STREAM_CASE(4, int16_t, fuzzed_data_provider.ConsumeIntegral<int16_t>()) - WRITE_TO_STREAM_CASE(5, uint16_t, fuzzed_data_provider.ConsumeIntegral<uint16_t>()) - WRITE_TO_STREAM_CASE(6, int32_t, fuzzed_data_provider.ConsumeIntegral<int32_t>()) - WRITE_TO_STREAM_CASE(7, uint32_t, fuzzed_data_provider.ConsumeIntegral<uint32_t>()) - WRITE_TO_STREAM_CASE(8, int64_t, fuzzed_data_provider.ConsumeIntegral<int64_t>()) - WRITE_TO_STREAM_CASE(9, uint64_t, fuzzed_data_provider.ConsumeIntegral<uint64_t>()) - WRITE_TO_STREAM_CASE(10, float, fuzzed_data_provider.ConsumeFloatingPoint<float>()) - WRITE_TO_STREAM_CASE(11, double, fuzzed_data_provider.ConsumeFloatingPoint<double>()) - WRITE_TO_STREAM_CASE(12, std::string, fuzzed_data_provider.ConsumeRandomLengthString(32)) - WRITE_TO_STREAM_CASE(13, std::vector<char>, ConsumeRandomLengthIntegralVector<char>(fuzzed_data_provider)) - } + CallOneOf( + fuzzed_data_provider, + WRITE_TO_STREAM_CASE(bool, fuzzed_data_provider.ConsumeBool()), + WRITE_TO_STREAM_CASE(char, fuzzed_data_provider.ConsumeIntegral<char>()), + WRITE_TO_STREAM_CASE(int8_t, fuzzed_data_provider.ConsumeIntegral<int8_t>()), + WRITE_TO_STREAM_CASE(uint8_t, fuzzed_data_provider.ConsumeIntegral<uint8_t>()), + WRITE_TO_STREAM_CASE(int16_t, fuzzed_data_provider.ConsumeIntegral<int16_t>()), + WRITE_TO_STREAM_CASE(uint16_t, fuzzed_data_provider.ConsumeIntegral<uint16_t>()), + WRITE_TO_STREAM_CASE(int32_t, fuzzed_data_provider.ConsumeIntegral<int32_t>()), + WRITE_TO_STREAM_CASE(uint32_t, fuzzed_data_provider.ConsumeIntegral<uint32_t>()), + WRITE_TO_STREAM_CASE(int64_t, fuzzed_data_provider.ConsumeIntegral<int64_t>()), + WRITE_TO_STREAM_CASE(uint64_t, fuzzed_data_provider.ConsumeIntegral<uint64_t>()), + WRITE_TO_STREAM_CASE(float, fuzzed_data_provider.ConsumeFloatingPoint<float>()), + WRITE_TO_STREAM_CASE(double, fuzzed_data_provider.ConsumeFloatingPoint<double>()), + WRITE_TO_STREAM_CASE(std::string, fuzzed_data_provider.ConsumeRandomLengthString(32)), + WRITE_TO_STREAM_CASE(std::vector<char>, ConsumeRandomLengthIntegralVector<char>(fuzzed_data_provider))); } catch (const std::ios_base::failure&) { break; } } } -#define READ_FROM_STREAM_CASE(id, type) \ - case id: { \ - type o; \ - stream >> o; \ - break; \ +#define READ_FROM_STREAM_CASE(type) \ + [&] { \ + type o; \ + stream >> o; \ } template <typename Stream> void ReadFromStream(FuzzedDataProvider& fuzzed_data_provider, Stream& stream) noexcept { while (fuzzed_data_provider.ConsumeBool()) { try { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 13)) { - READ_FROM_STREAM_CASE(0, bool) - READ_FROM_STREAM_CASE(1, char) - READ_FROM_STREAM_CASE(2, int8_t) - READ_FROM_STREAM_CASE(3, uint8_t) - READ_FROM_STREAM_CASE(4, int16_t) - READ_FROM_STREAM_CASE(5, uint16_t) - READ_FROM_STREAM_CASE(6, int32_t) - READ_FROM_STREAM_CASE(7, uint32_t) - READ_FROM_STREAM_CASE(8, int64_t) - READ_FROM_STREAM_CASE(9, uint64_t) - READ_FROM_STREAM_CASE(10, float) - READ_FROM_STREAM_CASE(11, double) - READ_FROM_STREAM_CASE(12, std::string) - READ_FROM_STREAM_CASE(13, std::vector<char>) - } + CallOneOf( + fuzzed_data_provider, + READ_FROM_STREAM_CASE(bool), + READ_FROM_STREAM_CASE(char), + READ_FROM_STREAM_CASE(int8_t), + READ_FROM_STREAM_CASE(uint8_t), + READ_FROM_STREAM_CASE(int16_t), + READ_FROM_STREAM_CASE(uint16_t), + READ_FROM_STREAM_CASE(int32_t), + READ_FROM_STREAM_CASE(uint32_t), + READ_FROM_STREAM_CASE(int64_t), + READ_FROM_STREAM_CASE(uint64_t), + READ_FROM_STREAM_CASE(float), + READ_FROM_STREAM_CASE(double), + READ_FROM_STREAM_CASE(std::string), + READ_FROM_STREAM_CASE(std::vector<char>)); } catch (const std::ios_base::failure&) { break; } diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index e167fc98fd..738f414cd0 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -192,9 +192,9 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const m_node.banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); m_node.connman = MakeUnique<CConnman>(0x1337, 0x1337); // Deterministic randomness for tests. - m_node.peerman = std::make_unique<PeerManager>(chainparams, *m_node.connman, m_node.banman.get(), - *m_node.scheduler, *m_node.chainman, *m_node.mempool, - false); + m_node.peerman = PeerManager::make(chainparams, *m_node.connman, m_node.banman.get(), + *m_node.scheduler, *m_node.chainman, *m_node.mempool, + false); { CConnman::Options options; options.m_msgproc = m_node.peerman.get(); diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp index c8a375275f..92d8cf2e7d 100644 --- a/src/test/validation_chainstate_tests.cpp +++ b/src/test/validation_chainstate_tests.cpp @@ -35,7 +35,7 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) return outp; }; - CChainState& c1 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(mempool)); + CChainState& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(mempool)); c1.InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23)); diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index 75939e0140..3d8570e27c 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -30,7 +30,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) // Create a legacy (IBD) chainstate. // - CChainState& c1 = *WITH_LOCK(::cs_main, return &manager.InitializeChainstate(mempool)); + CChainState& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(mempool)); chainstates.push_back(&c1); c1.InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); @@ -56,7 +56,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) // Create a snapshot-based chainstate. // - CChainState& c2 = *WITH_LOCK(::cs_main, return &manager.InitializeChainstate(mempool, GetRandHash())); + CChainState& c2 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(mempool, GetRandHash())); chainstates.push_back(&c2); c2.InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); @@ -116,7 +116,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches) // Create a legacy (IBD) chainstate. // - CChainState& c1 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(mempool)); + CChainState& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(mempool)); chainstates.push_back(&c1); c1.InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); @@ -134,7 +134,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches) // Create a snapshot-based chainstate. // - CChainState& c2 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(mempool, GetRandHash())); + CChainState& c2 = WITH_LOCK(cs_main, return manager.InitializeChainstate(mempool, GetRandHash())); chainstates.push_back(&c2); c2.InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index d18182c07d..9ae7b921b3 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -1127,5 +1127,3 @@ CTxMemPool::EpochGuard::~EpochGuard() ++pool.m_epoch; pool.m_has_epoch_guard = false; } - -SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} diff --git a/src/txmempool.h b/src/txmempool.h index 15797cbc00..9a1aa9bc2b 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -15,13 +15,13 @@ #include <amount.h> #include <coins.h> -#include <crypto/siphash.h> #include <indirectmap.h> #include <optional.h> #include <policy/feerate.h> #include <primitives/transaction.h> #include <sync.h> #include <random.h> +#include <util/hasher.h> #include <boost/multi_index_container.hpp> #include <boost/multi_index/hashed_index.hpp> @@ -398,20 +398,6 @@ enum class MemPoolRemovalReason { REPLACED, //!< Removed for replacement }; -class SaltedTxidHasher -{ -private: - /** Salt */ - const uint64_t k0, k1; - -public: - SaltedTxidHasher(); - - size_t operator()(const uint256& txid) const { - return SipHashUint256(k0, k1, txid); - } -}; - /** * CTxMemPool stores valid-according-to-the-current-best-chain transactions * that may be included in the next block. diff --git a/src/util/hasher.cpp b/src/util/hasher.cpp new file mode 100644 index 0000000000..5900daf050 --- /dev/null +++ b/src/util/hasher.cpp @@ -0,0 +1,19 @@ +// Copyright (c) 2019 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 <random.h> +#include <util/hasher.h> + +#include <limits> + +SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} + +SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} + +SaltedSipHasher::SaltedSipHasher() : m_k0(GetRand(std::numeric_limits<uint64_t>::max())), m_k1(GetRand(std::numeric_limits<uint64_t>::max())) {} + +size_t SaltedSipHasher::operator()(const Span<const unsigned char>& script) const +{ + return CSipHasher(m_k0, m_k1).Write(script.data(), script.size()).Finalize(); +} diff --git a/src/util/hasher.h b/src/util/hasher.h new file mode 100644 index 0000000000..fa2fea30d8 --- /dev/null +++ b/src/util/hasher.h @@ -0,0 +1,99 @@ +// Copyright (c) 2019 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_UTIL_HASHER_H +#define BITCOIN_UTIL_HASHER_H + +#include <crypto/siphash.h> +#include <primitives/transaction.h> +#include <uint256.h> + +class SaltedTxidHasher +{ +private: + /** Salt */ + const uint64_t k0, k1; + +public: + SaltedTxidHasher(); + + size_t operator()(const uint256& txid) const { + return SipHashUint256(k0, k1, txid); + } +}; + +class SaltedOutpointHasher +{ +private: + /** Salt */ + const uint64_t k0, k1; + +public: + SaltedOutpointHasher(); + + /** + * This *must* return size_t. With Boost 1.46 on 32-bit systems the + * unordered_map will behave unpredictably if the custom hasher returns a + * uint64_t, resulting in failures when syncing the chain (#4634). + * + * Having the hash noexcept allows libstdc++'s unordered_map to recalculate + * the hash during rehash, so it does not have to cache the value. This + * reduces node's memory by sizeof(size_t). The required recalculation has + * a slight performance penalty (around 1.6%), but this is compensated by + * memory savings of about 9% which allow for a larger dbcache setting. + * + * @see https://gcc.gnu.org/onlinedocs/gcc-9.2.0/libstdc++/manual/manual/unordered_associative.html + */ + size_t operator()(const COutPoint& id) const noexcept { + return SipHashUint256Extra(k0, k1, id.hash, id.n); + } +}; + +struct FilterHeaderHasher +{ + size_t operator()(const uint256& hash) const { return ReadLE64(hash.begin()); } +}; + +/** + * We're hashing a nonce into the entries themselves, so we don't need extra + * blinding in the set hash computation. + * + * This may exhibit platform endian dependent behavior but because these are + * nonced hashes (random) and this state is only ever used locally it is safe. + * All that matters is local consistency. + */ +class SignatureCacheHasher +{ +public: + template <uint8_t hash_select> + uint32_t operator()(const uint256& key) const + { + static_assert(hash_select <8, "SignatureCacheHasher only has 8 hashes available."); + uint32_t u; + std::memcpy(&u, key.begin()+4*hash_select, 4); + return u; + } +}; + +struct BlockHasher +{ + // this used to call `GetCheapHash()` in uint256, which was later moved; the + // cheap hash function simply calls ReadLE64() however, so the end result is + // identical + size_t operator()(const uint256& hash) const { return ReadLE64(hash.begin()); } +}; + +class SaltedSipHasher +{ +private: + /** Salt */ + const uint64_t m_k0, m_k1; + +public: + SaltedSipHasher(); + + size_t operator()(const Span<const unsigned char>& script) const; +}; + +#endif // BITCOIN_UTIL_HASHER_H diff --git a/src/validation.cpp b/src/validation.cpp index 4f7c95b2ad..a22c75a686 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -404,36 +404,41 @@ static void UpdateMempoolForReorg(CTxMemPool& mempool, DisconnectedBlockTransact LimitMempoolSize(mempool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)}); } -// Used to avoid mempool polluting consensus critical paths if CCoinsViewMempool -// were somehow broken and returning the wrong scriptPubKeys -static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& view, const CTxMemPool& pool, - unsigned int flags, PrecomputedTransactionData& txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { +/** +* Checks to avoid mempool polluting consensus critical paths since cached +* signature and script validity results will be reused if we validate this +* transaction again during block validation. +* */ +static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationState& state, + const CCoinsViewCache& view, const CTxMemPool& pool, + unsigned int flags, PrecomputedTransactionData& txdata) + EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs) +{ AssertLockHeld(cs_main); - - // pool.cs should be locked already, but go ahead and re-take the lock here - // to enforce that mempool doesn't change between when we check the view - // and when we actually call through to CheckInputScripts - LOCK(pool.cs); + AssertLockHeld(pool.cs); assert(!tx.IsCoinBase()); for (const CTxIn& txin : tx.vin) { const Coin& coin = view.AccessCoin(txin.prevout); - // AcceptToMemoryPoolWorker has already checked that the coins are - // available, so this shouldn't fail. If the inputs are not available - // here then return false. + // This coin was checked in PreChecks and MemPoolAccept + // has been holding cs_main since then. + Assume(!coin.IsSpent()); if (coin.IsSpent()) return false; - // Check equivalence for available inputs. + // If the Coin is available, there are 2 possibilities: + // it is available in our current ChainstateActive UTXO set, + // or it's a UTXO provided by a transaction in our mempool. + // Ensure the scriptPubKeys in Coins from CoinsView are correct. const CTransactionRef& txFrom = pool.get(txin.prevout.hash); if (txFrom) { assert(txFrom->GetHash() == txin.prevout.hash); assert(txFrom->vout.size() > txin.prevout.n); assert(txFrom->vout[txin.prevout.n] == coin.out); } else { - const Coin& coinFromDisk = ::ChainstateActive().CoinsTip().AccessCoin(txin.prevout); - assert(!coinFromDisk.IsSpent()); - assert(coinFromDisk.out == coin.out); + const Coin& coinFromUTXOSet = ::ChainstateActive().CoinsTip().AccessCoin(txin.prevout); + assert(!coinFromUTXOSet.IsSpent()); + assert(coinFromUTXOSet.out == coin.out); } } @@ -502,13 +507,13 @@ private: // Run the script checks using our policy flags. As this can be slow, we should // only invoke this on transactions that have otherwise passed policy checks. - bool PolicyScriptChecks(ATMPArgs& args, const Workspace& ws, PrecomputedTransactionData& txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool PolicyScriptChecks(ATMPArgs& args, const Workspace& ws, PrecomputedTransactionData& txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); // Re-run the script checks, using consensus flags, and try to cache the // result in the scriptcache. This should be done after // PolicyScriptChecks(). This requires that all inputs either be in our // utxo set or in the mempool. - bool ConsensusScriptChecks(ATMPArgs& args, const Workspace& ws, PrecomputedTransactionData &txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool ConsensusScriptChecks(ATMPArgs& args, const Workspace& ws, PrecomputedTransactionData &txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); // Try to add the transaction to the mempool, removing any conflicts first. // Returns true if the transaction is in the mempool after any size @@ -516,7 +521,7 @@ private: bool Finalize(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); // Compare a package's feerate against minimum allowed. - bool CheckFeeRate(size_t package_size, CAmount package_fee, TxValidationState& state) + bool CheckFeeRate(size_t package_size, CAmount package_fee, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs) { CAmount mempoolRejectFee = m_pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(package_size); if (mempoolRejectFee > 0 && package_fee < mempoolRejectFee) { diff --git a/src/validation.h b/src/validation.h index d10b260d8a..1b4e40cd53 100644 --- a/src/validation.h +++ b/src/validation.h @@ -23,6 +23,7 @@ #include <txdb.h> #include <versionbits.h> #include <serialize.h> +#include <util/hasher.h> #include <atomic> #include <map> @@ -93,14 +94,6 @@ static const unsigned int DEFAULT_CHECKLEVEL = 3; // Setting the target to >= 550 MiB will make it likely we can respect the target. static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES = 550 * 1024 * 1024; -struct BlockHasher -{ - // this used to call `GetCheapHash()` in uint256, which was later moved; the - // cheap hash function simply calls ReadLE64() however, so the end result is - // identical - size_t operator()(const uint256& hash) const { return ReadLE64(hash.begin()); } -}; - /** Current sync state passed to tip changed callbacks. */ enum class SynchronizationState { INIT_REINDEX, diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index cec46a0fbb..8f6b69bc78 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -304,7 +304,7 @@ private: /* the HD chain data model (external chain counters) */ CHDChain m_hd_chain; - std::unordered_map<CKeyID, CHDChain, KeyIDHasher> m_inactive_hd_chains; + std::unordered_map<CKeyID, CHDChain, SaltedSipHasher> m_inactive_hd_chains; /* HD derive new child key (on internal or external chain) */ void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); diff --git a/test/sanitizer_suppressions/tsan b/test/sanitizer_suppressions/tsan index 29221a94f2..3a04418e8b 100644 --- a/test/sanitizer_suppressions/tsan +++ b/test/sanitizer_suppressions/tsan @@ -13,7 +13,7 @@ mutex:CConnman::ThreadOpenConnections mutex:CConnman::ThreadOpenAddedConnections mutex:CConnman::SocketHandler mutex:UpdateTip -mutex:PeerManager::UpdatedBlockTip +mutex:PeerManagerImpl::UpdatedBlockTip mutex:g_best_block_mutex # race (TODO fix) |