diff --git a/ci/test/00_setup_env_freebsd_cross.sh b/ci/test/00_setup_env_freebsd_cross.sh index d4919d55e15d..dc754dc1ee9a 100755 --- a/ci/test/00_setup_env_freebsd_cross.sh +++ b/ci/test/00_setup_env_freebsd_cross.sh @@ -7,7 +7,7 @@ export LC_ALL=C.UTF-8 export CONTAINER_NAME=ci_freebsd_cross -export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:24.04" +export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:26.04" export APT_LLVM_V="22" export FREEBSD_VERSION=15.0 export PACKAGES="clang-${APT_LLVM_V} llvm-${APT_LLVM_V} lld" diff --git a/ci/test/00_setup_env_i686.sh b/ci/test/00_setup_env_i686.sh index 7ea763fea631..c8819b909c35 100755 --- a/ci/test/00_setup_env_i686.sh +++ b/ci/test/00_setup_env_i686.sh @@ -8,7 +8,7 @@ export LC_ALL=C.UTF-8 export HOST=i686-pc-linux-gnu export CONTAINER_NAME=ci_i686 -export CI_IMAGE_NAME_TAG="mirror.gcr.io/debian:trixie" +export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:26.04" export CI_IMAGE_PLATFORM="linux/amd64" export CI_CONTAINER_CAP="--security-opt seccomp=unconfined" export PACKAGES="llvm clang g++-multilib" diff --git a/ci/test/00_setup_env_native_alpine_musl.sh b/ci/test/00_setup_env_native_alpine_musl.sh index 4f63472997e9..591b38116c00 100755 --- a/ci/test/00_setup_env_native_alpine_musl.sh +++ b/ci/test/00_setup_env_native_alpine_musl.sh @@ -7,7 +7,7 @@ export LC_ALL=C.UTF-8 export CONTAINER_NAME=ci_native_alpine_musl -export CI_IMAGE_NAME_TAG="mirror.gcr.io/alpine:3.23" +export CI_IMAGE_NAME_TAG="mirror.gcr.io/alpine:3.24" export CI_BASE_PACKAGES="build-base musl-dev pkgconf curl ccache make ninja git python3-dev py3-pip which patch xz procps rsync util-linux bison e2fsprogs cmake dash linux-headers" export PIP_PACKAGES="--break-system-packages pycapnp" export DEP_OPTS="DEBUG=1" diff --git a/ci/test/00_setup_env_native_fuzz.sh b/ci/test/00_setup_env_native_fuzz.sh index 84e48caed06f..7a236663066a 100755 --- a/ci/test/00_setup_env_native_fuzz.sh +++ b/ci/test/00_setup_env_native_fuzz.sh @@ -6,7 +6,7 @@ export LC_ALL=C.UTF-8 -export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:24.04" +export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:26.04" export CONTAINER_NAME=ci_native_fuzz export APT_LLVM_V="22" export PACKAGES="clang-${APT_LLVM_V} llvm-${APT_LLVM_V} libclang-rt-${APT_LLVM_V}-dev libevent-dev libboost-dev libcapnp-dev capnproto" diff --git a/ci/test/00_setup_env_native_fuzz_with_msan.sh b/ci/test/00_setup_env_native_fuzz_with_msan.sh index 93569a76b3bd..d5b4eeaaf1d3 100755 --- a/ci/test/00_setup_env_native_fuzz_with_msan.sh +++ b/ci/test/00_setup_env_native_fuzz_with_msan.sh @@ -6,7 +6,7 @@ export LC_ALL=C.UTF-8 -export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:24.04" +export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:26.04" export APT_LLVM_V="22" LIBCXX_DIR="/cxx_build/" export MSAN_FLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O1 -fno-optimize-sibling-calls" diff --git a/ci/test/00_setup_env_native_fuzz_with_valgrind.sh b/ci/test/00_setup_env_native_fuzz_with_valgrind.sh index 612bc594b466..a720725f91ee 100755 --- a/ci/test/00_setup_env_native_fuzz_with_valgrind.sh +++ b/ci/test/00_setup_env_native_fuzz_with_valgrind.sh @@ -6,7 +6,7 @@ export LC_ALL=C.UTF-8 -export CI_IMAGE_NAME_TAG="mirror.gcr.io/debian:trixie" +export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:26.04" export CONTAINER_NAME=ci_native_fuzz_valgrind export PACKAGES="clang llvm libclang-rt-dev libevent-dev libboost-dev valgrind libcapnp-dev capnproto" export NO_DEPENDS=1 diff --git a/ci/test/00_setup_env_native_iwyu.sh b/ci/test/00_setup_env_native_iwyu.sh index b34fe477ac33..01547712e022 100755 --- a/ci/test/00_setup_env_native_iwyu.sh +++ b/ci/test/00_setup_env_native_iwyu.sh @@ -6,7 +6,7 @@ export LC_ALL=C.UTF-8 -export CI_IMAGE_NAME_TAG="mirror.gcr.io/debian:trixie" # To build codegen, CMake must be 3.31 or newer. +export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:26.04" # To build codegen, CMake must be 3.31 or newer. export CONTAINER_NAME=ci_native_iwyu export IWYU_LLVM_V="22" export APT_LLVM_V="${IWYU_LLVM_V}" diff --git a/ci/test/00_setup_env_native_msan.sh b/ci/test/00_setup_env_native_msan.sh index b5b3dbc047e4..18c6613f6a37 100755 --- a/ci/test/00_setup_env_native_msan.sh +++ b/ci/test/00_setup_env_native_msan.sh @@ -6,7 +6,7 @@ export LC_ALL=C.UTF-8 -export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:24.04" +export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:26.04" export APT_LLVM_V="22" LIBCXX_DIR="/cxx_build/" export MSAN_FLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O1 -fno-optimize-sibling-calls" diff --git a/ci/test/00_setup_env_native_tidy.sh b/ci/test/00_setup_env_native_tidy.sh index e74a30237025..102f2b23ef4f 100755 --- a/ci/test/00_setup_env_native_tidy.sh +++ b/ci/test/00_setup_env_native_tidy.sh @@ -6,7 +6,7 @@ export LC_ALL=C.UTF-8 -export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:24.04" +export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:26.04" export CONTAINER_NAME=ci_native_tidy export TIDY_LLVM_V="22" export APT_LLVM_V="${TIDY_LLVM_V}" diff --git a/ci/test/00_setup_env_native_valgrind.sh b/ci/test/00_setup_env_native_valgrind.sh index 21e844ccb8c2..67d6790e5593 100755 --- a/ci/test/00_setup_env_native_valgrind.sh +++ b/ci/test/00_setup_env_native_valgrind.sh @@ -6,7 +6,7 @@ export LC_ALL=C.UTF-8 -export CI_IMAGE_NAME_TAG="mirror.gcr.io/debian:trixie" +export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:26.04" export CONTAINER_NAME=ci_native_valgrind export PACKAGES="clang llvm libclang-rt-dev valgrind libevent-dev libboost-dev libcapnp-dev capnproto python3-pip" export PIP_PACKAGES="--break-system-packages pycapnp" diff --git a/ci/test/00_setup_env_s390x.sh b/ci/test/00_setup_env_s390x.sh index cfab5a19b2b5..324ec75b8e8d 100755 --- a/ci/test/00_setup_env_s390x.sh +++ b/ci/test/00_setup_env_s390x.sh @@ -8,7 +8,7 @@ export LC_ALL=C.UTF-8 export HOST=s390x-linux-gnu export CONTAINER_NAME=ci_s390x -export CI_IMAGE_NAME_TAG="mirror.gcr.io/debian:trixie" +export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:26.04" export CI_IMAGE_PLATFORM="linux/s390x" # bind tests excluded for now, see https://github.com/bitcoin/bitcoin/issues/17765#issuecomment-602068547 export TEST_RUNNER_EXTRA="--exclude rpc_bind --exclude feature_bind_extra" diff --git a/ci/test/01_base_install.sh b/ci/test/01_base_install.sh index 512d1a32e87c..54dafbe8fd40 100755 --- a/ci/test/01_base_install.sh +++ b/ci/test/01_base_install.sh @@ -62,10 +62,13 @@ if [ -n "$PIP_PACKAGES" ]; then fi if [[ -n "${USE_INSTRUMENTED_LIBCPP}" ]]; then - ${CI_RETRY_EXE} git clone --depth=1 https://github.com/llvm/llvm-project -b "llvmorg-22.1.3" /llvm-project + ${CI_RETRY_EXE} git clone --depth=1 https://github.com/llvm/llvm-project -b "llvmorg-22.1.7" /llvm-project +# LLVM is configured with LIBCXXABI_USE_LLVM_UNWINDER=OFF, +# because libunwind doesn't handle exceptions under MSAN. +# https://github.com/llvm/llvm-project/issues/84348 cmake -G Ninja -B /cxx_build/ \ - -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind" \ + -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \ -DCMAKE_BUILD_TYPE=Release \ -DLLVM_USE_SANITIZER="${USE_INSTRUMENTED_LIBCPP}" \ -DCMAKE_C_COMPILER=clang \ @@ -86,7 +89,10 @@ fi if [[ "${RUN_IWYU}" == true ]]; then ${CI_RETRY_EXE} git clone --depth=1 https://github.com/include-what-you-use/include-what-you-use -b clang_"${IWYU_LLVM_V}" /include-what-you-use - (cd /include-what-you-use && patch -p1 < /ci_container_base/ci/test/01_iwyu.patch) + pushd /include-what-you-use + patch -p1 < /ci_container_base/ci/test/01_iwyu.patch + patch -p1 < /ci_container_base/ci/test/02_iwyu_hash.patch + popd cmake -B /iwyu-build/ -G 'Unix Makefiles' -DCMAKE_PREFIX_PATH=/usr/lib/llvm-"${IWYU_LLVM_V}" -S /include-what-you-use make -C /iwyu-build/ install "$MAKEJOBS" fi diff --git a/ci/test/02_iwyu_hash.patch b/ci/test/02_iwyu_hash.patch new file mode 100644 index 000000000000..12df7a04ed5c --- /dev/null +++ b/ci/test/02_iwyu_hash.patch @@ -0,0 +1,44 @@ +Map std::hash to its providing standard headers. +Backport of https://github.com/include-what-you-use/include-what-you-use/pull/2013 +(commit 52f85e1f4d990f55fc6556d543eb051d79364a16) to the clang_22 release +branch. Drop once the upstream fix lands in the IWYU branch tracked by +ci/test/00_setup_env_native_iwyu.sh. + +--- a/std_symbol_map.inc ++++ b/std_symbol_map.inc +@@ -1054,12 +1054,27 @@ + { "std::has_unique_object_representations_v", kPrivate, "", kPublic }, + { "std::has_virtual_destructor", kPrivate, "", kPublic }, + { "std::has_virtual_destructor_v", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, + { "std::hash>", kPrivate, "", kPublic }, + { "std::hash, :0>>", kPrivate, "", kPublic }, + { "std::hash, :0>>", kPrivate, "", kPublic }, + { "std::hash, :0>>", kPrivate, "", kPublic }, + { "std::hash, :0>>", kPrivate, "", kPublic }, + { "std::hash, :0>>", kPrivate, "", kPublic }, ++{ "std::hash>", kPrivate, "", kPublic }, + { "std::hash>", kPrivate, "", kPublic }, + { "std::hash", kPrivate, "", kPublic }, + { "std::hash", kPrivate, "", kPublic }, +@@ -1069,6 +1084,7 @@ + { "std::hash>", kPrivate, "", kPublic }, + { "std::hash", kPrivate, "", kPublic }, + { "std::hash", kPrivate, "", kPublic }, ++{ "std::hash", kPrivate, "", kPublic }, + { "std::hash", kPrivate, "", kPublic }, + { "std::hash", kPrivate, "", kPublic }, + { "std::hash", kPrivate, "", kPublic }, diff --git a/ci/test_imagefile b/ci/test_imagefile index 908e9a0f5ab4..dac6b5531564 100644 --- a/ci/test_imagefile +++ b/ci/test_imagefile @@ -16,7 +16,8 @@ ENV BASE_ROOT_DIR=${BASE_ROOT_DIR} # Make retry available in PATH, needed for CI_RETRY_EXE COPY ./ci/retry/retry /usr/bin/retry -COPY ./ci/test/00_setup_env.sh ./${FILE_ENV} ./ci/test/01_base_install.sh ./ci/test/01_iwyu.patch /ci_container_base/ci/test/ +COPY ./ci/test/00_setup_env.sh ./${FILE_ENV} ./ci/test/01_base_install.sh /ci_container_base/ci/test/ +COPY ./ci/test/*.patch /ci_container_base/ci/test/ # Bash is required, so install it when missing RUN sh -c "bash -c 'true' || ( apk update && apk add --no-cache bash )" diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index cc1b3aba2c7d..98d1c486d86b 100755 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -5,73 +5,13 @@ export LC_ALL=C set -e -o pipefail -# Environment variables for determinism -export TAR_OPTIONS="--no-same-owner --owner=0 --group=0 --numeric-owner --mtime='@${SOURCE_DATE_EPOCH}' --sort=name" -export TZ=UTC - -# Although Guix _does_ set umask when building its own packages (in our case, -# this is all packages in manifest.scm), it does not set it for `guix -# shell`. It does make sense for at least `guix shell --container` -# to set umask, so if that change gets merged upstream and we bump the -# time-machine to a commit which includes the aforementioned change, we can -# remove this line. -# -# This line should be placed before any commands which creates files. -umask 0022 - -if [ -n "$V" ]; then - # Print both unexpanded (-v) and expanded (-x) forms of commands as they are - # read from this file. - set -vx - # Set VERBOSE for CMake-based builds - export VERBOSE="$V" -fi - -# Check that required environment variables are set -cat << EOF -Required environment variables as seen inside the container: - DIST_ARCHIVE_BASE: ${DIST_ARCHIVE_BASE:?not set} - DISTNAME: ${DISTNAME:?not set} - HOST: ${HOST:?not set} - SOURCE_DATE_EPOCH: ${SOURCE_DATE_EPOCH:?not set} - JOBS: ${JOBS:?not set} - DISTSRC: ${DISTSRC:?not set} - OUTDIR: ${OUTDIR:?not set} -EOF - -ACTUAL_OUTDIR="${OUTDIR}" -OUTDIR="${DISTSRC}/output" - -##################### -# Environment Setup # -##################### - -# The depends folder also serves as a base-prefix for depends packages for -# $HOSTs after successfully building. -BASEPREFIX="${PWD}/depends" - -# Given a package name and an output name, return the path of that output in our -# current guix environment -store_path() { - grep --extended-regexp "/[^-]{32}-${1}-[^-]+${2:+-${2}}" "${GUIX_ENVIRONMENT}/manifest" \ - | head --lines=1 \ - | sed --expression='s|\x29*$||' \ - --expression='s|^[[:space:]]*"||' \ - --expression='s|"[[:space:]]*$||' -} - +# shellcheck source=setup.sh +source "$(dirname "${BASH_SOURCE[0]}")/setup.sh" # Set environment variables to point the NATIVE toolchain to the right # includes/libs NATIVE_GCC="$(store_path gcc-toolchain)" -unset LIBRARY_PATH -unset CPATH -unset C_INCLUDE_PATH -unset CPLUS_INCLUDE_PATH -unset OBJC_INCLUDE_PATH -unset OBJCPLUS_INCLUDE_PATH - # Set native toolchain build_CC="${NATIVE_GCC}/bin/gcc -isystem ${NATIVE_GCC}/include" build_CXX="${NATIVE_GCC}/bin/g++ -isystem ${NATIVE_GCC}/include/c++ -isystem ${NATIVE_GCC}/include" @@ -134,15 +74,6 @@ for p in "${PATHS[@]}"; do fi done -# Disable Guix ld auto-rpath behavior -export GUIX_LD_WRAPPER_DISABLE_RPATH=yes - -# Make /usr/bin if it doesn't exist -[ -e /usr/bin ] || mkdir -p /usr/bin - -# Symlink env to a conventional path -[ -e /usr/bin/env ] || ln -s --no-dereference "$(command -v env)" /usr/bin/env - # Determine the correct value for -Wl,--dynamic-linker for the current $HOST case "$HOST" in *linux*) @@ -186,20 +117,6 @@ case "$HOST" in ;; esac -########################### -# Source Tarball Building # -########################### - -GIT_ARCHIVE="${DIST_ARCHIVE_BASE}/${DISTNAME}.tar.gz" - -# Create the source tarball if not already there -if [ ! -e "$GIT_ARCHIVE" ]; then - mkdir -p "$(dirname "$GIT_ARCHIVE")" - git archive --prefix="${DISTNAME}/" --output="$GIT_ARCHIVE" HEAD -fi - -mkdir -p "$OUTDIR" - ########################### # Binary Tarball Building # ########################### diff --git a/contrib/guix/libexec/setup.sh b/contrib/guix/libexec/setup.sh new file mode 100755 index 000000000000..617a7327f7bf --- /dev/null +++ b/contrib/guix/libexec/setup.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# Copyright (c) The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://opensource.org/license/mit. +export LC_ALL=C +set -e -o pipefail + +# Environment variables for determinism +export TAR_OPTIONS="--no-same-owner --owner=0 --group=0 --numeric-owner --mtime='@${SOURCE_DATE_EPOCH}' --sort=name" +export TZ=UTC + +# Although Guix _does_ set umask when building its own packages (in our case, +# this is all packages in manifest.scm), it does not set it for `guix +# shell`. It does make sense for at least `guix shell --container` +# to set umask, so if that change gets merged upstream and we bump the +# time-machine to a commit which includes the aforementioned change, we can +# remove this line. +# +# This line should be placed before any commands which creates files. +umask 0022 + +if [ -n "$V" ]; then + # Print both unexpanded (-v) and expanded (-x) forms of commands as they are + # read from this file. + set -vx + # Set VERBOSE for CMake-based builds + export VERBOSE="$V" +fi + +# Check that required environment variables are set +cat << EOF +Required environment variables as seen inside the container: + DIST_ARCHIVE_BASE: ${DIST_ARCHIVE_BASE:?not set} + DISTNAME: ${DISTNAME:?not set} + HOST: ${HOST:?not set} + SOURCE_DATE_EPOCH: ${SOURCE_DATE_EPOCH:?not set} + JOBS: ${JOBS:?not set} + DISTSRC: ${DISTSRC:?not set} + OUTDIR: ${OUTDIR:?not set} +EOF + +export ACTUAL_OUTDIR="${OUTDIR}" +export OUTDIR="${DISTSRC}/output" + +##################### +# Environment Setup # +##################### + +# The depends folder also serves as a base-prefix for depends packages for +# $HOSTs after successfully building. +export BASEPREFIX="${PWD}/depends" + +# Given a package name and an output name, return the path of that output in our +# current guix environment +store_path() { + grep --extended-regexp "/[^-]{32}-${1}-[^-]+${2:+-${2}}" "${GUIX_ENVIRONMENT}/manifest" \ + | head --lines=1 \ + | sed --expression='s|\x29*$||' \ + --expression='s|^[[:space:]]*"||' \ + --expression='s|"[[:space:]]*$||' +} + +# Disable Guix ld auto-rpath behavior +export GUIX_LD_WRAPPER_DISABLE_RPATH=yes + +# Make /usr/bin if it doesn't exist +[ -e /usr/bin ] || mkdir -p /usr/bin + +# Symlink env to a conventional path +[ -e /usr/bin/env ] || ln -s --no-dereference "$(command -v env)" /usr/bin/env + +########################### +# Source Tarball Building # +########################### + +GIT_ARCHIVE="${DIST_ARCHIVE_BASE}/${DISTNAME}.tar.gz" + +# Create the source tarball if not already there +if [ ! -e "$GIT_ARCHIVE" ]; then + mkdir -p "$(dirname "$GIT_ARCHIVE")" + git archive --prefix="${DISTNAME}/" --output="$GIT_ARCHIVE" HEAD +fi + +mkdir -p "$OUTDIR" + +unset LIBRARY_PATH +unset CPATH +unset C_INCLUDE_PATH +unset CPLUS_INCLUDE_PATH +unset OBJC_INCLUDE_PATH +unset OBJCPLUS_INCLUDE_PATH diff --git a/doc/bips.md b/doc/bips.md index 5a9248bd83b7..0a8381e26117 100644 --- a/doc/bips.md +++ b/doc/bips.md @@ -68,3 +68,4 @@ BIPs that are implemented by Bitcoin Core: * [`BIP 390`](https://github.com/bitcoin/bips/blob/master/bip-0390.mediawiki): MuSig2 Descriptor parsing is implemented in **v30.0** ([PR 31244](https://github.com/bitcoin/bitcoin/pull/31244)) and signing in **v31.0** ([PR 29675](https://github.com/bitcoin/bitcoin/pull/29675)) * [`BIP 431`](https://github.com/bitcoin/bips/blob/master/bip-0431.mediawiki): transactions with nVersion=3 are standard and treated as Topologically Restricted Until Confirmation as of **v28.0** ([PR 29496](https://github.com/bitcoin/bitcoin/pull/29496)). * [`BIP 433`](https://github.com/bitcoin/bips/blob/master/bip-0433.mediawiki): Spending of Pay to Anchor (P2A) outputs is standard as of **v28.0** ([PR 30352](https://github.com/bitcoin/bitcoin/pull/30352)). +* [`BIP 434`](https://github.com/bitcoin/bips/blob/master/bip-0434.md): Peer Feature Negotiation as of **v32.0** ([PR 35221](https://github.com/bitcoin/bitcoin/pull/35221)). diff --git a/src/bench/readwriteblock.cpp b/src/bench/readwriteblock.cpp index 87551eb9a8e0..ef567931b230 100644 --- a/src/bench/readwriteblock.cpp +++ b/src/bench/readwriteblock.cpp @@ -32,6 +32,7 @@ static void WriteBlockBench(benchmark::Bench& bench) auto& blockman{testing_setup->m_node.chainman->m_blockman}; const CBlock block{CreateTestBlock()}; bench.run([&] { + LOCK(::cs_main); const auto pos{blockman.WriteBlock(block, 413'567)}; assert(!pos.IsNull()); }); @@ -43,7 +44,7 @@ static void ReadBlockBench(benchmark::Bench& bench) auto& blockman{testing_setup->m_node.chainman->m_blockman}; const auto& test_block{CreateTestBlock()}; const auto& expected_hash{test_block.GetHash()}; - const auto& pos{blockman.WriteBlock(test_block, 413'567)}; + const auto& pos{WITH_LOCK(::cs_main, return blockman.WriteBlock(test_block, 413'567))}; bench.run([&] { CBlock block; const auto success{blockman.ReadBlock(block, pos, expected_hash)}; @@ -55,7 +56,7 @@ static void ReadRawBlockBench(benchmark::Bench& bench) { const auto testing_setup{MakeNoLogFileContext(ChainType::MAIN)}; auto& blockman{testing_setup->m_node.chainman->m_blockman}; - const auto pos{blockman.WriteBlock(CreateTestBlock(), 413'567)}; + const auto pos{WITH_LOCK(::cs_main, return blockman.WriteBlock(CreateTestBlock(), 413'567))}; bench.run([&] { const auto res{blockman.ReadRawBlock(pos)}; assert(res); diff --git a/src/bench/util_time.cpp b/src/bench/util_time.cpp index 19ffd0e04985..0079549c0d1f 100644 --- a/src/bench/util_time.cpp +++ b/src/bench/util_time.cpp @@ -15,7 +15,7 @@ static void BenchTimeDeprecated(benchmark::Bench& bench) static void BenchTimeMock(benchmark::Bench& bench) { - NodeClockContext clock_ctx{111s}; + FakeNodeClock clock{111s}; bench.run([&] { (void)GetTime(); }); diff --git a/src/bip324.h b/src/bip324.h index 396a28a44894..821cc3f759fc 100644 --- a/src/bip324.h +++ b/src/bip324.h @@ -15,6 +15,8 @@ #include #include +static constexpr unsigned BIP324_SHORTIDS_IMPLEMENTED{38}; + /** The BIP324 packet cipher, encapsulating its key derivation, stream cipher, and AEAD. */ class BIP324Cipher { diff --git a/src/btcsignals.h b/src/btcsignals.h index ebe7a5acb783..9eeca7699618 100644 --- a/src/btcsignals.h +++ b/src/btcsignals.h @@ -123,11 +123,11 @@ class scoped_connection scoped_connection(connection rhs) noexcept : m_conn{std::move(rhs)} {} scoped_connection(scoped_connection&&) noexcept = default; - scoped_connection& operator=(scoped_connection&&) noexcept = default; /** - * For simplicity, disable copy assignment and construction. + * For simplicity, disable copy construction and copy/move assignment. */ + scoped_connection& operator=(scoped_connection&&) = delete; scoped_connection& operator=(const scoped_connection&) = delete; scoped_connection(const scoped_connection&) = delete; diff --git a/src/chainstate.cpp b/src/chainstate.cpp index 12b81ebc15be..3b6d07ff5599 100644 --- a/src/chainstate.cpp +++ b/src/chainstate.cpp @@ -293,7 +293,6 @@ bool Chainstate::FlushStateToDisk( bool fFlushForPrune = false; CoinsCacheSizeState cache_state = GetCoinsCacheSizeState(); - LOCK(m_blockman.cs_LastBlockFile); if (m_blockman.IsPruneMode() && (m_blockman.m_check_for_pruning || nManualPruneHeight > 0) && m_chainman.m_blockman.m_blockfiles_indexed) { // make sure we don't prune above any of the prune locks bestblocks // pruning is height-based diff --git a/src/crypto/hmac_sha256.cpp b/src/crypto/hmac_sha256.cpp index a95ef70849b5..0796bbeb3271 100644 --- a/src/crypto/hmac_sha256.cpp +++ b/src/crypto/hmac_sha256.cpp @@ -5,6 +5,7 @@ #include #include +#include #include @@ -26,6 +27,8 @@ CHMAC_SHA256::CHMAC_SHA256(const unsigned char* key, size_t keylen) for (int n = 0; n < 64; n++) rkey[n] ^= 0x5c ^ 0x36; inner.Write(rkey, 64); + + memory_cleanse(rkey, sizeof(rkey)); } void CHMAC_SHA256::Finalize(unsigned char hash[OUTPUT_SIZE]) @@ -33,4 +36,5 @@ void CHMAC_SHA256::Finalize(unsigned char hash[OUTPUT_SIZE]) unsigned char temp[32]; inner.Finalize(temp); outer.Write(temp, 32).Finalize(hash); + memory_cleanse(temp, sizeof(temp)); } diff --git a/src/crypto/hmac_sha512.cpp b/src/crypto/hmac_sha512.cpp index f37e709d13cf..0a9d1041a67d 100644 --- a/src/crypto/hmac_sha512.cpp +++ b/src/crypto/hmac_sha512.cpp @@ -5,6 +5,7 @@ #include #include +#include #include @@ -26,6 +27,8 @@ CHMAC_SHA512::CHMAC_SHA512(const unsigned char* key, size_t keylen) for (int n = 0; n < 128; n++) rkey[n] ^= 0x5c ^ 0x36; inner.Write(rkey, 128); + + memory_cleanse(rkey, sizeof(rkey)); } void CHMAC_SHA512::Finalize(unsigned char hash[OUTPUT_SIZE]) @@ -33,4 +36,5 @@ void CHMAC_SHA512::Finalize(unsigned char hash[OUTPUT_SIZE]) unsigned char temp[64]; inner.Finalize(temp); outer.Write(temp, 64).Finalize(hash); + memory_cleanse(temp, sizeof(temp)); } diff --git a/src/hash.h b/src/hash.h index 34486af64a1d..b671761fbb6e 100644 --- a/src/hash.h +++ b/src/hash.h @@ -13,12 +13,20 @@ #include #include #include +#include #include #include #include -typedef uint256 ChainCode; +/** A BIP32 chain code. Cleansed on destruction. */ +class ChainCode : public base_blob<256> { +public: + constexpr ChainCode() = default; + constexpr explicit ChainCode(std::span vch) : base_blob<256>(vch) {} + constexpr explicit ChainCode(const base_blob<256>& b) : base_blob<256>(b) {} + ~ChainCode() { memory_cleanse(data(), size()); } +}; /** A hasher class for Bitcoin's 256-bit hash (double SHA-256). */ class CHash256 { diff --git a/src/logging/timer.h b/src/logging/timer.h index 2b183822e969..1706c9e71bb3 100644 --- a/src/logging/timer.h +++ b/src/logging/timer.h @@ -99,13 +99,13 @@ class Timer #define LOG_TIME_MICROS_WITH_CATEGORY(end_msg, log_category) \ - BCLog::Timer UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category) + BCLog::Timer BITCOIN_UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category) #define LOG_TIME_MILLIS_WITH_CATEGORY(end_msg, log_category) \ - BCLog::Timer UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category) + BCLog::Timer BITCOIN_UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category) #define LOG_TIME_MILLIS_WITH_CATEGORY_MSG_ONCE(end_msg, log_category) \ - BCLog::Timer UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category, /* msg_on_completion=*/false) + BCLog::Timer BITCOIN_UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category, /* msg_on_completion=*/false) #define LOG_TIME_SECONDS(end_msg) \ - BCLog::Timer UNIQUE_NAME(logging_timer)(__func__, end_msg) + BCLog::Timer BITCOIN_UNIQUE_NAME(logging_timer)(__func__, end_msg) #endif // BITCOIN_LOGGING_TIMER_H diff --git a/src/musig.cpp b/src/musig.cpp index d211790c43b2..d187ad001338 100644 --- a/src/musig.cpp +++ b/src/musig.cpp @@ -11,10 +11,7 @@ //! MuSig2 chaincode as defined by BIP 328 using namespace util::hex_literals; -constexpr uint256 MUSIG_CHAINCODE{ - // Use immediate lambda to work around GCC-14 bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117966 - []() consteval { return uint256{"868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965"_hex_u8}; }(), -}; +const ChainCode MUSIG_CHAINCODE{"868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965"_hex_u8}; static bool GetMuSig2KeyAggCache(const std::vector& pubkeys, secp256k1_musig_keyagg_cache& keyagg_cache) { diff --git a/src/net.cpp b/src/net.cpp index 00e7c1f8ccd2..ef5049bb30e1 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -296,7 +297,7 @@ bool AddLocal(const CService& addr_, int nScore) const auto [it, is_newly_added] = mapLocalHost.emplace(addr, LocalServiceInfo()); LocalServiceInfo &info = it->second; if (is_newly_added || nScore >= info.nScore) { - info.nScore = nScore + (is_newly_added ? 0 : 1); + info.nScore = SaturatingAdd(nScore, is_newly_added ? 0 : 1); info.nPort = addr.GetPort(); } } @@ -325,7 +326,7 @@ bool SeenLocal(const CService& addr) LOCK(g_maplocalhost_mutex); const auto it = mapLocalHost.find(addr); if (it == mapLocalHost.end()) return false; - ++it->second.nScore; + it->second.nScore = SaturatingAdd(it->second.nScore, 1); return true; } @@ -921,7 +922,7 @@ namespace { * Only message types that are actually implemented in this codebase need to be listed, as other * messages get ignored anyway - whether we know how to decode them or not. */ -const std::array V2_MESSAGE_IDS = { +const std::array V2_MESSAGE_IDS = { "", // 12 bytes follow encoding the message type like in V1 NetMsgType::ADDR, NetMsgType::BLOCK, @@ -951,11 +952,10 @@ const std::array V2_MESSAGE_IDS = { "", // formerly NetMsgType::GETCFCHECKPT "", // formerly NetMsgType::CFCHECKPT NetMsgType::ADDRV2, - // Unimplemented message types that are assigned in BIP324: - "", - "", - "", - "" + "", "", "", // Unimplemented message types 29-31 + "", "", "", "", // Unimplemented message types 32-35 + "", // Unimplemented message type 36 + NetMsgType::FEATURE, }; class V2MessageMap diff --git a/src/net.h b/src/net.h index 596f636af525..d4142107f319 100644 --- a/src/net.h +++ b/src/net.h @@ -831,6 +831,13 @@ class CNode return m_conn_type == ConnectionType::PRIVATE_BROADCAST; } + /** Protocol version advertised in our VERSION message. + * Private broadcast connections use a fixed version to maximise anonymity. */ + int AdvertisedVersion() const + { + return IsPrivateBroadcastConn() ? WTXID_RELAY_VERSION : PROTOCOL_VERSION; + } + bool IsInboundConn() const { return m_conn_type == ConnectionType::INBOUND; } diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 1e6d1a6a78e4..e4ed6081c7ad 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -569,6 +569,9 @@ class PeerManagerImpl final : public PeerManager * May return an empty shared_ptr if the Peer object can't be found. */ PeerRef RemovePeer(NodeId id) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + /// Get all existing peers in m_peer_map. + std::vector GetAllPeers() const EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + /** Mark a peer as misbehaving, which will cause it to be disconnected and its * address discouraged. */ void Misbehaving(Peer& peer, const std::string& message); @@ -716,6 +719,15 @@ class PeerManagerImpl final : public PeerManager { m_connman.PushMessage(&node, NetMsg::Make(std::move(msg_type), std::forward(args)...)); } + template + void MakeAndPushFeature(CNode& node, std::string_view feature_id, Args&&... args) const + { + if (!Assume(feature_id.size() >= 4 && feature_id.size() <= MAX_FEATUREID_LENGTH)) return; + std::vector feature_data; + VectorWriter{feature_data, 0, std::forward(args)...}; + if (!Assume(feature_data.size() <= MAX_FEATUREDATA_LENGTH)) return; + MakeAndPushMessage(node, NetMsgType::FEATURE, feature_id, std::move(feature_data)); + } /** Send a version message to a peer */ void PushNodeVersion(CNode& pnode, const Peer& peer); @@ -1474,7 +1486,7 @@ void PeerManagerImpl::PushNodeVersion(CNode& pnode, const Peer& peer) MakeAndPushMessage( pnode, NetMsgType::VERSION, - PROTOCOL_VERSION, + pnode.AdvertisedVersion(), my_services, my_time, // your_services + CNetAddr::V1(your_addr) is the pre-version-31402 serialization of your_addr (without nTime) @@ -1488,7 +1500,7 @@ void PeerManagerImpl::PushNodeVersion(CNode& pnode, const Peer& peer) LogDebug( BCLog::NET, "send version message: version=%d, blocks=%d%s, txrelay=%d, peer=%d\n", - PROTOCOL_VERSION, my_height, + pnode.AdvertisedVersion(), my_height, fLogIPs ? strprintf(", them=%s", your_addr.ToStringAddrPort()) : "", my_tx_relay, pnode.GetId()); } @@ -1684,6 +1696,17 @@ PeerRef PeerManagerImpl::RemovePeer(NodeId id) return ret; } +std::vector PeerManagerImpl::GetAllPeers() const +{ + std::vector peers; + LOCK(m_peer_mutex); + peers.reserve(m_peer_map.size()); + for (const auto& [_, peer] : m_peer_map) { + peers.push_back(peer); + } + return peers; +} + bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const { { @@ -2137,9 +2160,10 @@ void PeerManagerImpl::SendPings() void PeerManagerImpl::InitiateTxBroadcastToAll(const Txid& txid, const Wtxid& wtxid) { - LOCK(m_peer_mutex); - for(auto& it : m_peer_map) { - Peer& peer = *it.second; + for (const PeerRef& peer_ref : GetAllPeers()) { + if (!peer_ref) continue; + Peer& peer{*peer_ref}; + auto tx_relay = peer.GetTxRelay(); if (!tx_relay) continue; @@ -3395,7 +3419,7 @@ void PeerManagerImpl::ProcessMessage(Peer& peer, CNode& pfrom, const std::string } // Change version - const int greatest_common_version = std::min(nVersion, PROTOCOL_VERSION); + const int greatest_common_version = std::min(nVersion, pfrom.AdvertisedVersion()); pfrom.SetCommonVersion(greatest_common_version); pfrom.nVersion = nVersion; @@ -3470,6 +3494,11 @@ void PeerManagerImpl::ProcessMessage(Peer& peer, CNode& pfrom, const std::string } } + if (greatest_common_version >= FEATURE_VERSION) { + // announce supported features + // MakeAndPushFeature(pfrom, NetMsgFeature::FOO, uint32_t{1}); + } + MakeAndPushMessage(pfrom, NetMsgType::VERACK); // Potentially mark this peer as a preferred download peer. @@ -3685,6 +3714,45 @@ void PeerManagerImpl::ProcessMessage(Peer& peer, CNode& pfrom, const std::string return; } + if (msg_type == NetMsgType::FEATURE) { + if (pfrom.fSuccessfullyConnected) { + // Disconnect peers that send a FEATURE message after VERACK. + LogDebug(BCLog::NET, "feature received after verack, %s", pfrom.DisconnectMsg()); + pfrom.fDisconnect = true; + return; + } else if (pfrom.GetCommonVersion() < FEATURE_VERSION) { + // Disconnect peers that send a FEATURE message without valid version negotiation. + LogDebug(BCLog::NET, "feature received with incompatible version %d, %s", pfrom.GetCommonVersion(), pfrom.DisconnectMsg()); + pfrom.fDisconnect = true; + return; + } + + std::string feature_id; + DataStream feature_data; + try { + vRecv >> LIMITED_STRING(feature_id, MAX_FEATUREID_LENGTH); + std::vector feature_data_vec; + vRecv >> LIMITED_VECTOR(feature_data_vec, MAX_FEATUREDATA_LENGTH); + feature_data = DataStream(feature_data_vec); + } catch (const std::exception&) { + feature_id.clear(); // use empty feature_id as error indicator + } + if (feature_id.size() < 4 || !vRecv.empty()) { + LogDebug(BCLog::NET, "invalid feature payload, %s", pfrom.DisconnectMsg()); + pfrom.fDisconnect = true; + return; + } + + // if (feature_id == NetMsgFeature::FOO) { + // ... + // return; + // } + + // ignore unknown feature_id + LogDebug(BCLog::NET, "unknown feature advertised: %s", SanitizeString(feature_id)); + return; + } + // Received from a peer demonstrating readiness to announce transactions via reconciliations. // This feature negotiation must happen between VERSION and VERACK to avoid relay problems // from switching announcement protocols after the connection is up. diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index f5f274cb9620..23ac06077889 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -254,7 +254,6 @@ CBlockIndex* BlockManager::AddToBlockIndex(const CBlockHeader& block, CBlockInde void BlockManager::PruneOneBlockFile(const int fileNumber) { AssertLockHeld(cs_main); - LOCK(cs_LastBlockFile); for (auto& entry : m_block_index) { CBlockIndex* pindex = &entry.second; @@ -292,7 +291,7 @@ void BlockManager::FindFilesToPruneManual( { assert(IsPruneMode() && nManualPruneHeight > 0); - LOCK2(cs_main, cs_LastBlockFile); + LOCK(::cs_main); if (chain.m_chain.Height() < 0) { return; } @@ -320,7 +319,7 @@ void BlockManager::FindFilesToPrune( const Chainstate& chain, ChainstateManager& chainman) { - LOCK2(cs_main, cs_LastBlockFile); + LOCK(::cs_main); // Compute `target` value with maximum size (in bytes) of blocks below the // `last_prune` height which should be preserved and not pruned. const auto target = std::max( @@ -447,7 +446,9 @@ bool BlockManager::LoadBlockIndex() pindex->m_chain_tx_count = pindex->pprev->m_chain_tx_count + pindex->nTx; } else { pindex->m_chain_tx_count = 0; - m_blocks_unlinked.insert(std::make_pair(pindex->pprev, pindex)); + if (pindex->nStatus & BLOCK_HAVE_DATA) { + m_blocks_unlinked.insert(std::make_pair(pindex->pprev, pindex)); + } } } else { pindex->m_chain_tx_count = pindex->nTx; @@ -488,12 +489,13 @@ void BlockManager::WriteBlockIndexDB() vBlocks.push_back(*it); m_dirty_blockindex.erase(it++); } - int max_blockfile = WITH_LOCK(cs_LastBlockFile, return this->MaxBlockfileNum()); + int max_blockfile{this->MaxBlockfileNum()}; m_block_tree_db->WriteBatchSync(vFiles, max_blockfile, vBlocks); } bool BlockManager::LoadBlockIndexDB() { + AssertLockHeld(::cs_main); if (!LoadBlockIndex()) { return false; } @@ -533,7 +535,6 @@ bool BlockManager::LoadBlockIndexDB() { // Initialize the blockfile cursor. - LOCK(cs_LastBlockFile); if (!m_blockfile_info.empty()) { m_blockfile_cursor = {static_cast(m_blockfile_info.size() - 1), 0}; } @@ -556,7 +557,7 @@ bool BlockManager::LoadBlockIndexDB() void BlockManager::ScanAndUnlinkAlreadyPrunedFiles() { AssertLockHeld(::cs_main); - int max_blockfile = WITH_LOCK(cs_LastBlockFile, return this->MaxBlockfileNum()); + int max_blockfile{this->MaxBlockfileNum()}; if (!m_have_pruned) { return; } @@ -654,8 +655,7 @@ void BlockManager::CleanupBlockRevFiles() const CBlockFileInfo* BlockManager::GetBlockFileInfo(size_t n) { - LOCK(cs_LastBlockFile); - + AssertLockHeld(::cs_main); return &m_blockfile_info.at(n); } @@ -706,8 +706,8 @@ bool BlockManager::FlushUndoFile(int block_file, bool finalize) bool BlockManager::FlushBlockFile(int blockfile_num, bool fFinalize, bool finalize_undo) { + AssertLockHeld(::cs_main); bool success = true; - LOCK(cs_LastBlockFile); if (m_blockfile_info.size() < 1) { // Return if we haven't loaded any blockfiles yet. This happens during @@ -735,14 +735,13 @@ bool BlockManager::FlushBlockFile(int blockfile_num, bool fFinalize, bool finali bool BlockManager::FlushChainstateBlockFile(int tip_height) { - LOCK(cs_LastBlockFile); + AssertLockHeld(::cs_main); return FlushBlockFile(m_blockfile_cursor.file_num, /*fFinalize=*/false, /*finalize_undo=*/false); } uint64_t BlockManager::CalculateCurrentUsage() { - LOCK(cs_LastBlockFile); - + AssertLockHeld(::cs_main); uint64_t retval = 0; for (const CBlockFileInfo& file : m_blockfile_info) { retval += file.nSize + file.nUndoSize; @@ -781,7 +780,7 @@ fs::path BlockManager::GetBlockPosFilename(const FlatFilePos& pos) const FlatFilePos BlockManager::FindNextBlockPos(unsigned int nAddSize, unsigned int nHeight, uint64_t nTime) { - LOCK(cs_LastBlockFile); + AssertLockHeld(::cs_main); const int last_blockfile = m_blockfile_cursor.file_num; @@ -862,8 +861,7 @@ FlatFilePos BlockManager::FindNextBlockPos(unsigned int nAddSize, unsigned int n void BlockManager::UpdateBlockInfo(const CBlock& block, unsigned int nHeight, const FlatFilePos& pos) { - LOCK(cs_LastBlockFile); - + AssertLockHeld(::cs_main); // Update the cursor so it points to the last file. if (m_blockfile_cursor.file_num < pos.nFile) { m_blockfile_cursor = BlockfileCursor{pos.nFile}; @@ -882,10 +880,9 @@ void BlockManager::UpdateBlockInfo(const CBlock& block, unsigned int nHeight, co bool BlockManager::FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize) { + AssertLockHeld(::cs_main); pos.nFile = nFile; - LOCK(cs_LastBlockFile); - pos.nPos = m_blockfile_info[nFile].nUndoSize; m_blockfile_info[nFile].nUndoSize += nAddSize; m_dirty_fileinfo.insert(nFile); @@ -905,7 +902,6 @@ bool BlockManager::FindUndoPos(BlockValidationState& state, int nFile, FlatFileP bool BlockManager::WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex& block) { AssertLockHeld(::cs_main); - LOCK(cs_LastBlockFile); auto& cursor{m_blockfile_cursor}; // Write undo information to disk @@ -1071,6 +1067,7 @@ BlockManager::ReadRawBlockResult BlockManager::ReadRawBlock(const FlatFilePos& p FlatFilePos BlockManager::WriteBlock(const CBlock& block, int nHeight) { + AssertLockHeld(::cs_main); const unsigned int block_size{static_cast(GetSerializeSize(TX_WITH_WITNESS(block)))}; FlatFilePos pos{FindNextBlockPos(block_size + STORAGE_HEADER_BYTES, nHeight, block.GetBlockTime())}; if (pos.IsNull()) { diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index 99a0c5919210..00a2da5e4117 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -199,7 +199,7 @@ class BlockManager EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Return false if block file or undo file flushing fails. */ - [[nodiscard]] bool FlushBlockFile(int blockfile_num, bool fFinalize, bool finalize_undo); + [[nodiscard]] bool FlushBlockFile(int blockfile_num, bool fFinalize, bool finalize_undo) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); /** Return false if undo file flushing fails. */ [[nodiscard]] bool FlushUndoFile(int block_file, bool finalize = false); @@ -213,9 +213,9 @@ class BlockManager * The nAddSize argument passed to this function should include not just the size of the serialized CBlock, but also the size of * separator fields (STORAGE_HEADER_BYTES). */ - [[nodiscard]] FlatFilePos FindNextBlockPos(unsigned int nAddSize, unsigned int nHeight, uint64_t nTime); - [[nodiscard]] bool FlushChainstateBlockFile(int tip_height); - bool FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize); + [[nodiscard]] FlatFilePos FindNextBlockPos(unsigned int nAddSize, unsigned int nHeight, uint64_t nTime) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + [[nodiscard]] bool FlushChainstateBlockFile(int tip_height) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + [[nodiscard]] bool FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); AutoFile OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false) const; @@ -247,13 +247,12 @@ class BlockManager const Chainstate& chain, ChainstateManager& chainman); - RecursiveMutex cs_LastBlockFile; - //! The current blockfile cursor used for appending new blocks. - BlockfileCursor m_blockfile_cursor GUARDED_BY(cs_LastBlockFile){}; + BlockfileCursor m_blockfile_cursor GUARDED_BY(::cs_main){}; - int MaxBlockfileNum() const EXCLUSIVE_LOCKS_REQUIRED(cs_LastBlockFile) + int MaxBlockfileNum() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + AssertLockHeld(::cs_main); return m_blockfile_cursor.file_num; } @@ -341,7 +340,7 @@ class BlockManager const CBlockIndex* LookupBlockIndex(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Get block file info entry for one block file */ - CBlockFileInfo* GetBlockFileInfo(size_t n); + CBlockFileInfo* GetBlockFileInfo(size_t n) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex& block) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); @@ -354,7 +353,7 @@ class BlockManager * @returns in case of success, the position to which the block was written to * in case of an error, an empty FlatFilePos */ - FlatFilePos WriteBlock(const CBlock& block, int nHeight); + FlatFilePos WriteBlock(const CBlock& block, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); /** Update blockfile info while processing a block during reindex. The block must be available on disk. * @@ -362,7 +361,7 @@ class BlockManager * @param[in] nHeight the height of the block * @param[in] pos the position of the serialized CBlock on disk */ - void UpdateBlockInfo(const CBlock& block, unsigned int nHeight, const FlatFilePos& pos); + void UpdateBlockInfo(const CBlock& block, unsigned int nHeight, const FlatFilePos& pos) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); /** Whether running in -prune mode. */ [[nodiscard]] bool IsPruneMode() const { return m_prune_mode; } @@ -374,7 +373,7 @@ class BlockManager [[nodiscard]] bool LoadingBlocks() const { return m_importing || !m_blockfiles_indexed; } /** Calculate the amount of disk space the block & undo files currently use */ - uint64_t CalculateCurrentUsage(); + uint64_t CalculateCurrentUsage() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); //! Check if all blocks in the [upper_block, lower_block] range have data available as //! defined by the status mask. diff --git a/src/node/protocol_version.h b/src/node/protocol_version.h index 7904086fe555..a72ac7774655 100644 --- a/src/node/protocol_version.h +++ b/src/node/protocol_version.h @@ -9,7 +9,7 @@ * network protocol versioning */ -static const int PROTOCOL_VERSION = 70016; +static const int PROTOCOL_VERSION = 70017; //! initial proto version, to be increased after version/verack negotiation static const int INIT_PROTO_VERSION = 209; @@ -35,4 +35,7 @@ static const int INVALID_CB_NO_BAN_VERSION = 70015; //! "wtxidrelay" message type for wtxid-based relay starts with this version static const int WTXID_RELAY_VERSION = 70016; +//! "feature" message type for feature negotiation starts with this version +static const int FEATURE_VERSION = 70017; + #endif // BITCOIN_NODE_PROTOCOL_VERSION_H diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 34bb9571c153..3a7735e149b6 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -403,4 +403,16 @@ struct CMutableTransaction typedef std::shared_ptr CTransactionRef; template static inline CTransactionRef MakeTransactionRef(Tx&& txIn) { return std::make_shared(std::forward(txIn)); } +namespace std { +/** Disable default std::hash for CTransactionRef to prevent accidentally + * comparing by pointer. Use CTransactionRefHash or provide a custom + * hasher. */ +template <> +struct hash { + hash() = delete; + // Belt-and-suspenders, already implied by the above. + size_t operator()(const CTransactionRef&) const = delete; +}; +} // namespace std + #endif // BITCOIN_PRIMITIVES_TRANSACTION_H diff --git a/src/protocol.h b/src/protocol.h index 69038cb6a295..4e440c5f2d8b 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -228,6 +228,10 @@ inline constexpr const char* WTXIDRELAY{"wtxidrelay"}; * txreconciliation, as described by BIP 330. */ inline constexpr const char* SENDTXRCNCL{"sendtxrcncl"}; +/** + * BIP 434 Peer feature negotiation + */ +inline constexpr const char* FEATURE{"feature"}; }; // namespace NetMsgType /** All known message types (see above). Keep this in the same order as the list of messages above. */ @@ -261,8 +265,16 @@ inline const std::array ALL_NET_MESSAGE_TYPES{std::to_array({ NetMsgType::BLOCKTXN, NetMsgType::WTXIDRELAY, NetMsgType::SENDTXRCNCL, + NetMsgType::FEATURE, })}; +static constexpr size_t MAX_FEATUREID_LENGTH{80}; +static constexpr size_t MAX_FEATUREDATA_LENGTH{512}; + +namespace NetMsgFeature { +//inline constexpr std::string_view FOO{"BIP-FOO"}; +} + /** nServices flags */ enum ServiceFlags : uint64_t { // NOTE: When adding here, be sure to update serviceFlagToStr too diff --git a/src/pubkey.h b/src/pubkey.h index 02ad7371a796..0391609ebcc3 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -27,8 +27,6 @@ class CKeyID : public uint160 explicit CKeyID(const uint160& in) : uint160(in) {} }; -typedef uint256 ChainCode; - /** An encapsulated public key. */ class CPubKey { diff --git a/src/script/miniscript.h b/src/script/miniscript.h index 1e25a30e3e9c..4e8beb19e6bb 100644 --- a/src/script/miniscript.h +++ b/src/script/miniscript.h @@ -18,7 +18,6 @@ #include #include -#include #include #include #include diff --git a/src/serialize.h b/src/serialize.h index 5cef72a9e03d..5d38a5100760 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -493,6 +494,7 @@ static inline Wrapper Using(T&& t) { return Wrapper>(obj) #define COMPACTSIZE(obj) Using>(obj) #define LIMITED_STRING(obj,n) Using>(obj) +#define LIMITED_VECTOR(obj,n) Using>(obj) /** Serialization wrapper class for integers in VarInt format. */ template @@ -705,6 +707,12 @@ struct VectorFormatter template void Serialize(Stream& os, const std::basic_string& str); template void Unserialize(Stream& is, std::basic_string& str); +/** + * string_view + */ +template void Serialize(Stream& os, const std::basic_string_view& str); +template void Unserialize(Stream& is, std::basic_string_view& str) = delete; + /** * prevector */ @@ -783,6 +791,35 @@ struct DefaultFormatter static void Unser(Stream& s, T& t) { Unserialize(s, t); } }; +/** + * Limited vector formatter. Throws an error if a vector is oversized. + */ + +template +struct LimitedVectorFormatter +{ + template + void Unser(Stream& s, V& v) + { + Formatter formatter; + v.clear(); + size_t size = ReadCompactSize(s); + if (size > Limit) { + throw std::ios_base::failure("Vector length limit exceeded"); + } + v.reserve(size); + for (size_t i = 0; i < size; ++i) { + v.emplace_back(); + formatter.Unser(s, v.back()); + } + } + + template + void Ser(Stream& s, const V& v) + { + VectorFormatter{}.Ser(s, v); + } +}; @@ -807,7 +844,17 @@ void Unserialize(Stream& is, std::basic_string& str) is.read(MakeWritableByteSpan(str)); } - +/** + * string_view + */ +template +void Serialize(Stream& os, const std::basic_string_view& str) +{ + WriteCompactSize(os, str.size()); + if (!str.empty()) { + os.write(MakeByteSpan(str)); + } +} /** * prevector diff --git a/src/sync.h b/src/sync.h index 123184d44f61..28bc78e91157 100644 --- a/src/sync.h +++ b/src/sync.h @@ -251,7 +251,7 @@ class SCOPED_LOCKABLE UniqueLock : public MutexType::unique_lock // it is not possible to use the lock's copy of the mutex for that purpose. // Instead, the original mutex needs to be passed back to the reverse_lock for // the sake of thread-safety analysis, but it is not actually used otherwise. -#define REVERSE_LOCK(g, cs) typename std::decay::type::reverse_lock UNIQUE_NAME(revlock)(g, cs, #cs, __FILE__, __LINE__) +#define REVERSE_LOCK(g, cs) typename std::decay::type::reverse_lock BITCOIN_UNIQUE_NAME(revlock)(g, cs, #cs, __FILE__, __LINE__) // When locking a Mutex, require negative capability to ensure the lock // is not already held @@ -265,7 +265,7 @@ inline MutexType& MaybeCheckNotHeld(MutexType& m) LOCKS_EXCLUDED(m) LOCK_RETURNE template inline MutexType* MaybeCheckNotHeld(MutexType* m) LOCKS_EXCLUDED(m) LOCK_RETURNED(m) { return m; } -#define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) +#define LOCK(cs) UniqueLock BITCOIN_UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) #define LOCK2(cs1, cs2) \ UniqueLock criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \ UniqueLock criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__) diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index cd1c651a08c2..117ca8c6a142 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -97,8 +97,7 @@ BOOST_AUTO_TEST_CASE(addrman_simple) BOOST_AUTO_TEST_CASE(addrman_terrible_many_failures) { - auto now = Now(); - SetMockTime(now - (ADDRMAN_MIN_FAIL + 24h)); + FakeNodeClock clock{}; auto addrman{std::make_unique(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node))}; @@ -109,7 +108,7 @@ BOOST_AUTO_TEST_CASE(addrman_terrible_many_failures) BOOST_CHECK(addrman->Add({addr}, source)); BOOST_CHECK(addrman->Good(addr)); - SetMockTime(now); + clock += ADDRMAN_MIN_FAIL + 24h; CAddress addr_helper{CAddress(ResolveService("251.252.2.3", 8333), NODE_NONE)}; addr_helper.nTime = Now(); @@ -132,7 +131,7 @@ BOOST_AUTO_TEST_CASE(addrman_terrible_many_failures) BOOST_AUTO_TEST_CASE(addrman_penalty_self_announcement) { - SetMockTime(Now()); + FakeNodeClock clock{}; auto addrman = std::make_unique(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); const auto base_time{Now() - 10000s}; @@ -1031,8 +1030,8 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) BOOST_CHECK_EQUAL(addrman->SelectTriedCollision().first.ToStringAddrPort(), "250.1.1.36:0"); // Eviction is also successful if too much time has passed since last try - NodeClockContext clock_ctx{}; - clock_ctx += 4h; + FakeNodeClock clock{}; + clock += 4h; addrman->ResolveCollisions(); BOOST_CHECK(addrman->SelectTriedCollision().first.ToStringAddrPort() == "[::]:0"); //Now 19 is in tried again, and 36 back to new diff --git a/src/test/banman_tests.cpp b/src/test/banman_tests.cpp index d1e79e357742..a2053896b04c 100644 --- a/src/test/banman_tests.cpp +++ b/src/test/banman_tests.cpp @@ -17,7 +17,7 @@ BOOST_FIXTURE_TEST_SUITE(banman_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(file) { - NodeClockContext clock_ctx{777s}; + FakeNodeClock clock{777s}; const fs::path banlist_path{m_args.GetDataDirBase() / "banlist_test"}; { const std::string entries_write{ diff --git a/src/test/blockmanager_tests.cpp b/src/test/blockmanager_tests.cpp index 04bfd20c06d7..5f18afb9af4d 100644 --- a/src/test/blockmanager_tests.cpp +++ b/src/test/blockmanager_tests.cpp @@ -42,6 +42,7 @@ BOOST_AUTO_TEST_CASE(blockmanager_find_block_pos) }; BlockManager blockman{*Assert(m_node.shutdown_signal), blockman_opts}; // simulate adding a genesis block normally + LOCK(::cs_main); BOOST_CHECK_EQUAL(blockman.WriteBlock(params->GenesisBlock(), 0).nPos, STORAGE_HEADER_BYTES); // simulate what happens during reindex // simulate a well-formed genesis block being found at offset 8 in the blk00000.dat file @@ -257,6 +258,7 @@ BOOST_AUTO_TEST_CASE(blockmanager_flush_block_file) constexpr int TEST_BLOCK_SIZE{81}; // Blockstore is empty + LOCK(::cs_main); BOOST_CHECK_EQUAL(blockman.CalculateCurrentUsage(), 0); // Write the first block to a new location. diff --git a/src/test/chainstate_write_tests.cpp b/src/test/chainstate_write_tests.cpp index cafadf38f528..3e89d574f660 100644 --- a/src/test/chainstate_write_tests.cpp +++ b/src/test/chainstate_write_tests.cpp @@ -29,7 +29,7 @@ BOOST_FIXTURE_TEST_CASE(chainstate_write_interval, TestingSetup) m_node.validation_signals->RegisterSharedValidationInterface(sub); auto& chainstate{Assert(m_node.chainman)->ActiveChainstate()}; BlockValidationState state_dummy{}; - NodeClockContext clock_ctx{}; + FakeNodeClock clock{}; // The first periodic flush sets m_next_write and does not flush chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC); @@ -37,12 +37,12 @@ BOOST_FIXTURE_TEST_CASE(chainstate_write_interval, TestingSetup) BOOST_CHECK(!sub->m_did_flush); // The periodic flush interval is between 50 and 70 minutes (inclusive) - clock_ctx += DATABASE_WRITE_INTERVAL_MIN - 1min; + clock += DATABASE_WRITE_INTERVAL_MIN - 1min; chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC); m_node.validation_signals->SyncWithValidationInterfaceQueue(); BOOST_CHECK(!sub->m_did_flush); - clock_ctx += DATABASE_WRITE_INTERVAL_MAX; + clock += DATABASE_WRITE_INTERVAL_MAX; chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC); m_node.validation_signals->SyncWithValidationInterfaceQueue(); BOOST_CHECK(sub->m_did_flush); @@ -86,7 +86,7 @@ BOOST_FIXTURE_TEST_CASE(write_during_multiblock_activation, TestChain100Setup) m_node.validation_signals->SyncWithValidationInterfaceQueue(); // The periodic flush interval is between 50 and 70 minutes (inclusive) // The next call to a PERIODIC write will flush - SetMockTime(GetMockTime() + DATABASE_WRITE_INTERVAL_MAX); + m_clock += DATABASE_WRITE_INTERVAL_MAX; const auto sub{std::make_shared()}; m_node.validation_signals->RegisterSharedValidationInterface(sub); diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 05dfcb1e2327..4251a5a4b26c 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -91,8 +91,8 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) } connman.FlushSendBuffer(dummyNode1); - NodeClockContext clock_ctx{}; - clock_ctx += 21min; + FakeNodeClock clock{}; + clock += 21min; BOOST_CHECK(peerman.SendMessages(dummyNode1)); // should result in getheaders { @@ -101,7 +101,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) BOOST_CHECK(!to_send.empty()); } - clock_ctx += 3min; + clock += 3min; BOOST_CHECK(peerman.SendMessages(dummyNode1)); // should result in disconnect BOOST_CHECK(dummyNode1.fDisconnect == true); @@ -153,7 +153,7 @@ BOOST_FIXTURE_TEST_CASE(stale_tip_peer_management, OutboundTest) options.m_max_automatic_connections = DEFAULT_MAX_PEER_CONNECTIONS; const auto time_init{Now()}; - NodeClockContext clock_ctx{time_init}; + FakeNodeClock clock{time_init}; const auto delta{3 * std::chrono::seconds{m_node.chainman->GetConsensus().nPowTargetSpacing} + 1s}; connman->Init(options); std::vector vNodes; @@ -170,7 +170,7 @@ BOOST_FIXTURE_TEST_CASE(stale_tip_peer_management, OutboundTest) BOOST_CHECK(node->fDisconnect == false); } - clock_ctx += delta; + clock += delta; // Now tip should definitely be stale, and we should look for an extra // outbound peer @@ -185,9 +185,9 @@ BOOST_FIXTURE_TEST_CASE(stale_tip_peer_management, OutboundTest) // If we add one more peer, something should get marked for eviction // on the next check (since we're mocking the time to be in the future, the // required time connected check should be satisfied). - clock_ctx.set(time_init); + clock.set(time_init); AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY); - clock_ctx += delta; + clock += delta; peerLogic->CheckForStaleTipAndEvictPeers(); for (int i = 0; i < max_outbound_full_relay; ++i) { @@ -213,9 +213,9 @@ BOOST_FIXTURE_TEST_CASE(stale_tip_peer_management, OutboundTest) // Add an onion peer, that will be protected because it is the only one for // its network, so another peer gets disconnected instead. - clock_ctx.set(time_init); + clock.set(time_init); AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY, /*onion_peer=*/true); - clock_ctx += delta; + clock += delta; peerLogic->CheckForStaleTipAndEvictPeers(); for (int i = 0; i < max_outbound_full_relay - 2; ++i) { @@ -226,9 +226,9 @@ BOOST_FIXTURE_TEST_CASE(stale_tip_peer_management, OutboundTest) BOOST_CHECK(vNodes[max_outbound_full_relay]->fDisconnect == false); // Add a second onion peer which won't be protected - clock_ctx.set(time_init); + clock.set(time_init); AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY, /*onion_peer=*/true); - clock_ctx += delta; + clock += delta; peerLogic->CheckForStaleTipAndEvictPeers(); BOOST_CHECK(vNodes.back()->fDisconnect == true); @@ -243,6 +243,7 @@ BOOST_FIXTURE_TEST_CASE(stale_tip_peer_management, OutboundTest) BOOST_FIXTURE_TEST_CASE(block_relay_only_eviction, OutboundTest) { NodeId id{0}; + FakeNodeClock clock{}; auto connman = std::make_unique(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman, Params()); auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, *m_node.warnings, {}); @@ -274,8 +275,7 @@ BOOST_FIXTURE_TEST_CASE(block_relay_only_eviction, OutboundTest) } BOOST_CHECK(vNodes.back()->fDisconnect == false); - NodeClockContext clock_ctx{}; - clock_ctx += MINIMUM_CONNECT_TIME; + clock += MINIMUM_CONNECT_TIME; peerLogic->CheckForStaleTipAndEvictPeers(); for (int i = 0; i < max_outbound_block_relay; ++i) { BOOST_CHECK(vNodes[i]->fDisconnect == false); @@ -413,7 +413,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), *m_node.chainman, *m_node.mempool, *m_node.warnings, {}); banman->ClearBanned(); - const NodeClockContext clock_ctx{}; // keep mocktime constant + const FakeNodeClock clock{}; // keep mocktime constant CAddress addr(ip(0xa0b0c001), NODE_NONE); NodeId id{0}; diff --git a/src/test/feerounder_tests.cpp b/src/test/feerounder_tests.cpp index 82cd7d65b409..d88a4f771b6c 100644 --- a/src/test/feerounder_tests.cpp +++ b/src/test/feerounder_tests.cpp @@ -9,7 +9,7 @@ #include -BOOST_AUTO_TEST_SUITE(fee_rounder_tests) +BOOST_AUTO_TEST_SUITE(feerounder_tests) BOOST_AUTO_TEST_CASE(FeeRounder) { diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index 750480285de9..435de296d846 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -117,7 +117,7 @@ FUZZ_TARGET(addrman, .init = initialize_addrman) { SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; NetGroupManager netgroupman{ConsumeNetGroupManager(fuzzed_data_provider)}; auto addr_man_ptr = std::make_unique(netgroupman, fuzzed_data_provider, GetCheckRatio()); if (fuzzed_data_provider.ConsumeBool()) { @@ -202,7 +202,7 @@ FUZZ_TARGET(addrman_serdeser, .init = initialize_addrman) { SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; NetGroupManager netgroupman{ConsumeNetGroupManager(fuzzed_data_provider)}; AddrManDeterministic addr_man1{netgroupman, fuzzed_data_provider, GetCheckRatio()}; diff --git a/src/test/fuzz/banman.cpp b/src/test/fuzz/banman.cpp index aa7e76b90c79..aac93c9dad75 100644 --- a/src/test/fuzz/banman.cpp +++ b/src/test/fuzz/banman.cpp @@ -45,7 +45,7 @@ FUZZ_TARGET(banman, .init = initialize_banman) { SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; fs::path banlist_file = gArgs.GetDataDirNet() / "fuzzed_banlist"; const bool start_with_corrupted_banlist{fuzzed_data_provider.ConsumeBool()}; @@ -125,7 +125,7 @@ FUZZ_TARGET(banman, .init = initialize_banman) } if (!force_read_and_write_to_err) { ban_man.DumpBanlist(); - clock_ctx.set(ConsumeTime(fuzzed_data_provider)); + clock.set(ConsumeTime(fuzzed_data_provider)); banmap_t banmap; ban_man.GetBanned(banmap); BanMan ban_man_read{banlist_file, /*client_interface=*/nullptr, /*default_ban_time=*/0}; diff --git a/src/test/fuzz/block_index_tree.cpp b/src/test/fuzz/block_index_tree.cpp index 6247ba756866..8fb012e008a9 100644 --- a/src/test/fuzz/block_index_tree.cpp +++ b/src/test/fuzz/block_index_tree.cpp @@ -42,7 +42,7 @@ void initialize_block_index_tree() FUZZ_TARGET(block_index_tree, .init = initialize_block_index_tree) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; auto& chainman = static_cast(*g_setup->m_node.chainman); auto& blockman = static_cast(chainman.m_blockman); CBlockIndex* genesis = chainman.ActiveChainstate().m_chain[0]; diff --git a/src/test/fuzz/cmpctblock.cpp b/src/test/fuzz/cmpctblock.cpp index 06666a5da5c6..614334388804 100644 --- a/src/test/fuzz/cmpctblock.cpp +++ b/src/test/fuzz/cmpctblock.cpp @@ -164,7 +164,7 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock) SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - NodeClockContext clock_ctx{1610000000s}; + FakeNodeClock clock{1610000000s}; auto setup = g_setup; auto& mempool = *setup->m_node.mempool; @@ -454,10 +454,10 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock) [&]() { // Set mock time randomly or to tip's time. if (fuzzed_data_provider.ConsumeBool()) { - clock_ctx.set(ConsumeTime(fuzzed_data_provider)); + clock.set(ConsumeTime(fuzzed_data_provider)); } else { const NodeSeconds tip_time = WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()->Time()); - clock_ctx.set(tip_time); + clock.set(tip_time); } sent_net_msg = false; diff --git a/src/test/fuzz/connman.cpp b/src/test/fuzz/connman.cpp index 669a1169a839..614bdf4f337d 100644 --- a/src/test/fuzz/connman.cpp +++ b/src/test/fuzz/connman.cpp @@ -41,7 +41,7 @@ FUZZ_TARGET(connman, .init = initialize_connman) { SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; auto netgroupman{ConsumeNetGroupManager(fuzzed_data_provider)}; auto addr_man_ptr{std::make_unique(netgroupman, fuzzed_data_provider, GetCheckRatio())}; if (fuzzed_data_provider.ConsumeBool()) { diff --git a/src/test/fuzz/dbwrapper.cpp b/src/test/fuzz/dbwrapper.cpp index 784b696d8166..1b5cd7b025d0 100644 --- a/src/test/fuzz/dbwrapper.cpp +++ b/src/test/fuzz/dbwrapper.cpp @@ -167,7 +167,16 @@ void VerifyIterator(CDBWrapper& dbw, const Oracle& oracle, } /** Maximum number of concurrent reader threads in dbwrapper_concurrent_reads. */ -constexpr size_t MAX_READ_WORKERS{16}; +constexpr size_t MAX_READ_WORKERS{8}; + +ThreadPool g_read_pool{"dbfuzz"}; +Mutex g_read_pool_mutex; + +void StartReadPoolIfNeeded() EXCLUSIVE_LOCKS_REQUIRED(!g_read_pool_mutex) +{ + LOCK(g_read_pool_mutex); + if (!g_read_pool.WorkersCount()) g_read_pool.Start(MAX_READ_WORKERS); +} /** Build randomized DBParams from the fuzz input, shared by all targets. */ DBParams ConsumeDBParams(FuzzedDataProvider& provider, leveldb::Env* testing_env, @@ -185,39 +194,6 @@ DBParams ConsumeDBParams(FuzzedDataProvider& provider, leveldb::Env* testing_env }; } -/** A single read-only operation run concurrently in dbwrapper_concurrent_reads. */ -enum class ReadOp { Read, Exists, IteratorSeek }; -using ReadQuery = std::tuple; -using Results = std::vector>; - -Results RunReadQueries(CDBWrapper& db, const std::vector& queries, FastRandomContext& rng) -{ - std::vector order(queries.size()); - std::iota(order.begin(), order.end(), size_t{0}); - std::shuffle(order.begin(), order.end(), rng); - - Results results(queries.size()); - for (const auto i : order) { - const auto& [op, key] = queries[i]; - std::string v; - switch (op) { - case ReadOp::Read: - if (db.Read(key, v)) results[i] = std::move(v); - break; - case ReadOp::Exists: - if (db.Exists(key)) results[i] = std::move(v); - break; - case ReadOp::IteratorSeek: { - const std::unique_ptr it{db.NewIterator()}; - it->Seek(key); - if (it->Valid() && it->GetValue(v)) results[i] = std::move(v); - break; - } - } - } - return results; -} - template void TestDbWrapper(FuzzedDataProvider& provider, leveldb::Env* testing_env, @@ -381,8 +357,9 @@ FUZZ_TARGET(dbwrapper_threaded, .init = [] { static auto setup{MakeNoLogFileCont /*allow_force_compact=*/true); } -FUZZ_TARGET(dbwrapper_concurrent_reads, .init = [] { static auto setup{MakeNoLogFileContext<>()}; }) +FUZZ_TARGET(dbwrapper_concurrent_reads, .init = [] { static auto setup{MakeNoLogFileContext<>()}; }) EXCLUSIVE_LOCKS_REQUIRED(!g_read_pool_mutex) { + StartReadPoolIfNeeded(); SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider provider{buffer.data(), buffer.size()}; @@ -394,17 +371,20 @@ FUZZ_TARGET(dbwrapper_concurrent_reads, .init = [] { static auto setup{MakeNoLog // Seed the DB. Drain work after small batches so we don't deadlock on a // scheduled compaction. - const size_t num_entries{provider.ConsumeIntegralInRange(100, 5'000)}; + const size_t num_entries{provider.ConsumeIntegralInRange(100, 3'000)}; std::vector keys; keys.reserve(num_entries); + Oracle oracle; constexpr size_t SEED_BATCH_SIZE{400}; for (size_t start{0}; start < num_entries; start += SEED_BATCH_SIZE) { CDBBatch batch{db}; const size_t end{std::min(start + SEED_BATCH_SIZE, num_entries)}; for (size_t i{start}; i < end; ++i) { const auto k{ConsumeKey(provider)}; - batch.Write(k, MakeValue(k, ConsumeValueSize(provider))); + const auto size{ConsumeValueSize(provider)}; + batch.Write(k, MakeValue(k, size)); keys.push_back(k); + oracle[k] = size; } det_env.DrainWork(); db.WriteBatch(batch, /*fSync=*/true); @@ -414,40 +394,71 @@ FUZZ_TARGET(dbwrapper_concurrent_reads, .init = [] { static auto setup{MakeNoLog // Build query list from seeded and random keys. const size_t num_queries{provider.ConsumeIntegralInRange(1, 2'000)}; - std::vector queries; + enum class ReadOp { Read, Exists, IteratorSeek }; + std::vector> queries; queries.reserve(num_queries); for (size_t i{0}; i < num_queries; ++i) { - const auto op{static_cast(provider.ConsumeIntegralInRange(0, 2))}; + const auto op{provider.PickValueInArray({ReadOp::Read, ReadOp::Exists, ReadOp::IteratorSeek})}; const uint16_t key{provider.ConsumeBool() ? keys[provider.ConsumeIntegralInRange(0, keys.size() - 1)] : ConsumeKey(provider)}; queries.emplace_back(op, key); } - // Baseline read on a single thread - FastRandomContext rng{ConsumeUInt256(provider)}; - const Results baseline{RunReadQueries(db, queries, rng)}; - - ThreadPool pool{"dbfuzz"}; - pool.Start(MAX_READ_WORKERS); // Workers + main thread synchronize on the latch so all reads start together. std::latch start_latch{static_cast(MAX_READ_WORKERS + 1)}; - std::vector> tasks(MAX_READ_WORKERS); - std::generate(tasks.begin(), tasks.end(), [&] { - return [&, seed = rng.rand256()]() -> Results { + std::vector> tasks(MAX_READ_WORKERS); + FastRandomContext rng{ConsumeUInt256(provider)}; + std::ranges::generate(tasks, [&] { + return [&, seed = rng.rand256()] { FastRandomContext thread_rng{seed}; + std::vector order(queries.size()); + std::iota(order.begin(), order.end(), size_t{0}); + std::ranges::shuffle(order, thread_rng); + std::vector v; + std::string key_str; start_latch.arrive_and_wait(); - return RunReadQueries(db, queries, thread_rng); + const std::unique_ptr it{db.NewIterator()}; + // Every read must agree with the oracle, the source of truth. + for (const auto i : order) { + const auto& [op, key] = queries[i]; + switch (op) { + case ReadOp::Read: + if (const auto oit{oracle.find(key)}; oit != oracle.end()) { + assert(db.Read(key, v) && v == MakeValue(key, oit->second)); + } else { + assert(!db.Read(key, v)); + } + break; + case ReadOp::Exists: + assert(db.Exists(key) == oracle.contains(key)); + break; + case ReadOp::IteratorSeek: + it->Seek(key); + // Skip the obfuscation metadata entry (a non-uint16_t key) if we land + // on it, so the result matches the oracle, which only tracks user keys. + if (it->Valid() && it->GetKey(key_str) && key_str == OBFUSCATION_KEY) it->Next(); + if (const auto oit{oracle.lower_bound(key)}; oit != oracle.end()) { + assert(it->Valid()); + uint16_t actual_key; + assert(it->GetKey(actual_key) && actual_key == oit->first); + assert(it->GetValue(v) && v == MakeValue(actual_key, oit->second)); + } else { + assert(!it->Valid()); + } + break; + } + } }; }); - auto futures{*Assert(pool.Submit(std::move(tasks)))}; + auto futures{*Assert(g_read_pool.Submit(std::move(tasks)))}; // Release the workers and immediately run the queued compaction on this // thread, so compaction races against the concurrent reads. start_latch.arrive_and_wait(); det_env.DrainWork(); - for (auto& fut : futures) assert(fut.get() == baseline); + for (auto& fut : futures) fut.get(); det_env.DrainWork(); } diff --git a/src/test/fuzz/descriptor_parse.cpp b/src/test/fuzz/descriptor_parse.cpp index 6b3084e23d15..2e73cfcc438a 100644 --- a/src/test/fuzz/descriptor_parse.cpp +++ b/src/test/fuzz/descriptor_parse.cpp @@ -55,7 +55,11 @@ static void TestDescriptor(const Descriptor& desc, FlatSigningProvider& sig_prov } const auto max_sat_maxsig{desc.MaxSatisfactionWeight(true)}; - const auto max_sat_nonmaxsig{desc.MaxSatisfactionWeight(true)}; + const auto max_sat_nonmaxsig{desc.MaxSatisfactionWeight(false)}; + // Whether an estimate is available must not depend on the signature-size + // assumption, and assuming non-max-size signatures must never increase it. + assert(max_sat_maxsig.has_value() == max_sat_nonmaxsig.has_value()); + assert(max_sat_nonmaxsig <= max_sat_maxsig); const auto max_elems{desc.MaxSatisfactionElems()}; // We must be able to estimate the max satisfaction size for any solvable descriptor (but combo). const bool is_nontop_or_nonsolvable{!*is_solvable || !desc.GetOutputType()}; diff --git a/src/test/fuzz/headerssync.cpp b/src/test/fuzz/headerssync.cpp index cca89155c880..2cf2d5a52a72 100644 --- a/src/test/fuzz/headerssync.cpp +++ b/src/test/fuzz/headerssync.cpp @@ -61,7 +61,7 @@ FUZZ_TARGET(headers_sync_state, .init = initialize_headers_sync_state_fuzz) CBlockHeader genesis_header{Params().GenesisBlock()}; CBlockIndex start_index(genesis_header); - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider, /*min=*/start_index.GetMedianTimePast())}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider, /*min=*/start_index.GetMedianTimePast())}; const uint256 genesis_hash = genesis_header.GetHash(); start_index.phashBlock = &genesis_hash; diff --git a/src/test/fuzz/i2p.cpp b/src/test/fuzz/i2p.cpp index d262e1d0f319..5c94a19b8d82 100644 --- a/src/test/fuzz/i2p.cpp +++ b/src/test/fuzz/i2p.cpp @@ -27,7 +27,7 @@ FUZZ_TARGET(i2p, .init = initialize_i2p) SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; // Mock CreateSock() to create FuzzedSock. auto CreateSockOrig = CreateSock; diff --git a/src/test/fuzz/key.cpp b/src/test/fuzz/key.cpp index b774df43d5d3..57d2e5bd9cba 100644 --- a/src/test/fuzz/key.cpp +++ b/src/test/fuzz/key.cpp @@ -84,7 +84,7 @@ FUZZ_TARGET(key, .init = initialize_key) { CKey child_key; ChainCode child_chaincode; - const bool ok = key.Derive(child_key, child_chaincode, 0, random_uint256); + const bool ok = key.Derive(child_key, child_chaincode, 0, ChainCode{random_uint256}); assert(ok); assert(child_key.IsValid()); assert(!(child_key == key)); @@ -274,7 +274,7 @@ FUZZ_TARGET(key, .init = initialize_key) { CPubKey child_pubkey; ChainCode child_chaincode; - const bool ok = pubkey.Derive(child_pubkey, child_chaincode, 0, random_uint256); + const bool ok = pubkey.Derive(child_pubkey, child_chaincode, 0, ChainCode{random_uint256}); assert(ok); assert(child_pubkey != pubkey); assert(child_pubkey.IsCompressed()); diff --git a/src/test/fuzz/load_external_block_file.cpp b/src/test/fuzz/load_external_block_file.cpp index e796bbf981e8..83792662c4d5 100644 --- a/src/test/fuzz/load_external_block_file.cpp +++ b/src/test/fuzz/load_external_block_file.cpp @@ -29,7 +29,7 @@ void initialize_load_external_block_file() FUZZ_TARGET(load_external_block_file, .init = initialize_load_external_block_file) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; FuzzedFileProvider fuzzed_file_provider{fuzzed_data_provider}; AutoFile fuzzed_block_file{fuzzed_file_provider.open()}; if (fuzzed_block_file.IsNull()) { diff --git a/src/test/fuzz/mini_miner.cpp b/src/test/fuzz/mini_miner.cpp index daa3dae34112..27bf32379221 100644 --- a/src/test/fuzz/mini_miner.cpp +++ b/src/test/fuzz/mini_miner.cpp @@ -49,7 +49,7 @@ FUZZ_TARGET(mini_miner, .init = initialize_miner) { SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; bilingual_str error; CTxMemPool pool{CTxMemPool::Options{}, error}; Assert(error.empty()); diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp index 20fbae1594bd..93ea00cb94c4 100644 --- a/src/test/fuzz/net.cpp +++ b/src/test/fuzz/net.cpp @@ -32,7 +32,7 @@ void initialize_net() FUZZ_TARGET(net, .init = initialize_net) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; CNode node{ConsumeNode(fuzzed_data_provider)}; node.SetCommonVersion(fuzzed_data_provider.ConsumeIntegral()); if (const auto service_opt = @@ -81,7 +81,7 @@ FUZZ_TARGET(net, .init = initialize_net) FUZZ_TARGET(local_address, .init = initialize_net) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; CService service{ConsumeService(fuzzed_data_provider)}; CNode node{ConsumeNode(fuzzed_data_provider)}; { diff --git a/src/test/fuzz/p2p_handshake.cpp b/src/test/fuzz/p2p_handshake.cpp index 85b18476f1e1..affc63c9a163 100644 --- a/src/test/fuzz/p2p_handshake.cpp +++ b/src/test/fuzz/p2p_handshake.cpp @@ -41,7 +41,7 @@ FUZZ_TARGET(p2p_handshake, .init = ::initialize) auto& node{g_setup->m_node}; auto& connman{static_cast(*node.connman)}; auto& chainman{static_cast(*node.chainman)}; - NodeClockContext clock_ctx{1610000000s}; // any time to successfully reset ibd + FakeNodeClock clock{1610000000s}; // any time to successfully reset ibd chainman.ResetIbd(); node.banman.reset(); @@ -80,7 +80,7 @@ FUZZ_TARGET(p2p_handshake, .init = ::initialize) continue; } - clock_ctx += std::chrono::seconds{ + clock += std::chrono::seconds{ fuzzed_data_provider.ConsumeIntegralInRange( -std::chrono::seconds{10min}.count(), // Allow mocktime to go backwards slightly std::chrono::seconds{TIMEOUT_INTERVAL}.count()), diff --git a/src/test/fuzz/p2p_headers_presync.cpp b/src/test/fuzz/p2p_headers_presync.cpp index 878249143e09..7703574f9504 100644 --- a/src/test/fuzz/p2p_headers_presync.cpp +++ b/src/test/fuzz/p2p_headers_presync.cpp @@ -169,7 +169,7 @@ FUZZ_TARGET(p2p_headers_presync, .init = initialize) ChainstateManager& chainman = *g_testing_setup->m_node.chainman; CBlockHeader base{chainman.GetParams().GenesisBlock()}; - const NodeClockContext clock_ctx{base.Time()}; + const FakeNodeClock clock{base.Time()}; LOCK(NetEventsInterface::g_msgproc_mutex); diff --git a/src/test/fuzz/partially_downloaded_block.cpp b/src/test/fuzz/partially_downloaded_block.cpp index 12fdb8f7d2e1..56815c4690d3 100644 --- a/src/test/fuzz/partially_downloaded_block.cpp +++ b/src/test/fuzz/partially_downloaded_block.cpp @@ -48,7 +48,7 @@ FUZZ_TARGET(partially_downloaded_block, .init = initialize_pdb) { SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; auto block{ConsumeDeserializable(fuzzed_data_provider, TX_WITH_WITNESS)}; if (!block || block->vtx.size() == 0 || diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index 4fa4f4b16c40..d6a730db7ea8 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -83,7 +83,7 @@ FUZZ_TARGET(process_message, .init = initialize_process_message) connman.Reset(); auto& chainman{static_cast(*node.chainman)}; const auto block_index_size{WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())}; - NodeClockContext clock_ctx{1610000000s}; // any time to successfully reset ibd + FakeNodeClock clock{1610000000s}; // any time to successfully reset ibd chainman.ResetIbd(); chainman.DisableNextWrite(); @@ -116,7 +116,7 @@ FUZZ_TARGET(process_message, .init = initialize_process_message) connman.AddTestNode(p2p_node); FillNode(fuzzed_data_provider, connman, p2p_node); - clock_ctx.set(ConsumeTime(fuzzed_data_provider)); + clock.set(ConsumeTime(fuzzed_data_provider)); CSerializedNetMsg net_msg; net_msg.m_type = random_message_type; diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp index bf301caa1fda..7fd9f8f3699f 100644 --- a/src/test/fuzz/process_messages.cpp +++ b/src/test/fuzz/process_messages.cpp @@ -72,7 +72,7 @@ FUZZ_TARGET(process_messages, .init = initialize_process_messages) connman.Reset(); auto& chainman{static_cast(*node.chainman)}; const auto block_index_size{WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())}; - NodeClockContext clock_ctx{1610000000s}; // any time to successfully reset ibd + FakeNodeClock clock{1610000000s}; // any time to successfully reset ibd chainman.ResetIbd(); chainman.DisableNextWrite(); @@ -110,7 +110,7 @@ FUZZ_TARGET(process_messages, .init = initialize_process_messages) { const std::string random_message_type{fuzzed_data_provider.ConsumeBytesAsString(CMessageHeader::MESSAGE_TYPE_SIZE).c_str()}; - clock_ctx.set(ConsumeTime(fuzzed_data_provider)); + clock.set(ConsumeTime(fuzzed_data_provider)); CSerializedNetMsg net_msg; net_msg.m_type = random_message_type; diff --git a/src/test/fuzz/rbf.cpp b/src/test/fuzz/rbf.cpp index 26488f1e9944..a94d2b9dc7a0 100644 --- a/src/test/fuzz/rbf.cpp +++ b/src/test/fuzz/rbf.cpp @@ -54,7 +54,7 @@ FUZZ_TARGET(rbf, .init = initialize_rbf) { SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; std::optional mtx = ConsumeDeserializable(fuzzed_data_provider, TX_WITH_WITNESS); if (!mtx) { return; @@ -96,7 +96,7 @@ FUZZ_TARGET(package_rbf, .init = initialize_package_rbf) { SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; // "Real" virtual size is not important for this test since ConsumeTxMemPoolEntry generates its own virtual size values // so we construct small transactions for performance reasons. Child simply needs an input for later to perhaps connect to parent. diff --git a/src/test/fuzz/socks5.cpp b/src/test/fuzz/socks5.cpp index 12db69a23687..0bd1710d2a88 100644 --- a/src/test/fuzz/socks5.cpp +++ b/src/test/fuzz/socks5.cpp @@ -31,7 +31,7 @@ void initialize_socks5() FUZZ_TARGET(socks5, .init = initialize_socks5) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; ProxyCredentials proxy_credentials; proxy_credentials.username = fuzzed_data_provider.ConsumeRandomLengthString(512); proxy_credentials.password = fuzzed_data_provider.ConsumeRandomLengthString(512); diff --git a/src/test/fuzz/txdownloadman.cpp b/src/test/fuzz/txdownloadman.cpp index 5256edd20fc6..33fb8a426897 100644 --- a/src/test/fuzz/txdownloadman.cpp +++ b/src/test/fuzz/txdownloadman.cpp @@ -169,7 +169,7 @@ FUZZ_TARGET(txdownloadman, .init = initialize) { SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; // Initialize txdownloadman bilingual_str error; @@ -294,7 +294,7 @@ FUZZ_TARGET(txdownloadman_impl, .init = initialize) { SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; // Initialize a TxDownloadManagerImpl bilingual_str error; diff --git a/src/test/fuzz/txorphan.cpp b/src/test/fuzz/txorphan.cpp index 394aed30ef65..fcb530b03da7 100644 --- a/src/test/fuzz/txorphan.cpp +++ b/src/test/fuzz/txorphan.cpp @@ -41,7 +41,7 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage) SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); FastRandomContext orphanage_rng{ConsumeUInt256(fuzzed_data_provider)}; - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; auto orphanage = node::MakeTxOrphanage(); std::vector outpoints; // Duplicates are tolerated @@ -232,7 +232,7 @@ FUZZ_TARGET(txorphan_protected, .init = initialize_orphanage) SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); FastRandomContext orphanage_rng{ConsumeUInt256(fuzzed_data_provider)}; - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; // We have num_peers peers. Some subset of them will never exceed their reserved weight or announcement count, and // should therefore never have any orphans evicted. diff --git a/src/test/fuzz/util/check_globals.cpp b/src/test/fuzz/util/check_globals.cpp index ca3111534d3b..1c29906932c5 100644 --- a/src/test/fuzz/util/check_globals.cpp +++ b/src/test/fuzz/util/check_globals.cpp @@ -19,6 +19,7 @@ struct CheckGlobalsImpl { g_seeded_g_prng_zero = false; g_used_system_time = false; SetMockTime(0s); + MockableSteadyClock::ClearMockTime(); } ~CheckGlobalsImpl() { @@ -43,7 +44,7 @@ struct CheckGlobalsImpl { "The current fuzz target accessed system time.\n\n" "This is acceptable, but requires the fuzz target to use \n" - "a NodeClockContext, SteadyClockContext or call \n" + "a FakeNodeClock, SteadyClockContext or call \n" "SetMockTime() at the \n" "beginning of processing the \n" "fuzz input.\n\n" diff --git a/src/test/fuzz/utxo_total_supply.cpp b/src/test/fuzz/utxo_total_supply.cpp index fba845326d64..676a4eecdd88 100644 --- a/src/test/fuzz/utxo_total_supply.cpp +++ b/src/test/fuzz/utxo_total_supply.cpp @@ -34,7 +34,7 @@ FUZZ_TARGET(utxo_total_supply) { SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)}; // regtest genesis block timestamp + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)}; // regtest genesis block timestamp /** The testing setup that creates a chainman only (no chainstate) */ ChainTestingSetup test_setup{ ChainType::REGTEST, diff --git a/src/test/fuzz/validation_load_mempool.cpp b/src/test/fuzz/validation_load_mempool.cpp index 002524dac13d..1f38253457d6 100644 --- a/src/test/fuzz/validation_load_mempool.cpp +++ b/src/test/fuzz/validation_load_mempool.cpp @@ -41,7 +41,7 @@ FUZZ_TARGET(validation_load_mempool, .init = initialize_validation_load_mempool) { SeedRandomStateForTest(SeedRand::ZEROS); FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; - NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; + FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; FuzzedFileProvider fuzzed_file_provider{fuzzed_data_provider}; bilingual_str error; diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index 77024c3edc6d..daaf2e61c28b 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -252,29 +252,29 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest) TryAddToMempool(pool, entry.Fee(900LL).FromTx(tx7)); std::vector vtx; - NodeClockContext clock_ctx{42s}; + FakeNodeClock clock{42s}; constexpr std::chrono::seconds HALFLIFE{CTxMemPool::ROLLING_FEE_HALFLIFE}; - clock_ctx += HALFLIFE; + clock += HALFLIFE; BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE); // ... we should keep the same min fee until we get a block pool.removeForBlock(vtx, 1); - clock_ctx += HALFLIFE; + clock += HALFLIFE; BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE)/2.0)); // ... then feerate should drop 1/2 each halflife - clock_ctx += HALFLIFE / 2; + clock += HALFLIFE / 2; BOOST_CHECK_EQUAL(pool.GetMinFee(pool.DynamicMemoryUsage() * 5 / 2).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE)/4.0)); // ... with a 1/2 halflife when mempool is < 1/2 its target size - clock_ctx += HALFLIFE / 4; + clock += HALFLIFE / 4; BOOST_CHECK_EQUAL(pool.GetMinFee(pool.DynamicMemoryUsage() * 9 / 2).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE)/8.0)); // ... with a 1/4 halflife when mempool is < 1/4 its target size - clock_ctx += 5 * HALFLIFE; + clock += 5 * HALFLIFE; BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), DEFAULT_INCREMENTAL_RELAY_FEE); // ... but feerate should never drop below DEFAULT_INCREMENTAL_RELAY_FEE - clock_ctx += HALFLIFE; + clock += HALFLIFE; BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), 0); // ... unless it has gone all the way to 0 (after getting past DEFAULT_INCREMENTAL_RELAY_FEE/2) } diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 31d90541b9a0..11fa73a5c9db 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -25,13 +25,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include #include #include @@ -341,6 +341,8 @@ std::vector CreateBigSigOpsCluster(const CTransactionRef& first void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::vector& txFirst, int baseheight) { + FakeNodeClock clock{}; + Txid hash; CMutableTransaction tx; TestMemPoolEntryHelper entry; @@ -564,7 +566,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: LOCK(tx_mempool.cs); // non-final txs in mempool - SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1); + clock.set(std::chrono::seconds{m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1}); const int flags{LOCKTIME_VERIFY_SEQUENCE}; // height map std::vector prevheights; @@ -669,7 +671,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std:: ancestor->nTime += SEQUENCE_LOCK_TIME; // Trick the MedianTimePast } m_node.chainman->ActiveChain().Tip()->nHeight++; - SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1); + clock.set(std::chrono::seconds{m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1}); block_template = mining->createNewBlock(options, /*cooldown=*/false); BOOST_REQUIRE(block_template); @@ -895,12 +897,10 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) TestBasicMining(scriptPubKey, txFirst, baseheight); m_node.chainman->ActiveChain().Tip()->nHeight--; - SetMockTime(0); TestPackageSelection(scriptPubKey, txFirst); m_node.chainman->ActiveChain().Tip()->nHeight--; - SetMockTime(0); TestPrioritisedMining(scriptPubKey, txFirst); } diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 946d77bbe675..458239e62311 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include #include @@ -802,6 +803,41 @@ BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle) BOOST_CHECK(!IsLocal(addr)); } +BOOST_AUTO_TEST_CASE(LocalAddress_nScore_Overflow) +{ + g_reachable_nets.Add(NET_IPV4); + const CService addr{UtilBuildAddress(0x002, 0x001, 0x001, 0x001), 1000}; // 2.1.1.1:1000 + + const auto get_score = [](const CService& service) -> int { + LOCK(g_maplocalhost_mutex); + const auto it = mapLocalHost.find(service); + return it != mapLocalHost.end() ? it->second.nScore : 0; + }; + + const int initial_score = 1000; + BOOST_REQUIRE(AddLocal(addr, initial_score)); + BOOST_REQUIRE(IsLocal(addr)); + BOOST_CHECK_EQUAL(get_score(addr), initial_score); + + // SeenLocal should increment nScore by 1. + BOOST_CHECK(SeenLocal(addr)); + BOOST_CHECK_EQUAL(get_score(addr), initial_score + 1); + + // AddLocal() saturates nScore when updating an existing entry at INT_MAX. + BOOST_REQUIRE(AddLocal(addr, std::numeric_limits::max())); + BOOST_CHECK_EQUAL(get_score(addr), std::numeric_limits::max()); + + BOOST_CHECK(AddLocal(addr, std::numeric_limits::max())); + BOOST_CHECK_EQUAL(get_score(addr), std::numeric_limits::max()); + + // SeenLocal() also saturates at INT_MAX. + BOOST_CHECK(SeenLocal(addr)); + BOOST_CHECK_EQUAL(get_score(addr), std::numeric_limits::max()); + + RemoveLocal(addr); + BOOST_CHECK(!IsLocal(addr)); +} + BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message) { LOCK(NetEventsInterface::g_msgproc_mutex); @@ -1533,7 +1569,7 @@ BOOST_AUTO_TEST_CASE(v2transport_test) tester.CompareSessionIDs(); auto msg_data_1 = m_rng.randbytes(4000000); // test that receiving 4M payload works auto msg_data_2 = m_rng.randbytes(4000000); // test that sending 4M payload works - tester.SendMessage(uint8_t(m_rng.randrange(223) + 33), {}); // unknown short id + tester.SendMessage(uint8_t(m_rng.randrange(256 - BIP324_SHORTIDS_IMPLEMENTED) + BIP324_SHORTIDS_IMPLEMENTED), {}); // unknown short id tester.SendMessage(uint8_t(2), msg_data_1); // "block" short id tester.AddMessage("blocktxn", msg_data_2); // schedule blocktxn to be sent to us ret = tester.Interact(); diff --git a/src/test/orphanage_tests.cpp b/src/test/orphanage_tests.cpp index f001360a1774..c09f987aca47 100644 --- a/src/test/orphanage_tests.cpp +++ b/src/test/orphanage_tests.cpp @@ -432,7 +432,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) FillableSigningProvider keystore; BOOST_CHECK(keystore.AddKey(key)); - NodeClockContext clock_ctx{}; + FakeNodeClock clock{}; std::vector orphans_added; diff --git a/src/test/pcp_tests.cpp b/src/test/pcp_tests.cpp index 51b004a5d525..8ecbab58d255 100644 --- a/src/test/pcp_tests.cpp +++ b/src/test/pcp_tests.cpp @@ -92,7 +92,6 @@ class PCPTestSock final : public Sock m_local_ip(local_ip), m_gateway_ip(gateway_ip) { - ElapseTime(std::chrono::seconds(0)); // start mocking steady time PrepareOp(); } @@ -193,10 +192,10 @@ class PCPTestSock final : public Sock { // Only handles receive events. if (AtEndOfScript() || requested != Sock::RECV) { - ElapseTime(timeout); + m_clock += timeout; } else { std::chrono::milliseconds delay = std::min(m_time_left, timeout); - ElapseTime(delay); + m_clock += delay; m_time_left -= delay; if (CurOp().op == TestOp::RECV && m_time_left == 0s && occurred != nullptr) { *occurred = Sock::RECV; @@ -223,18 +222,12 @@ class PCPTestSock final : public Sock const std::vector m_script; mutable size_t m_script_ptr = 0; mutable std::chrono::milliseconds m_time_left; - mutable std::chrono::milliseconds m_time{MockableSteadyClock::INITIAL_MOCK_TIME}; + mutable SteadyClockContext m_clock{}; mutable bool m_connected{false}; mutable CService m_bound; mutable CNetAddr m_local_ip; mutable CNetAddr m_gateway_ip; - void ElapseTime(std::chrono::milliseconds duration) const - { - m_time += duration; - MockableSteadyClock::SetMockTime(m_time); - } - bool AtEndOfScript() const { return m_script_ptr == m_script.size(); } const TestOp &CurOp() const { BOOST_REQUIRE(m_script_ptr < m_script.size()); diff --git a/src/test/peerman_tests.cpp b/src/test/peerman_tests.cpp index 869945f48f0d..111a2292eded 100644 --- a/src/test/peerman_tests.cpp +++ b/src/test/peerman_tests.cpp @@ -12,7 +12,10 @@ #include #include #include +#include +#include #include +#include #include @@ -24,17 +27,17 @@ BOOST_FIXTURE_TEST_SUITE(peerman_tests, RegTestingSetup) /** Window, in blocks, for connecting to NODE_NETWORK_LIMITED peers */ static constexpr int64_t NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS = 144; -static void mineBlock(node::NodeContext& node, std::chrono::seconds block_time) +static void mineBlock(node::NodeContext& node, FakeNodeClock& clock, std::chrono::seconds block_time) { auto curr_time = GetTime(); - SetMockTime(block_time); // update time so the block is created with it + clock.set(block_time); // update time so the block is created with it auto mining{interfaces::MakeMining(node)}; auto block_template{mining->createNewBlock({}, /*cooldown=*/false)}; BOOST_REQUIRE(block_template); CBlock block{block_template->getBlock()}; while (!CheckProofOfWork(block.GetHash(), block.nBits, node.chainman->GetConsensus())) ++block.nNonce; block.fChecked = true; // little speedup - SetMockTime(curr_time); // process block at current time + clock.set(curr_time); // process block at current time Assert(ProcessNewBlock(*node.chainman, std::make_shared(block), /*force_processing=*/true, /*min_pow_checked=*/true, nullptr)); node.validation_signals->SyncWithValidationInterfaceQueue(); // drain events queue } @@ -42,6 +45,7 @@ static void mineBlock(node::NodeContext& node, std::chrono::seconds block_time) // Verifying when network-limited peer connections are desirable based on the node's proximity to the tip BOOST_AUTO_TEST_CASE(connections_desirable_service_flags) { + FakeNodeClock clock{}; std::unique_ptr peerman = PeerManager::make(*m_node.connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, *m_node.warnings, {}); auto consensus = m_node.chainman->GetParams().GetConsensus(); @@ -55,15 +59,15 @@ BOOST_AUTO_TEST_CASE(connections_desirable_service_flags) int tip_block_height = tip->nHeight; peerman->SetBestBlock(tip_block_height, std::chrono::seconds{tip_block_time}); - SetMockTime(tip_block_time + 1); // Set node time to tip time + clock.set(std::chrono::seconds{tip_block_time + 1}); // Set node time to tip time BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS)); // Check we don't disallow limited peers connections when we are behind but still recoverable (below the connection safety window) - SetMockTime(GetTime() + std::chrono::seconds{consensus.nPowTargetSpacing * (NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS - 1)}); + clock += std::chrono::seconds{consensus.nPowTargetSpacing * (NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS - 1)}; BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS)); // Check we disallow limited peers connections when we are further than the limited peers safety window - SetMockTime(GetTime() + std::chrono::seconds{consensus.nPowTargetSpacing * 2}); + clock += std::chrono::seconds{consensus.nPowTargetSpacing * 2}; BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK | NODE_WITNESS)); // By now, we tested that the connections desirable services flags change based on the node's time proximity to the tip. @@ -72,15 +76,15 @@ BOOST_AUTO_TEST_CASE(connections_desirable_service_flags) // First, verify a block in the past doesn't enable limited peers connections // At this point, our time is (NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS + 1) * 10 minutes ahead the tip's time. - mineBlock(m_node, /*block_time=*/std::chrono::seconds{tip_block_time + 1}); + mineBlock(m_node, clock, /*block_time=*/std::chrono::seconds{tip_block_time + 1}); BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK | NODE_WITNESS)); // Verify a block close to the tip enables limited peers connections - mineBlock(m_node, /*block_time=*/GetTime()); + mineBlock(m_node, clock, /*block_time=*/GetTime()); BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS)); // Lastly, verify the stale tip checks can disallow limited peers connections after not receiving blocks for a prolonged period. - SetMockTime(GetTime() + std::chrono::seconds{consensus.nPowTargetSpacing * NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS + 1}); + clock += std::chrono::seconds{consensus.nPowTargetSpacing * NODE_NETWORK_LIMITED_ALLOW_CONN_BLOCKS + 1}; BOOST_CHECK(peerman->GetDesirableServiceFlags(peer_flags) == ServiceFlags(NODE_NETWORK | NODE_WITNESS)); } diff --git a/src/test/private_broadcast_tests.cpp b/src/test/private_broadcast_tests.cpp index 048284b23db1..8e7d13551ae6 100644 --- a/src/test/private_broadcast_tests.cpp +++ b/src/test/private_broadcast_tests.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -26,7 +27,7 @@ static CTransactionRef MakeDummyTx(uint32_t id, size_t num_witness) BOOST_AUTO_TEST_CASE(basic) { - SetMockTime(Now()); + FakeNodeClock clock{}; PrivateBroadcast pb; const NodeId recipient1{1}; @@ -94,7 +95,7 @@ BOOST_AUTO_TEST_CASE(basic) BOOST_CHECK_EQUAL(pb.GetStale().size(), 0); // 2. Fast-forward the mock clock past the INITIAL_STALE_DURATION. - SetMockTime(Now() + PrivateBroadcast::INITIAL_STALE_DURATION + 1min); + clock += PrivateBroadcast::INITIAL_STALE_DURATION + 1min; // 3. Now that the initial duration has passed, both unconfirmed transactions should be stale. BOOST_CHECK_EQUAL(pb.GetStale().size(), 2); @@ -125,7 +126,7 @@ BOOST_AUTO_TEST_CASE(basic) BOOST_CHECK_EQUAL(stale_state.size(), 1); BOOST_CHECK_EQUAL(stale_state[0], tx_for_recipient2); - SetMockTime(Now() + 10h); + clock += 10h; BOOST_CHECK_EQUAL(pb.GetStale().size(), 2); @@ -141,7 +142,7 @@ BOOST_AUTO_TEST_CASE(basic) BOOST_AUTO_TEST_CASE(stale_unpicked_tx) { - SetMockTime(Now()); + FakeNodeClock clock{}; PrivateBroadcast pb; const auto tx{MakeDummyTx(/*id=*/42, /*num_witness=*/0)}; @@ -149,9 +150,9 @@ BOOST_AUTO_TEST_CASE(stale_unpicked_tx) // Unpicked transactions use the longer INITIAL_STALE_DURATION. BOOST_CHECK_EQUAL(pb.GetStale().size(), 0); - SetMockTime(Now() + PrivateBroadcast::INITIAL_STALE_DURATION - 1min); + clock += PrivateBroadcast::INITIAL_STALE_DURATION - 1min; BOOST_CHECK_EQUAL(pb.GetStale().size(), 0); - SetMockTime(Now() + 2min); + clock += 2min; const auto stale_state{pb.GetStale()}; BOOST_REQUIRE_EQUAL(stale_state.size(), 1); BOOST_CHECK_EQUAL(stale_state[0], tx); diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp index f646ba5fafbd..9c2f16f03e30 100644 --- a/src/test/serialize_tests.cpp +++ b/src/test/serialize_tests.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -241,6 +242,40 @@ BOOST_AUTO_TEST_CASE(noncanonical) BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException); } +BOOST_AUTO_TEST_CASE(string_view) +{ + const std::string_view sv{"hello, world"}; + DataStream ss; + ss << sv; + std::string s; + ss >> s; + BOOST_CHECK_EQUAL(sv, s); +} + +BOOST_AUTO_TEST_CASE(limited_vector) +{ + const std::vector v = {1,2,3,4,-5,-6,-7,-8,-9,-10,10000,20000,-30000}; + + auto check = [&]() { + DataStream ss; + ss << v; + try { + std::vector r; + ss >> LIMITED_VECTOR(r, N); + BOOST_CHECK_LE(r.size(), N); + BOOST_CHECK(std::ranges::equal(r, v)); + } catch (const std::ios_base::failure&) { + BOOST_CHECK_GT(v.size(), N); + } + }; + check.operator()<0>(); + check.operator()<10>(); + check.operator()<12>(); + check.operator()<13>(); + check.operator()<14>(); + check.operator()<100>(); +} + BOOST_AUTO_TEST_CASE(class_methods) { int intval(100); diff --git a/src/test/testnet4_miner_tests.cpp b/src/test/testnet4_miner_tests.cpp index baf5a6320a2b..8339853eed93 100644 --- a/src/test/testnet4_miner_tests.cpp +++ b/src/test/testnet4_miner_tests.cpp @@ -41,7 +41,7 @@ BOOST_AUTO_TEST_CASE(MiningInterface) // Set node time a few minutes past the testnet4 genesis block const auto template_time{3min + WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->Time())}; - NodeClockContext clock_ctx{template_time}; + FakeNodeClock clock{template_time}; block_template = mining->createNewBlock({}, /*cooldown=*/false); BOOST_REQUIRE(block_template); @@ -57,14 +57,14 @@ BOOST_AUTO_TEST_CASE(MiningInterface) BOOST_REQUIRE(should_be_nullptr == nullptr); // This remains the case when exactly 20 minutes have gone by - clock_ctx += 17min; + clock += 17min; should_be_nullptr = block_template->waitNext(wait_options); BOOST_REQUIRE(should_be_nullptr == nullptr); // One second later the difficulty drops and it returns a new template // Note that we can't test the actual difficulty change, because the // difficulty is already at 1. - clock_ctx += 1s; + clock += 1s; block_template = block_template->waitNext(wait_options); BOOST_REQUIRE(block_template); } diff --git a/src/test/util/logging.h b/src/test/util/logging.h index ed3ee19e1bbf..6df208d841d5 100644 --- a/src/test/util/logging.h +++ b/src/test/util/logging.h @@ -39,6 +39,6 @@ class DebugLogHelper MatchFn m_match; }; -#define ASSERT_DEBUG_LOG(message) DebugLogHelper UNIQUE_NAME(debugloghelper)(message) +#define ASSERT_DEBUG_LOG(message) DebugLogHelper BITCOIN_UNIQUE_NAME(debugloghelper)(message) #endif // BITCOIN_TEST_UTIL_LOGGING_H diff --git a/src/test/util/net.cpp b/src/test/util/net.cpp index c4850c64cfd3..1415c8d4968e 100644 --- a/src/test/util/net.cpp +++ b/src/test/util/net.cpp @@ -56,7 +56,7 @@ void ConnmanTestMsg::Handshake(CNode& node, FlushSendBuffer(node); // Drop the verack message added by SendMessages. if (node.fDisconnect) return; assert(node.nVersion == version); - assert(node.GetCommonVersion() == std::min(version, PROTOCOL_VERSION)); + assert(node.GetCommonVersion() == std::min(version, node.AdvertisedVersion())); CNodeStateStats statestats; assert(peerman.GetNodeStateStats(node.GetId(), statestats)); assert(statestats.m_relay_txs == (relay_txs && !node.IsBlockOnlyConn())); diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 29e615e9e064..6565134196a1 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -406,7 +406,6 @@ TestChain100Setup::TestChain100Setup( TestOpts opts) : TestingSetup{ChainType::REGTEST, opts} { - SetMockTime(1598887952); constexpr std::array vchKey = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}; coinbaseKey.Set(vchKey.begin(), vchKey.end(), true); @@ -428,7 +427,7 @@ void TestChain100Setup::mineBlocks(int num_blocks) for (int i = 0; i < num_blocks; i++) { std::vector noTxns; CBlock b = CreateAndProcessBlock(noTxns, scriptPubKey); - SetMockTime(GetTime() + 1); + m_clock += 1s; m_coinbase_txns.push_back(b.vtx[0]); } } diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 62bfaba9a970..72a3a620d5a2 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -14,6 +14,7 @@ #include #include #include +#include #include // IWYU pragma: export #include #include @@ -226,6 +227,7 @@ struct TestChain100Setup : public TestingSetup { */ std::vector PopulateMempool(FastRandomContext& det_rand, size_t num_transactions, bool submit); + FakeNodeClock m_clock{std::chrono::seconds{1598887952}}; std::vector m_coinbase_txns; // For convenience, coinbase transactions CKey coinbaseKey; // private/public key needed to spend coinbase transactions }; diff --git a/src/test/util/time.h b/src/test/util/time.h index 8cd9a1dafcb9..2bc2553fea26 100644 --- a/src/test/util/time.h +++ b/src/test/util/time.h @@ -8,10 +8,24 @@ #include #include +/// CRTP Helper to limit a class to at most one at a time. +template +class LimitOne +{ +public: + LimitOne() { Assert(g_T_available) = false; } + ~LimitOne() { g_T_available = true; } + LimitOne(const LimitOne&) = delete; + LimitOne& operator=(const LimitOne&) = delete; + +private: + static inline bool g_T_available{true}; +}; + /// Helper to initialize the global MockableSteadyClock, let a duration elapse, /// and reset it after use in a test. -class SteadyClockContext +class SteadyClockContext : public LimitOne { MockableSteadyClock::mock_time_point::duration t{MockableSteadyClock::INITIAL_MOCK_TIME}; @@ -36,22 +50,22 @@ class SteadyClockContext /// Helper to initialize the global NodeClock, let a duration elapse, /// and reset it after use in a test. -class NodeClockContext +class FakeNodeClock : public LimitOne { NodeSeconds m_t{std::chrono::seconds::max()}; public: /// Initialize with the given time. - explicit NodeClockContext(NodeSeconds init_time) { set(init_time); } - explicit NodeClockContext(std::chrono::seconds init_time) { set(init_time); } - /// Initialize with current time, using the next tick to avoid going back by rounding to seconds. - explicit NodeClockContext() { set(++Now().time_since_epoch()); } + explicit FakeNodeClock(NodeSeconds init_time) { set(init_time); } + explicit FakeNodeClock(std::chrono::seconds init_time) { set(init_time); } + /// Initialize with current time. + explicit FakeNodeClock() { set(Now()); } /// Unset mocktime. - ~NodeClockContext() { set(0s); } + ~FakeNodeClock() { set(0s); } - NodeClockContext(const NodeClockContext&) = delete; - NodeClockContext& operator=(const NodeClockContext&) = delete; + FakeNodeClock(const FakeNodeClock&) = delete; + FakeNodeClock& operator=(const FakeNodeClock&) = delete; /// Set mocktime. void set(NodeSeconds t) { SetMockTime(m_t = t); } diff --git a/src/test/util/transaction_utils.cpp b/src/test/util/transaction_utils.cpp index b65a9568c65d..cf4ed9346690 100644 --- a/src/test/util/transaction_utils.cpp +++ b/src/test/util/transaction_utils.cpp @@ -7,7 +7,7 @@ #include