diff options
143 files changed, 2086 insertions, 896 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 75c4f0c05e..6cecb9b59a 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -208,7 +208,7 @@ task: FILE_ENV: "./ci/test/00_setup_env_native_qt5.sh" task: - name: '[depends, sanitizers: thread (TSan), no gui] [jammy]' + name: '[TSan, depends, no gui] [jammy]' << : *GLOBAL_TASK_TEMPLATE container: image: ubuntu:jammy @@ -220,7 +220,7 @@ task: FILE_ENV: "./ci/test/00_setup_env_native_tsan.sh" task: - name: '[depends, sanitizers: memory (MSan)] [focal]' + name: '[MSan, depends] [focal]' << : *GLOBAL_TASK_TEMPLATE container: image: ubuntu:focal @@ -229,7 +229,7 @@ task: FILE_ENV: "./ci/test/00_setup_env_native_msan.sh" task: - name: '[no depends, sanitizers: address/leak (ASan + LSan) + undefined (UBSan) + integer] [jammy]' + name: '[ASan + LSan + UBSan + integer, no depends] [jammy]' << : *GLOBAL_TASK_TEMPLATE container: image: ubuntu:jammy @@ -238,7 +238,7 @@ task: FILE_ENV: "./ci/test/00_setup_env_native_asan.sh" task: - name: '[no depends, sanitizers: fuzzer,address,undefined,integer] [focal]' + name: '[fuzzer,address,undefined,integer, no depends] [focal]' only_if: $CIRRUS_BRANCH == $CIRRUS_DEFAULT_BRANCH || $CIRRUS_BASE_BRANCH == $CIRRUS_DEFAULT_BRANCH << : *GLOBAL_TASK_TEMPLATE container: @@ -284,7 +284,7 @@ task: task: name: 'macOS 11 native [gui] [no depends]' brew_install_script: - - brew install boost libevent berkeley-db4 qt@5 miniupnpc libnatpmp ccache zeromq qrencode sqlite libtool automake pkg-config gnu-getopt + - brew install boost libevent berkeley-db@4 qt@5 miniupnpc libnatpmp ccache zeromq qrencode sqlite libtool automake pkg-config gnu-getopt << : *GLOBAL_TASK_TEMPLATE osx_instance: # Use latest image, but hardcode version to avoid silent upgrades (and breaks) diff --git a/build_msvc/libsecp256k1_config.h b/build_msvc/libsecp256k1_config.h index 5978b9a0d9..57f2f144ff 100644 --- a/build_msvc/libsecp256k1_config.h +++ b/build_msvc/libsecp256k1_config.h @@ -29,4 +29,4 @@ #define ECMULT_GEN_PREC_BITS 4 #define ECMULT_WINDOW_SIZE 15 -#endif /* BITCOIN_LIBSECP256K1_CONFIG_H */ +#endif // BITCOIN_LIBSECP256K1_CONFIG_H diff --git a/configure.ac b/configure.ac index 2c6e762d6d..77f45e2133 100644 --- a/configure.ac +++ b/configure.ac @@ -323,6 +323,11 @@ AC_ARG_ENABLE([external-signer], [use_external_signer=$enableval], [use_external_signer=yes]) +AC_ARG_ENABLE([lto], + [AS_HELP_STRING([--enable-lto],[build using LTO (default is no)])], + [enable_lto=$enableval], + [enable_lto=no]) + AC_LANG_PUSH([C++]) dnl Check for a flag to turn compiler warnings into errors. This is helpful for checks which may @@ -370,6 +375,11 @@ if test "x$enable_debug" = xyes; then AX_CHECK_COMPILE_FLAG([-ftrapv], [DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -ftrapv"], [], [$CXXFLAG_WERROR]) fi +if test "x$enable_lto" = "xyes"; then + AX_CHECK_COMPILE_FLAG([-flto], [LTO_CXXFLAGS="$LTO_CXXFLAGS -flto"], [AC_MSG_ERROR([compile failed with -flto])], [$CXXFLAG_WERROR]) + AX_CHECK_LINK_FLAG([-flto], [LTO_LDFLAGS="$LTO_LDFLAGS -flto"], [AC_MSG_ERROR([link failed with -flto])], [$CXXFLAG_WERROR]) +fi + if test x$use_sanitizers != x; then dnl First check if the compiler accepts flags. If an incompatible pair like dnl -fsanitize=address,thread is used here, this check will fail. This will also @@ -581,7 +591,7 @@ CXXFLAGS="$TEMP_CXXFLAGS" fi -CPPFLAGS="$CPPFLAGS -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS" +CPPFLAGS="$CPPFLAGS -DHAVE_BUILD_INFO" AC_ARG_WITH([utils], [AS_HELP_STRING([--with-utils], @@ -683,8 +693,8 @@ case $host in dnl It's safe to add these paths even if the functionality is disabled by dnl the user (--without-wallet or --without-gui for example). - if test "x$use_bdb" != xno && $BREW list --versions berkeley-db4 >/dev/null && test "x$BDB_CFLAGS" = "x" && test "x$BDB_LIBS" = "x"; then - bdb_prefix=$($BREW --prefix berkeley-db4 2>/dev/null) + if test "x$use_bdb" != xno && $BREW list --versions berkeley-db@4 >/dev/null && test "x$BDB_CFLAGS" = "x" && test "x$BDB_LIBS" = "x"; then + bdb_prefix=$($BREW --prefix berkeley-db@4 2>/dev/null) dnl This must precede the call to BITCOIN_FIND_BDB48 below. BDB_CFLAGS="-I$bdb_prefix/include" BDB_LIBS="-L$bdb_prefix/lib -ldb_cxx-4.8" @@ -694,8 +704,8 @@ case $host in export PKG_CONFIG_PATH="$($BREW --prefix sqlite3 2>/dev/null)/lib/pkgconfig:$PKG_CONFIG_PATH" fi - if $BREW list --versions qt5 >/dev/null; then - export PKG_CONFIG_PATH="$($BREW --prefix qt5 2>/dev/null)/lib/pkgconfig:$PKG_CONFIG_PATH" + if $BREW list --versions qt@5 >/dev/null; then + export PKG_CONFIG_PATH="$($BREW --prefix qt@5 2>/dev/null)/lib/pkgconfig:$PKG_CONFIG_PATH" fi case $host in @@ -1044,9 +1054,6 @@ AC_COMPILE_IFELSE([AC_LANG_SOURCE([ [AC_MSG_RESULT([no])] ) -dnl thread_local is currently disabled when building with glibc back compat. -dnl Our minimum supported glibc is 2.17, however support for thread_local -dnl did not arrive in glibc until 2.18. if test "x$use_thread_local" = xyes || test "x$use_thread_local" = xauto; then TEMP_LDFLAGS="$LDFLAGS" LDFLAGS="$TEMP_LDFLAGS $PTHREAD_CFLAGS" @@ -1826,6 +1833,8 @@ AC_SUBST(GPROF_LDFLAGS) AC_SUBST(HARDENED_CXXFLAGS) AC_SUBST(HARDENED_CPPFLAGS) AC_SUBST(HARDENED_LDFLAGS) +AC_SUBST(LTO_CXXFLAGS) +AC_SUBST(LTO_LDFLAGS) AC_SUBST(PIC_FLAGS) AC_SUBST(PIE_FLAGS) AC_SUBST(SANITIZER_CXXFLAGS) @@ -1926,7 +1935,7 @@ if test x$bitcoin_enable_qt != xno; then echo " with qr = $use_qr" fi echo " with zmq = $use_zmq" -if test x$enable_fuzz == xno; then +if test x$enable_fuzz = xno; then echo " with test = $use_tests" else echo " with test = not building test_bitcoin because fuzzing is enabled" @@ -1941,6 +1950,7 @@ echo " sanitizers = $use_sanitizers" echo " debug enabled = $enable_debug" echo " gprof enabled = $enable_gprof" echo " werror = $enable_werror" +echo " LTO = $enable_lto" echo echo " target os = $TARGET_OS" echo " build os = $build_os" @@ -1949,7 +1959,7 @@ echo " CC = $CC" echo " CFLAGS = $PTHREAD_CFLAGS $CFLAGS" echo " CPPFLAGS = $DEBUG_CPPFLAGS $HARDENED_CPPFLAGS $CPPFLAGS" echo " CXX = $CXX" -echo " CXXFLAGS = $DEBUG_CXXFLAGS $HARDENED_CXXFLAGS $WARN_CXXFLAGS $NOWARN_CXXFLAGS $ERROR_CXXFLAGS $GPROF_CXXFLAGS $CXXFLAGS" -echo " LDFLAGS = $PTHREAD_LIBS $HARDENED_LDFLAGS $GPROF_LDFLAGS $LDFLAGS" +echo " CXXFLAGS = $LTO_CXXFLAGS $DEBUG_CXXFLAGS $HARDENED_CXXFLAGS $WARN_CXXFLAGS $NOWARN_CXXFLAGS $ERROR_CXXFLAGS $GPROF_CXXFLAGS $CXXFLAGS" +echo " LDFLAGS = $LTO_LDFLAGS $PTHREAD_LIBS $HARDENED_LDFLAGS $GPROF_LDFLAGS $LDFLAGS" echo " ARFLAGS = $ARFLAGS" echo diff --git a/contrib/devtools/security-check.py b/contrib/devtools/security-check.py index ef421aebb1..677557b8fa 100755 --- a/contrib/devtools/security-check.py +++ b/contrib/devtools/security-check.py @@ -121,6 +121,21 @@ def check_PE_RELOC_SECTION(binary) -> bool: '''Check for a reloc section. This is required for functional ASLR.''' return binary.has_relocations +def check_PE_control_flow(binary) -> bool: + ''' + Check for control flow instrumentation + ''' + main = binary.get_symbol('main').value + + section_addr = binary.section_from_rva(main).virtual_address + virtual_address = binary.optional_header.imagebase + section_addr + main + + content = binary.get_content_from_virtual_address(virtual_address, 4, lief.Binary.VA_TYPES.VA) + + if content == [243, 15, 30, 250]: # endbr64 + return True + return False + def check_MACHO_NOUNDEFS(binary) -> bool: ''' Check for no undefined references. @@ -177,7 +192,8 @@ CHECKS = { ('DYNAMIC_BASE', check_PE_DYNAMIC_BASE), ('HIGH_ENTROPY_VA', check_PE_HIGH_ENTROPY_VA), ('NX', check_NX), - ('RELOC_SECTION', check_PE_RELOC_SECTION) + ('RELOC_SECTION', check_PE_RELOC_SECTION), + ('CONTROL_FLOW', check_PE_control_flow), ], 'MACHO': [ ('PIE', check_PIE), diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py index 136a9b70c1..15d4e729ac 100755 --- a/contrib/devtools/symbol-check.py +++ b/contrib/devtools/symbol-check.py @@ -19,35 +19,31 @@ import lief #type:ignore # https://github.com/lief-project/LIEF/pull/562 LIEF_ELF_ARCH_RISCV = lief.ELF.ARCH(243) -# Debian 8 (Jessie) EOL: 2020. https://wiki.debian.org/DebianReleases#Production_Releases +# Debian 9 (Stretch) EOL: 2022. https://wiki.debian.org/DebianReleases#Production_Releases # -# - g++ version 4.9.2 (https://packages.debian.org/search?suite=jessie&arch=any&searchon=names&keywords=g%2B%2B) -# - libc version 2.19 (https://packages.debian.org/search?suite=jessie&arch=any&searchon=names&keywords=libc6) +# - g++ version 6.3.0 (https://packages.debian.org/search?suite=stretch&arch=any&searchon=names&keywords=g%2B%2B) +# - libc version 2.24 (https://packages.debian.org/search?suite=stretch&arch=any&searchon=names&keywords=libc6) # -# Ubuntu 16.04 (Xenial) EOL: 2024. https://wiki.ubuntu.com/Releases +# Ubuntu 16.04 (Xenial) EOL: 2026. https://wiki.ubuntu.com/Releases # -# - g++ version 5.3.1 (https://packages.ubuntu.com/search?keywords=g%2B%2B&searchon=names&suite=xenial§ion=all) -# - libc version 2.23.0 (https://packages.ubuntu.com/search?keywords=libc6&searchon=names&suite=xenial§ion=all) +# - g++ version 5.3.1 +# - libc version 2.23 # -# CentOS 7 EOL: 2024. https://wiki.centos.org/FAQ/General +# CentOS Stream 8 EOL: 2024. https://wiki.centos.org/About/Product # -# - g++ version 4.8.5 (http://mirror.centos.org/centos/7/os/x86_64/Packages/) -# - libc version 2.17 (http://mirror.centos.org/centos/7/os/x86_64/Packages/) -# -# Taking the minimum of these as our target. -# -# According to GNU ABI document (https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html) this corresponds to: -# GCC 4.8.5: GCC_4.8.0 -# (glibc) GLIBC_2_17 +# - g++ version 8.5.0 (http://mirror.centos.org/centos/8-stream/AppStream/x86_64/os/Packages/) +# - libc version 2.28 (http://mirror.centos.org/centos/8-stream/AppStream/x86_64/os/Packages/) # +# See https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html for more info. + MAX_VERSIONS = { 'GCC': (4,8,0), 'GLIBC': { - lief.ELF.ARCH.i386: (2,17), - lief.ELF.ARCH.x86_64: (2,17), - lief.ELF.ARCH.ARM: (2,17), - lief.ELF.ARCH.AARCH64:(2,17), - lief.ELF.ARCH.PPC64: (2,17), + lief.ELF.ARCH.i386: (2,18), + lief.ELF.ARCH.x86_64: (2,18), + lief.ELF.ARCH.ARM: (2,18), + lief.ELF.ARCH.AARCH64:(2,18), + lief.ELF.ARCH.PPC64: (2,18), LIEF_ELF_ARCH_RISCV: (2,27), }, 'LIBATOMIC': (1,0), diff --git a/contrib/devtools/test-security-check.py b/contrib/devtools/test-security-check.py index 0af7cdf5e6..01df863ac0 100755 --- a/contrib/devtools/test-security-check.py +++ b/contrib/devtools/test-security-check.py @@ -70,16 +70,18 @@ class TestSecurityChecks(unittest.TestCase): write_testcode(source) self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--no-nxcompat','-Wl,--disable-reloc-section','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE']), - (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA NX RELOC_SECTION')) + (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA NX RELOC_SECTION CONTROL_FLOW')) self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--disable-reloc-section','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE']), - (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA RELOC_SECTION')) + (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA RELOC_SECTION CONTROL_FLOW')) self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE']), - (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA')) + (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA CONTROL_FLOW')) self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-pie','-fPIE']), - (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA')) # -pie -fPIE does nothing unless --dynamicbase is also supplied + (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA CONTROL_FLOW')) # -pie -fPIE does nothing unless --dynamicbase is also supplied self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--dynamicbase','-Wl,--no-high-entropy-va','-pie','-fPIE']), - (1, executable+': failed HIGH_ENTROPY_VA')) + (1, executable+': failed HIGH_ENTROPY_VA CONTROL_FLOW')) self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--dynamicbase','-Wl,--high-entropy-va','-pie','-fPIE']), + (1, executable+': failed CONTROL_FLOW')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--dynamicbase','-Wl,--high-entropy-va','-pie','-fPIE', '-fcf-protection=full']), (0, '')) clean_files(source, executable) diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py index 5246375fe3..d699e85026 100755 --- a/contrib/devtools/test-symbol-check.py +++ b/contrib/devtools/test-symbol-check.py @@ -44,7 +44,7 @@ class TestSymbolChecks(unittest.TestCase): self.skipTest("test not available for RISC-V") # nextup was introduced in GLIBC 2.24, so is newer than our supported - # glibc (2.17), and available in our release build environment (2.24). + # glibc (2.18), and available in our release build environment (2.24). with open(source, 'w', encoding="utf8") as f: f.write(''' #define _GNU_SOURCE diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index e009f97c60..596d0ca1fb 100755 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -238,9 +238,6 @@ mkdir -p "$OUTDIR" # CONFIGFLAGS CONFIGFLAGS="--enable-reduce-exports --disable-bench --disable-gui-tests --disable-fuzz-binary" -case "$HOST" in - *linux*) CONFIGFLAGS+=" --disable-threadlocal" ;; -esac # CFLAGS HOST_CFLAGS="-O2 -g" @@ -263,7 +260,7 @@ case "$HOST" in *mingw*) HOST_LDFLAGS="-Wl,--no-insert-timestamp" ;; esac -# Using --no-tls-get-addr-optimize retains compatibility with glibc 2.17, by +# Using --no-tls-get-addr-optimize retains compatibility with glibc 2.18, by # avoiding a PowerPC64 optimisation available in glibc 2.22 and later. # https://sourceware.org/binutils/docs-2.35/ld/PowerPC64-ELF64.html case "$HOST" in diff --git a/contrib/install_db4.sh b/contrib/install_db4.sh index 36a4ea08f6..81c88a2ae7 100755 --- a/contrib/install_db4.sh +++ b/contrib/install_db4.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright (c) 2017-2019 The Bitcoin Core developers +# Copyright (c) 2017-2021 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -62,6 +62,12 @@ http_get() { sha256_check "${3}" "${2}" } +# Ensure the commands we use exist on the system +if ! check_exists patch; then + echo "Command-line tool 'patch' not found. Install patch and try again." + exit 1 +fi + mkdir -p "${BDB_PREFIX}" http_get "${BDB_URL}" "${BDB_VERSION}.tar.gz" "${BDB_HASH}" tar -xzvf ${BDB_VERSION}.tar.gz -C "$BDB_PREFIX" diff --git a/depends/README.md b/depends/README.md index bd69768167..5c00807473 100644 --- a/depends/README.md +++ b/depends/README.md @@ -87,14 +87,6 @@ For linux S390X cross compilation: sudo apt-get install g++-s390x-linux-gnu binutils-s390x-linux-gnu -### Install the required dependencies: M1-based macOS - -To be able to build the `qt` package, ensure that Rosetta 2 is installed: - -``` -softwareupdate --install-rosetta -``` - ### Dependency Options The following can be set when running make: `make FOO=bar` diff --git a/depends/packages/boost.mk b/depends/packages/boost.mk index ab29742b55..5fe2b2bbb8 100644 --- a/depends/packages/boost.mk +++ b/depends/packages/boost.mk @@ -27,6 +27,7 @@ $(package)_cxxflags+=-std=c++17 $(package)_cxxflags_linux=-fPIC $(package)_cxxflags_android=-fPIC $(package)_cxxflags_x86_64_darwin=-fcf-protection=full +$(package)_cxxflags_mingw32=-fcf-protection=full endef define $(package)_preprocess_cmds diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 12e0494ad4..4ee7a64a53 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -10,9 +10,10 @@ $(package)_linguist_tools = lrelease lupdate lconvert $(package)_patches = qt.pro qttools_src.pro $(package)_patches += fix_qt_pkgconfig.patch mac-qmake.conf fix_no_printer.patch no-xlib.patch $(package)_patches += support_new_android_ndks.patch fix_android_jni_static.patch dont_hardcode_pwd.patch -$(package)_patches+= no_sdk_version_check.patch +$(package)_patches += dont_hardcode_x86_64.patch no_sdk_version_check.patch $(package)_patches+= fix_lib_paths.patch fix_android_pch.patch $(package)_patches+= qtbase-moc-ignore-gcc-macro.patch fix_limits_header.patch +$(package)_patches+= fix_montery_include.patch $(package)_qttranslations_file_name=qttranslations-$($(package)_suffix) $(package)_qttranslations_sha256_hash=577b0668a777eb2b451c61e8d026d79285371597ce9df06b6dee6c814164b7c3 @@ -127,8 +128,10 @@ $(package)_config_opts_darwin += -device-option MAC_TARGET=$(host) $(package)_config_opts_darwin += -device-option XCODE_VERSION=$(XCODE_VERSION) endif -# for macOS on Apple Silicon (ARM) see https://bugreports.qt.io/browse/QTBUG-85279 +ifneq ($(build_arch),$(host_arch)) $(package)_config_opts_aarch64_darwin += -device-option QMAKE_APPLE_DEVICE_ARCHS=arm64 +$(package)_config_opts_x86_64_darwin += -device-option QMAKE_APPLE_DEVICE_ARCHS=x86_64 +endif $(package)_config_opts_linux = -qt-xcb $(package)_config_opts_linux += -no-xcb-xlib @@ -228,10 +231,12 @@ define $(package)_preprocess_cmds patch -p1 -i $($(package)_patch_dir)/fix_android_jni_static.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_android_pch.patch && \ patch -p1 -i $($(package)_patch_dir)/no-xlib.patch && \ + patch -p1 -i $($(package)_patch_dir)/dont_hardcode_x86_64.patch && \ patch -p1 -i $($(package)_patch_dir)/no_sdk_version_check.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_lib_paths.patch && \ patch -p1 -i $($(package)_patch_dir)/qtbase-moc-ignore-gcc-macro.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_limits_header.patch && \ + patch -p1 -i $($(package)_patch_dir)/fix_montery_include.patch && \ mkdir -p qtbase/mkspecs/macx-clang-linux &&\ cp -f qtbase/mkspecs/macx-clang/qplatformdefs.h qtbase/mkspecs/macx-clang-linux/ &&\ cp -f $($(package)_patch_dir)/mac-qmake.conf qtbase/mkspecs/macx-clang-linux/qmake.conf && \ diff --git a/depends/patches/qt/dont_hardcode_x86_64.patch b/depends/patches/qt/dont_hardcode_x86_64.patch new file mode 100644 index 0000000000..0e1ca6acda --- /dev/null +++ b/depends/patches/qt/dont_hardcode_x86_64.patch @@ -0,0 +1,123 @@ +macOS: Don't hard-code x86_64 as the architecture when using qmake + +Upstream commit: + - Qt 6.1: 9082cc8e8d5a6441dabe5e7a95bc0cd9085b95fe + +For other Qt branches see +https://codereview.qt-project.org/q/I70db7e4c27f0d3da5d0af33cb491d72c312d3fa8 + + +--- old/qtbase/configure.json ++++ new/qtbase/configure.json +@@ -208,11 +208,18 @@ + + "testTypeDependencies": { + "linkerSupportsFlag": [ "use_gold_linker" ], +- "verifySpec": [ "shared", "use_gold_linker", "compiler-flags", "qmakeargs", "commit" ], ++ "verifySpec": [ ++ "shared", ++ "use_gold_linker", ++ "compiler-flags", "qmakeargs", ++ "simulator_and_device", ++ "thread", ++ "commit" ], + "compile": [ "verifyspec" ], + "detectPkgConfig": [ "cross_compile", "machineTuple" ], + "library": [ "pkg-config", "compiler-flags" ], +- "getPkgConfigVariable": [ "pkg-config" ] ++ "getPkgConfigVariable": [ "pkg-config" ], ++ "architecture" : [ "verifyspec" ] + }, + + "testTypeAliases": { +@@ -653,7 +660,7 @@ + }, + "architecture": { + "label": "Architecture", +- "output": [ "architecture" ] ++ "output": [ "architecture", "commitConfig" ] + }, + "pkg-config": { + "label": "Using pkg-config", +diff --git a/configure.pri b/configure.pri +index 33c90a8c2f..71767e29d6 100644 + +--- old/qtbase/configure.pri ++++ new/qtbase/configure.pri +@@ -642,6 +642,13 @@ defineTest(qtConfOutput_commitOptions) { + write_file($$QT_BUILD_TREE/mkspecs/qdevice.pri, $${currentConfig}.output.devicePro)|error() + } + ++# Output is written after configuring each Qt module, ++# but some tests within a module might depend on the ++# configuration output of previous tests. ++defineTest(qtConfOutput_commitConfig) { ++ qtConfProcessOutput() ++} ++ + # type (empty or 'host'), option name, default value + defineTest(processQtPath) { + out_var = config.rel_input.$${2} +diff --git a/mkspecs/common/macx.conf b/mkspecs/common/macx.conf +index 7d4a406134..de96c12fc9 100644 + +--- old/qtbase/mkspecs/common/macx.conf ++++ new/qtbase/mkspecs/common/macx.conf +@@ -6,7 +6,6 @@ QMAKE_PLATFORM += macos osx macx + QMAKE_MAC_SDK = macosx + + QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.12 +-QMAKE_APPLE_DEVICE_ARCHS = x86_64 + + QT_MAC_SDK_VERSION_MIN = 10.13 + QT_MAC_SDK_VERSION_MAX = 11.0 +diff --git a/mkspecs/features/mac/default_post.prf b/mkspecs/features/mac/default_post.prf +index d052808c14..0a89effe87 100644 + +--- old/qtbase/mkspecs/features/mac/default_post.prf ++++ new/qtbase/mkspecs/features/mac/default_post.prf +@@ -89,6 +89,11 @@ app_extension_api_only { + QMAKE_LFLAGS += $$QMAKE_CFLAGS_APPLICATION_EXTENSION + } + ++# Non-universal builds do not set QMAKE_APPLE_DEVICE_ARCHS, ++# so we pick it up from what the arch test resolved instead. ++isEmpty(QMAKE_APPLE_DEVICE_ARCHS): \ ++ QMAKE_APPLE_DEVICE_ARCHS = $$QT_ARCH ++ + macx-xcode { + qmake_pkginfo_typeinfo.name = QMAKE_PKGINFO_TYPEINFO + !isEmpty(QMAKE_PKGINFO_TYPEINFO): \ +@@ -144,9 +149,6 @@ macx-xcode { + simulator: VALID_SIMULATOR_ARCHS = $$QMAKE_APPLE_SIMULATOR_ARCHS + VALID_ARCHS = $$VALID_DEVICE_ARCHS $$VALID_SIMULATOR_ARCHS + +- isEmpty(VALID_ARCHS): \ +- error("QMAKE_APPLE_DEVICE_ARCHS or QMAKE_APPLE_SIMULATOR_ARCHS must contain at least one architecture") +- + single_arch: VALID_ARCHS = $$first(VALID_ARCHS) + + ACTIVE_ARCHS = $(filter $(EXPORT_VALID_ARCHS), $(ARCHS)) +diff --git a/mkspecs/features/toolchain.prf b/mkspecs/features/toolchain.prf +index 5003679bd0..c7c080cb07 100644 + +--- old/qtbase/mkspecs/features/toolchain.prf ++++ new/qtbase/mkspecs/features/toolchain.prf +@@ -182,9 +182,14 @@ isEmpty($${target_prefix}.INCDIRS) { + # UIKit simulator platforms will see the device SDK's sysroot in + # QMAKE_DEFAULT_*DIRS, because they're handled in a single build pass. + darwin { +- # Clang doesn't pick up the architecture from the sysroot, and will +- # default to the host architecture, so we need to manually set it. +- cxx_flags += -arch $$QMAKE_APPLE_DEVICE_ARCHS ++ uikit { ++ # Clang doesn't automatically pick up the architecture, just because ++ # we're passing the iOS sysroot below, and we will end up building the ++ # test for the host architecture, resulting in linker errors when ++ # linking against the iOS libraries. We work around this by passing ++ # the architecture explicitly. ++ cxx_flags += -arch $$first(QMAKE_APPLE_DEVICE_ARCHS) ++ } + + uikit:macx-xcode: \ + cxx_flags += -isysroot $$sdk_path_device.value diff --git a/depends/patches/qt/fix_montery_include.patch b/depends/patches/qt/fix_montery_include.patch new file mode 100644 index 0000000000..38b700addf --- /dev/null +++ b/depends/patches/qt/fix_montery_include.patch @@ -0,0 +1,21 @@ +From dece6f5840463ae2ddf927d65eb1b3680e34a547 +From: Øystein Heskestad <oystein.heskestad@qt.io> +Date: Wed, 27 Oct 2021 13:07:46 +0200 +Subject: [PATCH] Add missing macOS header file that was indirectly included before + +See: https://bugreports.qt.io/browse/QTBUG-97855 + +Upstream Commits: + - Qt 6.2: c884bf138a21dd7320e35cef34d24e22e74d7ce0 + +diff --git a/qtbase/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h b/qtbase/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h +index e070ba97..07c75b04 100644 +--- a/qtbase/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h ++++ b/qtbase/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h +@@ -40,6 +40,7 @@ + #ifndef QIOSURFACEGRAPHICSBUFFER_H + #define QIOSURFACEGRAPHICSBUFFER_H + ++#include <CoreGraphics/CGColorSpace.h> + #include <qpa/qplatformgraphicsbuffer.h> + #include <private/qcore_mac_p.h> diff --git a/depends/patches/qt/mac-qmake.conf b/depends/patches/qt/mac-qmake.conf index 190ab7a160..e4bfaa1463 100644 --- a/depends/patches/qt/mac-qmake.conf +++ b/depends/patches/qt/mac-qmake.conf @@ -13,7 +13,6 @@ QMAKE_MAC_SDK.macosx.Path = $${MAC_SDK_PATH} QMAKE_MAC_SDK.macosx.platform_name = macosx QMAKE_MAC_SDK.macosx.SDKVersion = $${MAC_SDK_VERSION} QMAKE_MAC_SDK.macosx.PlatformPath = /phony -QMAKE_APPLE_DEVICE_ARCHS=x86_64 !host_build: QMAKE_CFLAGS += -target $${MAC_TARGET} !host_build: QMAKE_OBJECTIVE_CFLAGS += $$QMAKE_CFLAGS !host_build: QMAKE_CXXFLAGS += $$QMAKE_CFLAGS diff --git a/doc/dependencies.md b/doc/dependencies.md index 0c1fd6ba98..6ccb53f6b3 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -12,7 +12,7 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | fontconfig | [2.12.1](https://www.freedesktop.org/software/fontconfig/release/) | | No | Yes | | | FreeType | [2.7.1](https://download.savannah.gnu.org/releases/freetype) | | No | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Android only) | | GCC | | [8.1](https://gcc.gnu.org/) (C++17 & std::filesystem support) | | | | -| glibc | | [2.17](https://www.gnu.org/software/libc/) | | | | | +| glibc | | [2.18](https://www.gnu.org/software/libc/) | | | | | | HarfBuzz-NG | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | | libevent | [2.1.12-stable](https://github.com/libevent/libevent/releases) | [2.0.21](https://github.com/bitcoin/bitcoin/pull/18676) | No | | | | libnatpmp | git commit [4536032...](https://github.com/miniupnp/libnatpmp/tree/4536032ae32268a45c073a4d5e91bbab4534773a) | | No | | | diff --git a/doc/developer-notes.md b/doc/developer-notes.md index 7ff1d36442..1888897856 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -1254,6 +1254,12 @@ A few guidelines for introducing and reviewing new RPC interfaces: - *Rationale*: User-facing consistency. +- Use `fs::path::u8string()` and `fs::u8path()` functions when converting path + to JSON strings, not `fs::PathToString` and `fs::PathFromString` + + - *Rationale*: JSON strings are Unicode strings, not byte strings, and + RFC8259 requires JSON to be encoded as UTF-8. + Internal interface guidelines ----------------------------- diff --git a/doc/release-notes-16807.md b/doc/release-notes-16807.md new file mode 100644 index 0000000000..5027550a99 --- /dev/null +++ b/doc/release-notes-16807.md @@ -0,0 +1,6 @@ +Updated RPCs +------------ + +- The `validateaddress` RPC now optionally returns an `error_locations` array, with the indices of +invalid characters in the address. For example, this will return the locations of up to two Bech32 +errors.
\ No newline at end of file diff --git a/doc/release-notes.md b/doc/release-notes.md index b460cd3eb2..4483dee1dd 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -127,6 +127,10 @@ Updated settings mean `-persistmempool=1`. Passing `-persistmempool=0`, `-persistmempool=1` and `-nopersistmempool` is unaffected. (#23061) +- `-maxuploadtarget` now allows human readable byte units [k|K|m|M|g|G|t|T]. + E.g. `-maxuploadtarget=500g`. No whitespace, +- or fractions allowed. + Default is `M` if no suffix provided. (#23249) + Tools and Utilities ------------------- diff --git a/src/Makefile.am b/src/Makefile.am index f7cd4966b7..9d79ee0e62 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,8 +8,8 @@ print-%: FORCE DIST_SUBDIRS = secp256k1 -AM_LDFLAGS = $(LIBTOOL_LDFLAGS) $(HARDENED_LDFLAGS) $(GPROF_LDFLAGS) $(SANITIZER_LDFLAGS) -AM_CXXFLAGS = $(DEBUG_CXXFLAGS) $(HARDENED_CXXFLAGS) $(WARN_CXXFLAGS) $(NOWARN_CXXFLAGS) $(ERROR_CXXFLAGS) $(GPROF_CXXFLAGS) $(SANITIZER_CXXFLAGS) +AM_LDFLAGS = $(LIBTOOL_LDFLAGS) $(HARDENED_LDFLAGS) $(GPROF_LDFLAGS) $(SANITIZER_LDFLAGS) $(LTO_LDFLAGS) +AM_CXXFLAGS = $(DEBUG_CXXFLAGS) $(HARDENED_CXXFLAGS) $(WARN_CXXFLAGS) $(NOWARN_CXXFLAGS) $(ERROR_CXXFLAGS) $(GPROF_CXXFLAGS) $(SANITIZER_CXXFLAGS) $(LTO_CXXFLAGS) AM_CPPFLAGS = $(DEBUG_CPPFLAGS) $(HARDENED_CPPFLAGS) AM_LIBTOOLFLAGS = --preserve-dup-deps PTHREAD_FLAGS = $(PTHREAD_CFLAGS) $(PTHREAD_LIBS) @@ -246,6 +246,7 @@ BITCOIN_CORE_H = \ util/macros.h \ util/message.h \ util/moneystr.h \ + util/overloaded.h \ util/rbf.h \ util/readwritefile.h \ util/serfloat.h \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 88a1ef798e..06d195aaaf 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -51,6 +51,7 @@ FUZZ_SUITE_LD_COMMON = \ $(BOOST_LIBS) \ $(LIBMEMENV) \ $(LIBSECP256K1) \ + $(MINISKETCH_LIBS) \ $(EVENT_LIBS) \ $(EVENT_PTHREADS_LIBS) @@ -160,6 +161,7 @@ BITCOIN_TESTS += \ wallet/test/wallet_tests.cpp \ wallet/test/walletdb_tests.cpp \ wallet/test/wallet_crypto_tests.cpp \ + wallet/test/wallet_transaction_tests.cpp \ wallet/test/coinselector_tests.cpp \ wallet/test/init_tests.cpp \ wallet/test/ismine_tests.cpp \ @@ -259,6 +261,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/locale.cpp \ test/fuzz/merkleblock.cpp \ test/fuzz/message.cpp \ + test/fuzz/minisketch.cpp \ test/fuzz/muhash.cpp \ test/fuzz/multiplication_overflow.cpp \ test/fuzz/net.cpp \ diff --git a/src/banman.h b/src/banman.h index f495dab49d..6cb6304744 100644 --- a/src/banman.h +++ b/src/banman.h @@ -95,4 +95,4 @@ private: CRollingBloomFilter m_discouraged GUARDED_BY(m_cs_banned) {50000, 0.000001}; }; -#endif +#endif // BITCOIN_BANMAN_H diff --git a/src/bech32.cpp b/src/bech32.cpp index 9da2488ef2..ea44480a6c 100644 --- a/src/bech32.cpp +++ b/src/bech32.cpp @@ -1,4 +1,5 @@ // Copyright (c) 2017, 2021 Pieter Wuille +// Copyright (c) 2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -30,6 +31,183 @@ const int8_t CHARSET_REV[128] = { 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 }; +// We work with the finite field GF(1024) defined as a degree 2 extension of the base field GF(32) +// The defining polynomial of the extension is x^2 + 9x + 23 +// Let (e) be a primitive element of GF(1024), that is, a generator of the field. +// Every non-zero element of the field can then be represented as (e)^k for some power k. +// The array GF1024_EXP contains all these powers of (e) - GF1024_EXP[k] = (e)^k in GF(1024). +// Conversely, GF1024_LOG contains the discrete logarithms of these powers, so +// GF1024_LOG[GF1024_EXP[k]] == k +// Each element v of GF(1024) is encoded as a 10 bit integer in the following way: +// v = v1 || v0 where v0, v1 are 5-bit integers (elements of GF(32)). +// +// The element (e) is encoded as 9 || 15. Given (v), we compute (e)*(v) by multiplying in the following way: +// v0' = 27*v1 + 15*v0 +// v1' = 6*v1 + 9*v0 +// e*v = v1' || v0' +// +// The following sage code can be used to reproduce both _EXP and _LOG arrays +// GF1024_LOG = [-1] + [0] * 1023 +// GF1024_EXP = [1] * 1024 +// v = 1 +// for i in range(1, 1023): +// v0 = v & 31 +// v1 = v >> 5 +// v0n = F.fetch_int(27)*F.fetch_int(v1) + F.fetch_int(15)*F.fetch_int(v0) +// v1n = F.fetch_int(6)*F.fetch_int(v1) + F.fetch_int(9)*F.fetch_int(v0) +// v = v1n.integer_representation() << 5 | v0n.integer_representation() +// GF1024_EXP[i] = v +// GF1024_LOG[v] = i + +const int16_t GF1024_EXP[] = { + 1, 303, 635, 446, 997, 640, 121, 142, 959, 420, 350, 438, 166, 39, 543, + 335, 831, 691, 117, 632, 719, 97, 107, 374, 558, 797, 54, 150, 858, 877, + 724, 1013, 294, 23, 354, 61, 164, 633, 992, 538, 469, 659, 174, 868, 184, + 809, 766, 563, 866, 851, 257, 520, 45, 770, 535, 524, 408, 213, 436, 760, + 472, 330, 933, 799, 616, 361, 15, 391, 756, 814, 58, 608, 554, 680, 993, + 821, 942, 813, 843, 484, 193, 935, 321, 919, 572, 741, 423, 559, 562, + 589, 296, 191, 493, 685, 891, 665, 435, 60, 395, 2, 606, 511, 853, 746, + 32, 219, 284, 631, 840, 661, 837, 332, 78, 311, 670, 887, 111, 195, 505, + 190, 194, 214, 709, 380, 819, 69, 261, 957, 1018, 161, 739, 588, 7, 708, + 83, 328, 507, 736, 317, 899, 47, 348, 1000, 345, 882, 245, 367, 996, 943, + 514, 304, 90, 804, 295, 312, 793, 387, 833, 249, 921, 660, 618, 823, 496, + 722, 30, 782, 225, 892, 93, 480, 372, 112, 738, 867, 636, 890, 950, 968, + 386, 622, 642, 551, 369, 234, 846, 382, 365, 442, 592, 343, 986, 122, + 1023, 59, 847, 81, 790, 4, 437, 983, 931, 244, 64, 415, 529, 487, 944, + 35, 938, 664, 156, 583, 53, 999, 222, 390, 987, 341, 388, 389, 170, 721, + 879, 138, 522, 627, 765, 322, 230, 440, 14, 168, 143, 656, 991, 224, 595, + 550, 94, 657, 752, 667, 1005, 451, 734, 744, 638, 292, 585, 157, 872, + 590, 601, 827, 774, 930, 475, 571, 33, 500, 871, 969, 173, 21, 828, 450, + 1009, 147, 960, 705, 201, 228, 998, 497, 1021, 613, 688, 772, 508, 36, + 366, 715, 468, 956, 725, 730, 861, 425, 647, 701, 221, 759, 95, 958, 139, + 805, 8, 835, 679, 614, 449, 128, 791, 299, 974, 617, 70, 628, 57, 273, + 430, 67, 750, 405, 780, 703, 643, 776, 778, 340, 171, 1022, 276, 308, + 495, 243, 644, 460, 857, 28, 336, 286, 41, 695, 448, 431, 364, 149, 43, + 233, 63, 762, 902, 181, 240, 501, 584, 434, 275, 1008, 444, 443, 895, + 812, 612, 927, 383, 66, 961, 1006, 690, 346, 3, 881, 900, 747, 271, 672, + 162, 402, 456, 748, 971, 755, 490, 105, 808, 977, 72, 732, 182, 897, 625, + 163, 189, 947, 850, 46, 115, 403, 231, 151, 629, 278, 874, 16, 934, 110, + 492, 898, 256, 807, 598, 700, 498, 140, 481, 91, 523, 860, 134, 252, 771, + 824, 119, 38, 816, 820, 641, 342, 757, 513, 577, 990, 463, 40, 920, 955, + 17, 649, 533, 82, 103, 896, 862, 728, 259, 86, 466, 87, 253, 556, 323, + 457, 963, 432, 845, 527, 745, 849, 863, 1015, 888, 488, 567, 727, 132, + 674, 764, 109, 669, 6, 1003, 552, 246, 542, 96, 324, 781, 912, 248, 694, + 239, 980, 210, 880, 683, 144, 177, 325, 546, 491, 326, 339, 623, 941, 92, + 207, 783, 462, 263, 483, 517, 1012, 9, 620, 220, 984, 548, 512, 878, 421, + 113, 973, 280, 962, 159, 310, 945, 268, 465, 806, 889, 199, 76, 873, 865, + 34, 645, 227, 290, 418, 693, 926, 80, 569, 639, 11, 50, 291, 141, 206, + 544, 949, 185, 518, 133, 909, 135, 467, 376, 646, 914, 678, 841, 954, + 318, 242, 939, 951, 743, 1017, 976, 359, 167, 264, 100, 241, 218, 51, 12, + 758, 368, 453, 309, 192, 648, 826, 553, 473, 101, 478, 673, 397, 1001, + 118, 265, 331, 650, 356, 982, 652, 655, 510, 634, 145, 414, 830, 924, + 526, 966, 298, 737, 18, 504, 401, 697, 360, 288, 1020, 842, 203, 698, + 537, 676, 279, 581, 619, 536, 907, 876, 1019, 398, 152, 1010, 994, 68, + 42, 454, 580, 836, 99, 565, 137, 379, 503, 22, 77, 582, 282, 412, 352, + 611, 347, 300, 266, 570, 270, 911, 729, 44, 557, 108, 946, 637, 597, 461, + 630, 615, 238, 763, 681, 718, 334, 528, 200, 459, 413, 79, 24, 229, 713, + 906, 579, 384, 48, 893, 370, 923, 202, 917, 98, 794, 754, 197, 530, 662, + 52, 712, 677, 56, 62, 981, 509, 267, 789, 885, 561, 316, 684, 596, 226, + 13, 985, 779, 123, 720, 576, 753, 948, 406, 125, 315, 104, 519, 426, 502, + 313, 566, 1016, 767, 796, 281, 749, 740, 136, 84, 908, 424, 936, 198, + 355, 274, 735, 967, 5, 154, 428, 541, 785, 704, 486, 671, 600, 532, 381, + 540, 574, 187, 88, 378, 216, 621, 499, 419, 922, 485, 494, 476, 255, 114, + 188, 668, 297, 400, 918, 787, 158, 25, 458, 178, 564, 422, 768, 73, 1011, + 717, 575, 404, 547, 196, 829, 237, 394, 301, 37, 65, 176, 106, 89, 85, + 675, 979, 534, 803, 995, 363, 593, 120, 417, 452, 26, 699, 822, 223, 169, + 416, 235, 609, 773, 211, 607, 208, 302, 852, 965, 603, 357, 761, 247, + 817, 539, 250, 232, 272, 129, 568, 848, 624, 396, 710, 525, 183, 686, 10, + 285, 856, 307, 811, 160, 972, 55, 441, 289, 723, 305, 373, 351, 153, 733, + 409, 506, 975, 838, 573, 970, 988, 913, 471, 205, 337, 49, 594, 777, 549, + 815, 277, 27, 916, 333, 353, 844, 800, 146, 751, 186, 375, 769, 358, 392, + 883, 474, 788, 602, 74, 130, 329, 212, 155, 131, 102, 687, 293, 870, 742, + 726, 427, 217, 834, 904, 29, 127, 869, 407, 338, 832, 470, 482, 810, 399, + 439, 393, 604, 929, 682, 447, 714, 251, 455, 875, 319, 477, 464, 521, + 258, 377, 937, 489, 792, 172, 314, 327, 124, 20, 531, 953, 591, 886, 320, + 696, 71, 859, 578, 175, 587, 707, 663, 283, 179, 795, 989, 702, 940, 371, + 692, 689, 555, 903, 410, 651, 75, 429, 818, 362, 894, 515, 31, 545, 666, + 706, 952, 864, 269, 254, 349, 711, 802, 716, 784, 1007, 925, 801, 445, + 148, 260, 658, 385, 287, 262, 204, 126, 586, 1004, 236, 165, 854, 411, + 932, 560, 19, 215, 1002, 775, 653, 928, 901, 964, 884, 798, 839, 786, + 433, 610, 116, 855, 180, 479, 910, 1014, 599, 915, 905, 306, 516, 731, + 626, 978, 825, 344, 605, 654, 209 +}; +// As above, GF1024_EXP contains all elements of GF(1024) except 0 +static_assert(std::size(GF1024_EXP) == 1023, "GF1024_EXP length should be 1023"); + +const int16_t GF1024_LOG[] = { + -1, 0, 99, 363, 198, 726, 462, 132, 297, 495, 825, 528, 561, 693, 231, + 66, 396, 429, 594, 990, 924, 264, 627, 33, 660, 759, 792, 858, 330, 891, + 165, 957, 104, 259, 518, 208, 280, 776, 416, 13, 426, 333, 618, 339, 641, + 52, 388, 140, 666, 852, 529, 560, 678, 213, 26, 832, 681, 309, 70, 194, + 97, 35, 682, 341, 203, 777, 358, 312, 617, 125, 307, 931, 379, 765, 875, + 951, 515, 628, 112, 659, 525, 196, 432, 134, 717, 781, 438, 440, 740, + 780, 151, 408, 487, 169, 239, 293, 467, 21, 672, 622, 557, 571, 881, 433, + 704, 376, 779, 22, 643, 460, 398, 116, 172, 503, 751, 389, 1004, 18, 576, + 415, 789, 6, 192, 696, 923, 702, 981, 892, 302, 816, 876, 880, 457, 537, + 411, 539, 716, 624, 224, 295, 406, 531, 7, 233, 478, 586, 864, 268, 974, + 338, 27, 392, 614, 839, 727, 879, 211, 250, 758, 507, 830, 129, 369, 384, + 36, 985, 12, 555, 232, 796, 221, 321, 920, 263, 42, 934, 778, 479, 761, + 939, 1006, 344, 381, 823, 44, 535, 866, 739, 752, 385, 119, 91, 566, 80, + 120, 117, 771, 675, 721, 514, 656, 271, 670, 602, 980, 850, 532, 488, + 803, 1022, 475, 801, 878, 57, 121, 991, 742, 888, 559, 105, 497, 291, + 215, 795, 236, 167, 692, 520, 272, 661, 229, 391, 814, 340, 184, 798, + 984, 773, 650, 473, 345, 558, 548, 326, 202, 145, 465, 810, 471, 158, + 813, 908, 412, 441, 964, 750, 401, 50, 915, 437, 975, 126, 979, 491, 556, + 577, 636, 685, 510, 963, 638, 367, 815, 310, 723, 349, 323, 857, 394, + 606, 505, 713, 630, 938, 106, 826, 332, 978, 599, 834, 521, 530, 248, + 883, 32, 153, 90, 754, 592, 304, 635, 775, 804, 1, 150, 836, 1013, 828, + 324, 565, 508, 113, 154, 708, 921, 703, 689, 138, 547, 911, 929, 82, 228, + 443, 468, 480, 483, 922, 135, 877, 61, 578, 111, 860, 654, 15, 331, 851, + 895, 484, 320, 218, 420, 190, 1019, 143, 362, 634, 141, 965, 10, 838, + 632, 861, 34, 722, 580, 808, 869, 554, 598, 65, 954, 787, 337, 187, 281, + 146, 563, 183, 668, 944, 171, 837, 23, 867, 541, 916, 741, 625, 123, 736, + 186, 357, 665, 977, 179, 156, 219, 220, 216, 67, 870, 902, 774, 98, 820, + 574, 613, 900, 755, 596, 370, 390, 769, 314, 701, 894, 56, 841, 949, 987, + 631, 658, 587, 204, 797, 790, 522, 745, 9, 502, 763, 86, 719, 288, 706, + 887, 728, 952, 311, 336, 446, 1002, 348, 96, 58, 199, 11, 901, 230, 833, + 188, 352, 351, 973, 3, 906, 335, 301, 266, 244, 791, 564, 619, 909, 371, + 444, 760, 657, 328, 647, 490, 425, 913, 511, 439, 540, 283, 40, 897, 849, + 60, 570, 872, 257, 749, 912, 572, 1007, 170, 407, 898, 492, 79, 747, 732, + 206, 454, 918, 375, 482, 399, 92, 748, 325, 163, 274, 405, 744, 260, 346, + 707, 626, 595, 118, 842, 136, 279, 684, 584, 101, 500, 422, 149, 956, + 1014, 493, 536, 705, 51, 914, 225, 409, 55, 822, 590, 448, 655, 205, 676, + 925, 735, 431, 784, 54, 609, 604, 39, 812, 737, 729, 466, 14, 533, 958, + 481, 770, 499, 855, 238, 182, 464, 569, 72, 947, 442, 642, 24, 87, 989, + 688, 88, 47, 762, 623, 709, 455, 817, 526, 637, 258, 84, 845, 738, 768, + 698, 423, 933, 664, 620, 607, 629, 212, 347, 249, 982, 935, 131, 89, 252, + 927, 189, 788, 853, 237, 691, 646, 403, 1010, 734, 253, 874, 807, 903, + 1020, 100, 802, 71, 799, 1003, 633, 355, 276, 300, 649, 64, 306, 161, + 608, 496, 743, 180, 485, 819, 383, 1016, 226, 308, 393, 648, 107, 19, 37, + 585, 2, 175, 645, 247, 527, 5, 419, 181, 317, 327, 519, 542, 289, 567, + 430, 579, 950, 582, 994, 1021, 583, 234, 240, 976, 41, 160, 109, 677, + 937, 210, 95, 959, 242, 753, 461, 114, 733, 368, 573, 458, 782, 605, 680, + 544, 299, 73, 652, 905, 477, 690, 93, 824, 882, 277, 946, 361, 17, 945, + 523, 472, 334, 930, 597, 603, 793, 404, 290, 942, 316, 731, 270, 960, + 936, 133, 122, 821, 966, 679, 662, 907, 282, 968, 767, 653, 20, 697, 222, + 164, 835, 30, 285, 886, 456, 436, 640, 286, 1015, 380, 840, 245, 724, + 137, 593, 173, 130, 715, 85, 885, 551, 246, 449, 103, 366, 372, 714, 313, + 865, 241, 699, 674, 374, 68, 421, 562, 292, 59, 809, 342, 651, 459, 227, + 46, 711, 764, 868, 53, 413, 278, 800, 255, 993, 318, 854, 319, 695, 315, + 469, 166, 489, 969, 730, 1001, 757, 873, 686, 197, 303, 919, 155, 673, + 940, 712, 25, 999, 63, 863, 972, 967, 785, 152, 296, 512, 402, 377, 45, + 899, 829, 354, 77, 69, 856, 417, 811, 953, 124, 418, 75, 794, 162, 414, + 1018, 568, 254, 265, 772, 588, 16, 896, 157, 889, 298, 621, 110, 844, + 1000, 108, 545, 601, 78, 862, 447, 185, 195, 818, 450, 387, 49, 805, 102, + 986, 1005, 827, 329, 28, 932, 410, 287, 435, 451, 962, 517, 48, 174, 43, + 893, 884, 261, 251, 516, 395, 910, 611, 29, 501, 223, 476, 364, 144, 871, + 998, 687, 928, 115, 453, 513, 176, 94, 168, 667, 955, 353, 434, 382, 400, + 139, 365, 996, 343, 948, 890, 1012, 663, 610, 718, 538, 1008, 639, 470, + 848, 543, 1011, 859, 671, 756, 83, 427, 159, 746, 669, 589, 971, 524, + 356, 995, 904, 256, 201, 988, 62, 397, 81, 720, 917, 209, 549, 943, 486, + 76, 148, 207, 509, 644, 386, 700, 534, 177, 550, 961, 926, 546, 428, 284, + 127, 294, 8, 269, 359, 506, 445, 997, 806, 591, 725, 178, 262, 846, 373, + 831, 504, 305, 843, 553, 378, 1017, 783, 474, 683, 581, 200, 498, 694, + 191, 217, 847, 941, 424, 235, 38, 74, 616, 786, 147, 4, 273, 214, 142, + 575, 992, 463, 983, 243, 360, 970, 350, 267, 615, 766, 494, 31, 1009, + 452, 710, 552, 128, 612, 600, 275, 322, 193 +}; +static_assert(std::size(GF1024_LOG) == 1024, "GF1024_EXP length should be 1024"); + /* Determine the final constant to use for the specified encoding. */ uint32_t EncodingConstant(Encoding encoding) { assert(encoding == Encoding::BECH32 || encoding == Encoding::BECH32M); @@ -127,12 +305,116 @@ uint32_t PolyMod(const data& v) return c; } +/** Syndrome computes the values s_j = R(e^j) for j in [997, 998, 999]. As described above, the + * generator polynomial G is the LCM of the minimal polynomials of (e)^997, (e)^998, and (e)^999. + * + * Consider a codeword with errors, of the form R(x) = C(x) + E(x). The residue is the bit-packed + * result of computing R(x) mod G(X), where G is the generator of the code. Because C(x) is a valid + * codeword, it is a multiple of G(X), so the residue is in fact just E(x) mod G(x). Note that all + * of the (e)^j are roots of G(x) by definition, so R((e)^j) = E((e)^j). + * + * Syndrome returns the three values packed into a 30-bit integer, where each 10 bits is one value. + */ +uint32_t Syndrome(const uint32_t residue) { + // Let R(x) = r1*x^5 + r2*x^4 + r3*x^3 + r4*x^2 + r5*x + r6 + // low is the first 5 bits, corresponding to the r6 in the residue + // (the constant term of the polynomial). + + uint32_t low = residue & 0x1f; + + // Recall that XOR corresponds to addition in a characteristic 2 field. + // + // To compute R((e)^j), we are really computing: + // r1*(e)^(j*5) + r2*(e)^(j*4) + r3*(e)^(j*3) + r4*(e)^(j*2) + r5*(e)^j + r6 + // Now note that all of the (e)^(j*i) for i in [5..0] are constants and can be precomputed + // for efficiency. But even more than that, we can consider each coefficient as a bit-string. + // For example, take r5 = (b_5, b_4, b_3, b_2, b_1) written out as 5 bits. Then: + // r5*(e)^j = b_1*(e)^j + b_2*(2*(e)^j) + b_3*(4*(e)^j) + b_4*(8*(e)^j) + b_5*(16*(e)^j) + // where all the (2^i*(e)^j) are constants and can be precomputed. Then we just add each + // of these corresponding constants to our final value based on the bit values b_i. + // This is exactly what is done below. Note that all three values of s_j for j in (997, 998, + // 999) are computed simultaneously. + // + // We begin by setting s_j = low = r6 for all three values of j, because these are unconditional. + // Then for each following bit, we add the corresponding precomputed constant if the bit is 1. + // For example, 0x31edd3c4 is 1100011110 1101110100 1111000100 when unpacked in groups of 10 + // bits, corresponding exactly to a^999 || a^998 || a^997 (matching the corresponding values in + // GF1024_EXP above). + // + // The following sage code reproduces these constants: + // for k in range(1, 6): + // for b in [1,2,4,8,16]: + // c0 = GF1024_EXP[(997*k + GF1024_LOG[b]) % 1023] + // c1 = GF1024_EXP[(998*k + GF1024_LOG[b]) % 1023] + // c2 = GF1024_EXP[(999*k + GF1024_LOG[b]) % 1023] + // c = c2 << 20 | c1 << 10 | c0 + // print("0x%x" % c) + + return low ^ (low << 10) ^ (low << 20) ^ + ((residue >> 5) & 1 ? 0x31edd3c4 : 0) ^ + ((residue >> 6) & 1 ? 0x335f86a8 : 0) ^ + ((residue >> 7) & 1 ? 0x363b8870 : 0) ^ + ((residue >> 8) & 1 ? 0x3e6390c9 : 0) ^ + ((residue >> 9) & 1 ? 0x2ec72192 : 0) ^ + ((residue >> 10) & 1 ? 0x1046f79d : 0) ^ + ((residue >> 11) & 1 ? 0x208d4e33 : 0) ^ + ((residue >> 12) & 1 ? 0x130ebd6f : 0) ^ + ((residue >> 13) & 1 ? 0x2499fade : 0) ^ + ((residue >> 14) & 1 ? 0x1b27d4b5 : 0) ^ + ((residue >> 15) & 1 ? 0x04be1eb4 : 0) ^ + ((residue >> 16) & 1 ? 0x0968b861 : 0) ^ + ((residue >> 17) & 1 ? 0x1055f0c2 : 0) ^ + ((residue >> 18) & 1 ? 0x20ab4584 : 0) ^ + ((residue >> 19) & 1 ? 0x1342af08 : 0) ^ + ((residue >> 20) & 1 ? 0x24f1f318 : 0) ^ + ((residue >> 21) & 1 ? 0x1be34739 : 0) ^ + ((residue >> 22) & 1 ? 0x35562f7b : 0) ^ + ((residue >> 23) & 1 ? 0x3a3c5bff : 0) ^ + ((residue >> 24) & 1 ? 0x266c96f7 : 0) ^ + ((residue >> 25) & 1 ? 0x25c78b65 : 0) ^ + ((residue >> 26) & 1 ? 0x1b1f13ea : 0) ^ + ((residue >> 27) & 1 ? 0x34baa2f4 : 0) ^ + ((residue >> 28) & 1 ? 0x3b61c0e1 : 0) ^ + ((residue >> 29) & 1 ? 0x265325c2 : 0); +} + /** Convert to lower case. */ inline unsigned char LowerCase(unsigned char c) { return (c >= 'A' && c <= 'Z') ? (c - 'A') + 'a' : c; } +void push_range(int from, int to, std::vector<int>& vec) +{ + for (int i = from; i < to; i++) { + vec.push_back(i); + } +} + +/** Return index of first invalid character in a Bech32 string. */ +bool CheckCharacters(const std::string& str, std::vector<int>& errors) { + bool lower = false, upper = false; + for (size_t i = 0; i < str.size(); ++i) { + unsigned char c = str[i]; + if (c >= 'a' && c <= 'z') { + if (upper) { + errors.push_back(i); + } else { + lower = true; + } + } else if (c >= 'A' && c <= 'Z') { + if (lower) { + errors.push_back(i); + } else { + upper = true; + } + } else if (c < 33 || c > 126) { + errors.push_back(i); + } + } + return errors.empty(); +} + /** Expand a HRP for use in checksum computation. */ data ExpandHRP(const std::string& hrp) { @@ -196,14 +478,8 @@ std::string Encode(Encoding encoding, const std::string& hrp, const data& values /** Decode a Bech32 or Bech32m string. */ DecodeResult Decode(const std::string& str) { - bool lower = false, upper = false; - for (size_t i = 0; i < str.size(); ++i) { - unsigned char c = str[i]; - if (c >= 'a' && c <= 'z') lower = true; - else if (c >= 'A' && c <= 'Z') upper = true; - else if (c < 33 || c > 126) return {}; - } - if (lower && upper) return {}; + std::vector<int> errors; + if (!CheckCharacters(str, errors)) return {}; size_t pos = str.rfind('1'); if (str.size() > 90 || pos == str.npos || pos == 0 || pos + 7 > str.size()) { return {}; @@ -227,4 +503,163 @@ DecodeResult Decode(const std::string& str) { return {result, std::move(hrp), data(values.begin(), values.end() - 6)}; } +/** Find index of an incorrect character in a Bech32 string. */ +std::string LocateErrors(const std::string& str, std::vector<int>& error_locations) { + if (str.size() > 90) { + push_range(90, str.size(), error_locations); + return "Bech32 string too long"; + } + if (!CheckCharacters(str, error_locations)){ + return "Invalid character or mixed case"; + } + size_t pos = str.rfind('1'); + if (pos == str.npos) { + return "Missing separator"; + } + if (pos == 0 || pos + 7 > str.size()) { + error_locations.push_back(pos); + return "Invalid separator position"; + } + std::string hrp; + for (size_t i = 0; i < pos; ++i) { + hrp += LowerCase(str[i]); + } + + size_t length = str.size() - 1 - pos; // length of data part + data values(length); + for (size_t i = pos + 1; i < str.size(); ++i) { + unsigned char c = str[i]; + int8_t rev = CHARSET_REV[c]; + if (rev == -1) { + error_locations.push_back(i); + return "Invalid Base 32 character"; + } + values[i - pos - 1] = rev; + } + + // We attempt error detection with both bech32 and bech32m, and choose the one with the fewest errors + // We can't simply use the segwit version, because that may be one of the errors + for (Encoding encoding : {Encoding::BECH32, Encoding::BECH32M}) { + std::vector<int> possible_errors; + // Recall that (ExpandHRP(hrp) ++ values) is interpreted as a list of coefficients of a polynomial + // over GF(32). PolyMod computes the "remainder" of this polynomial modulo the generator G(x). + uint32_t residue = PolyMod(Cat(ExpandHRP(hrp), values)) ^ EncodingConstant(encoding); + + // All valid codewords should be multiples of G(x), so this remainder (after XORing with the encoding + // constant) should be 0 - hence 0 indicates there are no errors present. + if (residue != 0) { + // If errors are present, our polynomial must be of the form C(x) + E(x) where C is the valid + // codeword (a multiple of G(x)), and E encodes the errors. + uint32_t syn = Syndrome(residue); + + // Unpack the three 10-bit syndrome values + int s0 = syn & 0x3FF; + int s1 = (syn >> 10) & 0x3FF; + int s2 = syn >> 20; + + // Get the discrete logs of these values in GF1024 for more efficient computation + int l_s0 = GF1024_LOG[s0]; + int l_s1 = GF1024_LOG[s1]; + int l_s2 = GF1024_LOG[s2]; + + // First, suppose there is only a single error. Then E(x) = e1*x^p1 for some position p1 + // Then s0 = E((e)^997) = e1*(e)^(997*p1) and s1 = E((e)^998) = e1*(e)^(998*p1) + // Therefore s1/s0 = (e)^p1, and by the same logic, s2/s1 = (e)^p1 too. + // Hence, s1^2 == s0*s2, which is exactly the condition we check first: + if (l_s0 != -1 && l_s1 != -1 && l_s2 != -1 && (2 * l_s1 - l_s2 - l_s0 + 2046) % 1023 == 0) { + // Compute the error position p1 as l_s1 - l_s0 = p1 (mod 1023) + size_t p1 = (l_s1 - l_s0 + 1023) % 1023; // the +1023 ensures it is positive + // Now because s0 = e1*(e)^(997*p1), we get e1 = s0/((e)^(997*p1)). Remember that (e)^1023 = 1, + // so 1/((e)^997) = (e)^(1023-997). + int l_e1 = l_s0 + (1023 - 997) * p1; + // Finally, some sanity checks on the result: + // - The error position should be within the length of the data + // - e1 should be in GF(32), which implies that e1 = (e)^(33k) for some k (the 31 non-zero elements + // of GF(32) form an index 33 subgroup of the 1023 non-zero elements of GF(1024)). + if (p1 < length && !(l_e1 % 33)) { + // Polynomials run from highest power to lowest, so the index p1 is from the right. + // We don't return e1 because it is dangerous to suggest corrections to the user, + // the user should check the address themselves. + possible_errors.push_back(str.size() - p1 - 1); + } + // Otherwise, suppose there are two errors. Then E(x) = e1*x^p1 + e2*x^p2. + } else { + // For all possible first error positions p1 + for (size_t p1 = 0; p1 < length; ++p1) { + // We have guessed p1, and want to solve for p2. Recall that E(x) = e1*x^p1 + e2*x^p2, so + // s0 = E((e)^997) = e1*(e)^(997^p1) + e2*(e)^(997*p2), and similar for s1 and s2. + // + // Consider s2 + s1*(e)^p1 + // = 2e1*(e)^(999^p1) + e2*(e)^(999*p2) + e2*(e)^(998*p2)*(e)^p1 + // = e2*(e)^(999*p2) + e2*(e)^(998*p2)*(e)^p1 + // (Because we are working in characteristic 2.) + // = e2*(e)^(998*p2) ((e)^p2 + (e)^p1) + // + int s2_s1p1 = s2 ^ (s1 == 0 ? 0 : GF1024_EXP[(l_s1 + p1) % 1023]); + if (s2_s1p1 == 0) continue; + int l_s2_s1p1 = GF1024_LOG[s2_s1p1]; + + // Similarly, s1 + s0*(e)^p1 + // = e2*(e)^(997*p2) ((e)^p2 + (e)^p1) + int s1_s0p1 = s1 ^ (s0 == 0 ? 0 : GF1024_EXP[(l_s0 + p1) % 1023]); + if (s1_s0p1 == 0) continue; + int l_s1_s0p1 = GF1024_LOG[s1_s0p1]; + + // So, putting these together, we can compute the second error position as + // (e)^p2 = (s2 + s1^p1)/(s1 + s0^p1) + // p2 = log((e)^p2) + size_t p2 = (l_s2_s1p1 - l_s1_s0p1 + 1023) % 1023; + + // Sanity checks that p2 is a valid position and not the same as p1 + if (p2 >= length || p1 == p2) continue; + + // Now we want to compute the error values e1 and e2. + // Similar to above, we compute s1 + s0*(e)^p2 + // = e1*(e)^(997*p1) ((e)^p1 + (e)^p2) + int s1_s0p2 = s1 ^ (s0 == 0 ? 0 : GF1024_EXP[(l_s0 + p2) % 1023]); + if (s1_s0p2 == 0) continue; + int l_s1_s0p2 = GF1024_LOG[s1_s0p2]; + + // And compute (the log of) 1/((e)^p1 + (e)^p2)) + int inv_p1_p2 = 1023 - GF1024_LOG[GF1024_EXP[p1] ^ GF1024_EXP[p2]]; + + // Then (s1 + s0*(e)^p1) * (1/((e)^p1 + (e)^p2))) + // = e2*(e)^(997*p2) + // Then recover e2 by dividing by (e)^(997*p2) + int l_e2 = l_s1_s0p1 + inv_p1_p2 + (1023 - 997) * p2; + // Check that e2 is in GF(32) + if (l_e2 % 33) continue; + + // In the same way, (s1 + s0*(e)^p2) * (1/((e)^p1 + (e)^p2))) + // = e1*(e)^(997*p1) + // So recover e1 by dividing by (e)^(997*p1) + int l_e1 = l_s1_s0p2 + inv_p1_p2 + (1023 - 997) * p1; + // Check that e1 is in GF(32) + if (l_e1 % 33) continue; + + // Again, we do not return e1 or e2 for safety. + // Order the error positions from the left of the string and return them + if (p1 > p2) { + possible_errors.push_back(str.size() - p1 - 1); + possible_errors.push_back(str.size() - p2 - 1); + } else { + possible_errors.push_back(str.size() - p2 - 1); + possible_errors.push_back(str.size() - p1 - 1); + } + break; + } + } + } else { + // No errors + error_locations.clear(); + return ""; + } + + if (error_locations.empty() || (!possible_errors.empty() && possible_errors.size() < error_locations.size())) { + error_locations = std::move(possible_errors); + } + } + return "Invalid checksum"; +} + } // namespace bech32 diff --git a/src/bech32.h b/src/bech32.h index e9450ccc2b..7e92d5d23a 100644 --- a/src/bech32.h +++ b/src/bech32.h @@ -1,4 +1,5 @@ // Copyright (c) 2017, 2021 Pieter Wuille +// Copyright (c) 2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -44,6 +45,9 @@ struct DecodeResult /** Decode a Bech32 or Bech32m string. */ DecodeResult Decode(const std::string& str); +/** Return the positions of errors in a Bech32 string. */ +std::string LocateErrors(const std::string& str, std::vector<int>& error_locations); + } // namespace bech32 #endif // BITCOIN_BECH32_H diff --git a/src/bench/ccoins_caching.cpp b/src/bench/ccoins_caching.cpp index d5275b0b76..dae3a47cd7 100644 --- a/src/bench/ccoins_caching.cpp +++ b/src/bench/ccoins_caching.cpp @@ -45,7 +45,7 @@ static void CCoinsCaching(benchmark::Bench& bench) // Benchmark. const CTransaction tx_1(t1); bench.run([&] { - bool success = AreInputsStandard(tx_1, coins, false); + bool success{AreInputsStandard(tx_1, coins)}; assert(success); }); ECC_Stop(); diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index f6a8c56743..3c24fee60f 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -18,7 +18,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<st tx.nLockTime = nextLockTime++; // so all transactions get different hashes tx.vout.resize(1); tx.vout[0].nValue = nValue; - wtxs.push_back(std::make_unique<CWalletTx>(MakeTransactionRef(std::move(tx)))); + wtxs.push_back(std::make_unique<CWalletTx>(MakeTransactionRef(std::move(tx)), TxStateInactive{})); } // Simple benchmark for wallet coin selection. Note that it maybe be necessary diff --git a/src/bench/rpc_mempool.cpp b/src/bench/rpc_mempool.cpp index f1eeef8885..67c827d0d3 100644 --- a/src/bench/rpc_mempool.cpp +++ b/src/bench/rpc_mempool.cpp @@ -12,7 +12,7 @@ static void AddTx(const CTransactionRef& tx, const CAmount& fee, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs) { LockPoints lp; - pool.addUnchecked(CTxMemPoolEntry(tx, fee, /* time */ 0, /* height */ 1, /* spendsCoinbase */ false, /* sigOpCost */ 4, lp)); + pool.addUnchecked(CTxMemPoolEntry(tx, fee, /*time=*/0, /*entry_height=*/1, /*spends_coinbase=*/false, /*sigops_cost=*/4, lp)); } static void RpcMempool(benchmark::Bench& bench) diff --git a/src/consensus/amount.h b/src/consensus/amount.h index 96566ea13f..59b8e3417a 100644 --- a/src/consensus/amount.h +++ b/src/consensus/amount.h @@ -26,4 +26,4 @@ static constexpr CAmount COIN = 100000000; static constexpr CAmount MAX_MONEY = 21000000 * COIN; inline bool MoneyRange(const CAmount& nValue) { return (nValue >= 0 && nValue <= MAX_MONEY); } -#endif // BITCOIN_CONSENSUS_AMOUNT_H +#endif // BITCOIN_CONSENSUS_AMOUNT_H diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp index 2fdc54464a..dbae2c45f2 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -136,6 +136,10 @@ CDBWrapper::CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory, bo TryCreateDirectories(path); LogPrintf("Opening LevelDB in %s\n", fs::PathToString(path)); } + // PathToString() return value is safe to pass to leveldb open function, + // because on POSIX leveldb passes the byte string directly to ::open(), and + // on Windows it converts from UTF-8 to UTF-16 before calling ::CreateFileW + // (see env_posix.cc and env_windows.cc). leveldb::Status status = leveldb::DB::Open(options, fs::PathToString(path), &pdb); dbwrapper_private::HandleError(status); LogPrintf("Opened LevelDB successfully\n"); @@ -94,31 +94,34 @@ static inline path operator+(path p1, path p2) /** * Convert path object to byte string. On POSIX, paths natively are byte - * strings so this is trivial. On Windows, paths natively are Unicode, so an - * encoding step is necessary. + * strings, so this is trivial. On Windows, paths natively are Unicode, so an + * encoding step is necessary. The inverse of \ref PathToString is \ref + * PathFromString. The strings returned and parsed by these functions can be + * used to call POSIX APIs, and for roundtrip conversion, logging, and + * debugging. * - * The inverse of \ref PathToString is \ref PathFromString. The strings - * returned and parsed by these functions can be used to call POSIX APIs, and - * for roundtrip conversion, logging, and debugging. But they are not - * guaranteed to be valid UTF-8, and are generally meant to be used internally, - * not externally. When communicating with external programs and libraries that - * require UTF-8, fs::path::u8string() and fs::u8path() methods can be used. - * For other applications, if support for non UTF-8 paths is required, or if - * higher-level JSON or XML or URI or C-style escapes are preferred, it may be - * also be appropriate to use different path encoding functions. - * - * Implementation note: On Windows, the std::filesystem::path(string) - * constructor and std::filesystem::path::string() method are not safe to use - * here, because these methods encode the path using C++'s narrow multibyte - * encoding, which on Windows corresponds to the current "code page", which is - * unpredictable and typically not able to represent all valid paths. So - * std::filesystem::path::u8string() and std::filesystem::u8path() functions - * are used instead on Windows. On POSIX, u8string/u8path functions are not - * safe to use because paths are not always valid UTF-8, so plain string - * methods which do not transform the path there are used. + * Because \ref PathToString and \ref PathFromString functions don't specify an + * encoding, they are meant to be used internally, not externally. They are not + * appropriate to use in applications requiring UTF-8, where + * fs::path::u8string() and fs::u8path() methods should be used instead. Other + * applications could require still different encodings. For example, JSON, XML, + * or URI applications might prefer to use higher level escapes (\uXXXX or + * &XXXX; or %XX) instead of multibyte encoding. Rust, Python, Java applications + * may require encoding paths with their respective UTF-8 derivatives WTF-8, + * PEP-383, and CESU-8 (see https://en.wikipedia.org/wiki/UTF-8#Derivatives). */ static inline std::string PathToString(const path& path) { + // Implementation note: On Windows, the std::filesystem::path(string) + // constructor and std::filesystem::path::string() method are not safe to + // use here, because these methods encode the path using C++'s narrow + // multibyte encoding, which on Windows corresponds to the current "code + // page", which is unpredictable and typically not able to represent all + // valid paths. So std::filesystem::path::u8string() and + // std::filesystem::u8path() functions are used instead on Windows. On + // POSIX, u8string/u8path functions are not safe to use because paths are + // not always valid UTF-8, so plain string methods which do not transform + // the path there are used. #ifdef WIN32 return path.u8string(); #else diff --git a/src/httprpc.h b/src/httprpc.h index 5a3b990646..6daf7d28f5 100644 --- a/src/httprpc.h +++ b/src/httprpc.h @@ -31,4 +31,4 @@ void InterruptREST(); */ void StopREST(); -#endif +#endif // BITCOIN_HTTPRPC_H @@ -267,4 +267,4 @@ private: } // namespace sam } // namespace i2p -#endif /* BITCOIN_I2P_H */ +#endif // BITCOIN_I2P_H diff --git a/src/init.cpp b/src/init.cpp index 8465f85a5d..d5c3acbaad 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -59,6 +59,7 @@ #include <util/asmap.h> #include <util/check.h> #include <util/moneystr.h> +#include <util/strencodings.h> #include <util/string.h> #include <util/syscall_sandbox.h> #include <util/system.h> @@ -436,7 +437,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-maxreceivebuffer=<n>", strprintf("Maximum per-connection receive buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxsendbuffer=<n>", strprintf("Maximum per-connection send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target (in MiB per 24h). Limit does not apply to peers with 'download' permission. 0 = no limit (default: %d)", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target per 24h. Limit does not apply to peers with 'download' permission or blocks created within past week. 0 = no limit (default: %s). Optional suffix units [k|K|m|M|g|G|t|T] (default: M). Lowercase is 1000 base while uppercase is 1024 base", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2psam=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2pacceptincoming", "If set and -i2psam is also set then incoming I2P connections are accepted via the SAM proxy. If this is not set but -i2psam is set then only outgoing connections will be made to the I2P network. Ignored if -i2psam is not set. Listening for incoming I2P connections is done through the SAM proxy, not by binding to a local address and port (default: 1)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -1109,6 +1110,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) { const ArgsManager& args = *Assert(node.args); const CChainParams& chainparams = Params(); + + auto opt_max_upload = ParseByteUnits(args.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET), ByteUnit::M); + if (!opt_max_upload) { + return InitError(strprintf(_("Unable to parse -maxuploadtarget: '%s' (possible integer overflow?)"), args.GetArg("-maxuploadtarget", ""))); + } + // ********************************************************* Step 4a: application initialization if (!CreatePidFile(args)) { // Detailed error printed inside CreatePidFile(). @@ -1760,8 +1767,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) connOptions.nSendBufferMaxSize = 1000 * args.GetIntArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER); connOptions.nReceiveFloodSize = 1000 * args.GetIntArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER); connOptions.m_added_nodes = args.GetArgs("-addnode"); - - connOptions.nMaxOutboundLimit = 1024 * 1024 * args.GetIntArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET); + connOptions.nMaxOutboundLimit = *opt_max_upload; connOptions.m_peer_connect_timeout = peer_connect_timeout; for (const std::string& bind_arg : args.GetArgs("-bind")) { diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index d4ceb517dd..38004ce95d 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -287,9 +287,6 @@ public: //! to be prepared to handle this by ignoring notifications about unknown //! removed transactions and already added new transactions. virtual void requestMempoolTransactions(Notifications& notifications) = 0; - - //! Check if Taproot has activated - virtual bool isTaprootActive() = 0; }; //! Interface to let node manage chain clients (wallets, or maybe tools for diff --git a/src/key_io.cpp b/src/key_io.cpp index 615f4c9312..6908c5ea52 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -76,12 +76,16 @@ public: std::string operator()(const CNoDestination& no) const { return {}; } }; -CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, std::string& error_str) +CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, std::string& error_str, std::vector<int>* error_locations) { std::vector<unsigned char> data; uint160 hash; error_str = ""; - if (DecodeBase58Check(str, data, 21)) { + + // Note this will be false if it is a valid Bech32 address for a different network + bool is_bech32 = (ToLower(str.substr(0, params.Bech32HRP().size())) == params.Bech32HRP()); + + if (!is_bech32 && DecodeBase58Check(str, data, 21)) { // base58-encoded Bitcoin addresses. // Public-key-hash-addresses have version 0 (or 111 testnet). // The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key. @@ -98,15 +102,27 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par return ScriptHash(hash); } - // Set potential error message. - // This message may be changed if the address can also be interpreted as a Bech32 address. - error_str = "Invalid prefix for Base58-encoded address"; + if (!std::equal(script_prefix.begin(), script_prefix.end(), data.begin()) && + !std::equal(pubkey_prefix.begin(), pubkey_prefix.end(), data.begin())) { + error_str = "Invalid prefix for Base58-encoded address"; + } else { + error_str = "Invalid length for Base58 address"; + } + return CNoDestination(); + } else if (!is_bech32) { + // Try Base58 decoding without the checksum, using a much larger max length + if (!DecodeBase58(str, data, 100)) { + error_str = "Invalid HRP or Base58 character in address"; + } else { + error_str = "Invalid checksum or length of Base58 address"; + } + return CNoDestination(); } + data.clear(); const auto dec = bech32::Decode(str); if ((dec.encoding == bech32::Encoding::BECH32 || dec.encoding == bech32::Encoding::BECH32M) && dec.data.size() > 0) { // Bech32 decoding - error_str = ""; if (dec.hrp != params.Bech32HRP()) { error_str = "Invalid prefix for Bech32 address"; return CNoDestination(); @@ -168,8 +184,13 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par } } - // Set error message if address can't be interpreted as Base58 or Bech32. - if (error_str.empty()) error_str = "Invalid address format"; + // Perform Bech32 error location + if (!error_locations) { + std::vector<int> dummy_errors; + error_str = bech32::LocateErrors(str, dummy_errors); + } else { + error_str = bech32::LocateErrors(str, *error_locations); + } return CNoDestination(); } @@ -258,9 +279,9 @@ std::string EncodeDestination(const CTxDestination& dest) return std::visit(DestinationEncoder(Params()), dest); } -CTxDestination DecodeDestination(const std::string& str, std::string& error_msg) +CTxDestination DecodeDestination(const std::string& str, std::string& error_msg, std::vector<int>* error_locations) { - return DecodeDestination(str, Params(), error_msg); + return DecodeDestination(str, Params(), error_msg, error_locations); } CTxDestination DecodeDestination(const std::string& str) @@ -272,7 +293,7 @@ CTxDestination DecodeDestination(const std::string& str) bool IsValidDestinationString(const std::string& str, const CChainParams& params) { std::string error_msg; - return IsValidDestination(DecodeDestination(str, params, error_msg)); + return IsValidDestination(DecodeDestination(str, params, error_msg, nullptr)); } bool IsValidDestinationString(const std::string& str) diff --git a/src/key_io.h b/src/key_io.h index bd81f7847e..2062bb4c44 100644 --- a/src/key_io.h +++ b/src/key_io.h @@ -23,7 +23,7 @@ std::string EncodeExtPubKey(const CExtPubKey& extpubkey); std::string EncodeDestination(const CTxDestination& dest); CTxDestination DecodeDestination(const std::string& str); -CTxDestination DecodeDestination(const std::string& str, std::string& error_msg); +CTxDestination DecodeDestination(const std::string& str, std::string& error_msg, std::vector<int>* error_locations = nullptr); bool IsValidDestinationString(const std::string& str); bool IsValidDestinationString(const std::string& str, const CChainParams& params); diff --git a/src/net.cpp b/src/net.cpp index 82e55d4189..db496c2185 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -326,8 +326,8 @@ bool IsLocal(const CService& addr) CNode* CConnman::FindNode(const CNetAddr& ip) { - LOCK(cs_vNodes); - for (CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (CNode* pnode : m_nodes) { if (static_cast<CNetAddr>(pnode->addr) == ip) { return pnode; } @@ -337,8 +337,8 @@ CNode* CConnman::FindNode(const CNetAddr& ip) CNode* CConnman::FindNode(const CSubNet& subNet) { - LOCK(cs_vNodes); - for (CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (CNode* pnode : m_nodes) { if (subNet.Match(static_cast<CNetAddr>(pnode->addr))) { return pnode; } @@ -348,8 +348,8 @@ CNode* CConnman::FindNode(const CSubNet& subNet) CNode* CConnman::FindNode(const std::string& addrName) { - LOCK(cs_vNodes); - for (CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (CNode* pnode : m_nodes) { if (pnode->m_addr_name == addrName) { return pnode; } @@ -359,8 +359,8 @@ CNode* CConnman::FindNode(const std::string& addrName) CNode* CConnman::FindNode(const CService& addr) { - LOCK(cs_vNodes); - for (CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (CNode* pnode : m_nodes) { if (static_cast<CService>(pnode->addr) == addr) { return pnode; } @@ -375,8 +375,8 @@ bool CConnman::AlreadyConnectedToAddress(const CAddress& addr) bool CConnman::CheckIncomingNonce(uint64_t nonce) { - LOCK(cs_vNodes); - for (const CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (const CNode* pnode : m_nodes) { if (!pnode->fSuccessfullyConnected && !pnode->IsInboundConn() && pnode->GetLocalNonce() == nonce) return false; } @@ -435,7 +435,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo } // It is possible that we already have a connection to the IP/port pszDest resolved to. // In that case, drop the connection that was just created. - LOCK(cs_vNodes); + LOCK(m_nodes_mutex); CNode* pnode = FindNode(static_cast<CService>(addrConnect)); if (pnode) { LogPrintf("Failed to open new connection, already connected\n"); @@ -1056,8 +1056,8 @@ bool CConnman::AttemptToEvictConnection() std::vector<NodeEvictionCandidate> vEvictionCandidates; { - LOCK(cs_vNodes); - for (const CNode* node : vNodes) { + LOCK(m_nodes_mutex); + for (const CNode* node : m_nodes) { if (node->HasPermission(NetPermissionFlags::NoBan)) continue; if (!node->IsInboundConn()) @@ -1084,8 +1084,8 @@ bool CConnman::AttemptToEvictConnection() if (!node_id_to_evict) { return false; } - LOCK(cs_vNodes); - for (CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (CNode* pnode : m_nodes) { if (pnode->GetId() == *node_id_to_evict) { LogPrint(BCLog::NET, "selected %s connection for eviction peer=%d; disconnecting\n", pnode->ConnectionTypeAsString(), pnode->GetId()); pnode->fDisconnect = true; @@ -1141,8 +1141,8 @@ void CConnman::CreateNodeFromAcceptedSocket(SOCKET hSocket, } { - LOCK(cs_vNodes); - for (const CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (const CNode* pnode : m_nodes) { if (pnode->IsInboundConn()) nInbound++; } } @@ -1210,8 +1210,8 @@ void CConnman::CreateNodeFromAcceptedSocket(SOCKET hSocket, LogPrint(BCLog::NET, "connection from %s accepted\n", addr.ToString()); { - LOCK(cs_vNodes); - vNodes.push_back(pnode); + LOCK(m_nodes_mutex); + m_nodes.push_back(pnode); } // We received a new connection, harvest entropy from the time (and our peer count) @@ -1238,8 +1238,8 @@ bool CConnman::AddConnection(const std::string& address, ConnectionType conn_typ } // no default case, so the compiler can warn about missing cases // Count existing connections - int existing_connections = WITH_LOCK(cs_vNodes, - return std::count_if(vNodes.begin(), vNodes.end(), [conn_type](CNode* node) { return node->m_conn_type == conn_type; });); + int existing_connections = WITH_LOCK(m_nodes_mutex, + return std::count_if(m_nodes.begin(), m_nodes.end(), [conn_type](CNode* node) { return node->m_conn_type == conn_type; });); // Max connections of specified type already exist if (max_connections != std::nullopt && existing_connections >= max_connections) return false; @@ -1255,11 +1255,11 @@ bool CConnman::AddConnection(const std::string& address, ConnectionType conn_typ void CConnman::DisconnectNodes() { { - LOCK(cs_vNodes); + LOCK(m_nodes_mutex); if (!fNetworkActive) { // Disconnect any connected nodes - for (CNode* pnode : vNodes) { + for (CNode* pnode : m_nodes) { if (!pnode->fDisconnect) { LogPrint(BCLog::NET, "Network not active, dropping peer=%d\n", pnode->GetId()); pnode->fDisconnect = true; @@ -1268,13 +1268,13 @@ void CConnman::DisconnectNodes() } // Disconnect unused nodes - std::vector<CNode*> vNodesCopy = vNodes; - for (CNode* pnode : vNodesCopy) + std::vector<CNode*> nodes_copy = m_nodes; + for (CNode* pnode : nodes_copy) { if (pnode->fDisconnect) { - // remove from vNodes - vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end()); + // remove from m_nodes + m_nodes.erase(remove(m_nodes.begin(), m_nodes.end(), pnode), m_nodes.end()); // release outbound grant (if any) pnode->grantOutbound.Release(); @@ -1284,18 +1284,18 @@ void CConnman::DisconnectNodes() // hold in disconnected pool until all refs are released pnode->Release(); - vNodesDisconnected.push_back(pnode); + m_nodes_disconnected.push_back(pnode); } } } { // Delete disconnected nodes - std::list<CNode*> vNodesDisconnectedCopy = vNodesDisconnected; - for (CNode* pnode : vNodesDisconnectedCopy) + std::list<CNode*> nodes_disconnected_copy = m_nodes_disconnected; + for (CNode* pnode : nodes_disconnected_copy) { // Destroy the object only after other threads have stopped using it. if (pnode->GetRefCount() <= 0) { - vNodesDisconnected.remove(pnode); + m_nodes_disconnected.remove(pnode); DeleteNode(pnode); } } @@ -1304,15 +1304,15 @@ void CConnman::DisconnectNodes() void CConnman::NotifyNumConnectionsChanged() { - size_t vNodesSize; + size_t nodes_size; { - LOCK(cs_vNodes); - vNodesSize = vNodes.size(); + LOCK(m_nodes_mutex); + nodes_size = m_nodes.size(); } - if(vNodesSize != nPrevNodeCount) { - nPrevNodeCount = vNodesSize; + if(nodes_size != nPrevNodeCount) { + nPrevNodeCount = nodes_size; if (m_client_interface) { - m_client_interface->NotifyNumConnectionsChanged(vNodesSize); + m_client_interface->NotifyNumConnectionsChanged(nodes_size); } } } @@ -1353,46 +1353,45 @@ bool CConnman::InactivityCheck(const CNode& node) const return false; } -bool CConnman::GenerateSelectSet(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set) +bool CConnman::GenerateSelectSet(const std::vector<CNode*>& nodes, + std::set<SOCKET>& recv_set, + std::set<SOCKET>& send_set, + std::set<SOCKET>& error_set) { for (const ListenSocket& hListenSocket : vhListenSocket) { recv_set.insert(hListenSocket.socket); } - { - LOCK(cs_vNodes); - for (CNode* pnode : vNodes) + for (CNode* pnode : nodes) { + // Implement the following logic: + // * If there is data to send, select() for sending data. As this only + // happens when optimistic write failed, we choose to first drain the + // write buffer in this case before receiving more. This avoids + // needlessly queueing received data, if the remote peer is not themselves + // receiving data. This means properly utilizing TCP flow control signalling. + // * Otherwise, if there is space left in the receive buffer, select() for + // receiving data. + // * Hand off all complete messages to the processor, to be handled without + // blocking here. + + bool select_recv = !pnode->fPauseRecv; + bool select_send; { - // Implement the following logic: - // * If there is data to send, select() for sending data. As this only - // happens when optimistic write failed, we choose to first drain the - // write buffer in this case before receiving more. This avoids - // needlessly queueing received data, if the remote peer is not themselves - // receiving data. This means properly utilizing TCP flow control signalling. - // * Otherwise, if there is space left in the receive buffer, select() for - // receiving data. - // * Hand off all complete messages to the processor, to be handled without - // blocking here. - - bool select_recv = !pnode->fPauseRecv; - bool select_send; - { - LOCK(pnode->cs_vSend); - select_send = !pnode->vSendMsg.empty(); - } + LOCK(pnode->cs_vSend); + select_send = !pnode->vSendMsg.empty(); + } - LOCK(pnode->cs_hSocket); - if (pnode->hSocket == INVALID_SOCKET) - continue; + LOCK(pnode->cs_hSocket); + if (pnode->hSocket == INVALID_SOCKET) + continue; - error_set.insert(pnode->hSocket); - if (select_send) { - send_set.insert(pnode->hSocket); - continue; - } - if (select_recv) { - recv_set.insert(pnode->hSocket); - } + error_set.insert(pnode->hSocket); + if (select_send) { + send_set.insert(pnode->hSocket); + continue; + } + if (select_recv) { + recv_set.insert(pnode->hSocket); } } @@ -1400,10 +1399,13 @@ bool CConnman::GenerateSelectSet(std::set<SOCKET> &recv_set, std::set<SOCKET> &s } #ifdef USE_POLL -void CConnman::SocketEvents(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set) +void CConnman::SocketEvents(const std::vector<CNode*>& nodes, + std::set<SOCKET>& recv_set, + std::set<SOCKET>& send_set, + std::set<SOCKET>& error_set) { std::set<SOCKET> recv_select_set, send_select_set, error_select_set; - if (!GenerateSelectSet(recv_select_set, send_select_set, error_select_set)) { + if (!GenerateSelectSet(nodes, recv_select_set, send_select_set, error_select_set)) { interruptNet.sleep_for(std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS)); return; } @@ -1442,10 +1444,13 @@ void CConnman::SocketEvents(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_s } } #else -void CConnman::SocketEvents(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set) +void CConnman::SocketEvents(const std::vector<CNode*>& nodes, + std::set<SOCKET>& recv_set, + std::set<SOCKET>& send_set, + std::set<SOCKET>& error_set) { std::set<SOCKET> recv_select_set, send_select_set, error_select_set; - if (!GenerateSelectSet(recv_select_set, send_select_set, error_select_set)) { + if (!GenerateSelectSet(nodes, recv_select_set, send_select_set, error_select_set)) { interruptNet.sleep_for(std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS)); return; } @@ -1519,34 +1524,33 @@ void CConnman::SocketEvents(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_s void CConnman::SocketHandler() { - std::set<SOCKET> recv_set, send_set, error_set; - SocketEvents(recv_set, send_set, error_set); - - if (interruptNet) return; + std::set<SOCKET> recv_set; + std::set<SOCKET> send_set; + std::set<SOCKET> error_set; - // - // Accept new connections - // - for (const ListenSocket& hListenSocket : vhListenSocket) { - if (hListenSocket.socket != INVALID_SOCKET && recv_set.count(hListenSocket.socket) > 0) - { - AcceptConnection(hListenSocket); - } - } + const NodesSnapshot snap{*this, /*shuffle=*/false}; - // - // Service each socket - // - std::vector<CNode*> vNodesCopy; - { - LOCK(cs_vNodes); - vNodesCopy = vNodes; - for (CNode* pnode : vNodesCopy) - pnode->AddRef(); + // Check for the readiness of the already connected sockets and the + // listening sockets in one call ("readiness" as in poll(2) or + // select(2)). If none are ready, wait for a short while and return + // empty sets. + SocketEvents(snap.Nodes(), recv_set, send_set, error_set); + + // Service (send/receive) each of the already connected nodes. + SocketHandlerConnected(snap.Nodes(), recv_set, send_set, error_set); } - for (CNode* pnode : vNodesCopy) - { + + // Accept new connections from listening sockets. + SocketHandlerListening(recv_set); +} + +void CConnman::SocketHandlerConnected(const std::vector<CNode*>& nodes, + const std::set<SOCKET>& recv_set, + const std::set<SOCKET>& send_set, + const std::set<SOCKET>& error_set) +{ + for (CNode* pnode : nodes) { if (interruptNet) return; @@ -1628,10 +1632,17 @@ void CConnman::SocketHandler() if (InactivityCheck(*pnode)) pnode->fDisconnect = true; } - { - LOCK(cs_vNodes); - for (CNode* pnode : vNodesCopy) - pnode->Release(); +} + +void CConnman::SocketHandlerListening(const std::set<SOCKET>& recv_set) +{ + for (const ListenSocket& listen_socket : vhListenSocket) { + if (interruptNet) { + return; + } + if (listen_socket.socket != INVALID_SOCKET && recv_set.count(listen_socket.socket) > 0) { + AcceptConnection(listen_socket); + } } } @@ -1705,8 +1716,8 @@ void CConnman::ThreadDNSAddressSeed() int nRelevant = 0; { - LOCK(cs_vNodes); - for (const CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (const CNode* pnode : m_nodes) { if (pnode->fSuccessfullyConnected && pnode->IsFullOutboundConn()) ++nRelevant; } } @@ -1814,8 +1825,8 @@ int CConnman::GetExtraFullOutboundCount() const { int full_outbound_peers = 0; { - LOCK(cs_vNodes); - for (const CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (const CNode* pnode : m_nodes) { if (pnode->fSuccessfullyConnected && !pnode->fDisconnect && pnode->IsFullOutboundConn()) { ++full_outbound_peers; } @@ -1828,8 +1839,8 @@ int CConnman::GetExtraBlockRelayCount() const { int block_relay_peers = 0; { - LOCK(cs_vNodes); - for (const CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (const CNode* pnode : m_nodes) { if (pnode->fSuccessfullyConnected && !pnode->fDisconnect && pnode->IsBlockOnlyConn()) { ++block_relay_peers; } @@ -1900,8 +1911,8 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // Checking !dnsseed is cheaper before locking 2 mutexes. if (!add_fixed_seeds_now && !dnsseed) { - LOCK2(m_addr_fetches_mutex, cs_vAddedNodes); - if (m_addr_fetches.empty() && vAddedNodes.empty()) { + LOCK2(m_addr_fetches_mutex, m_added_nodes_mutex); + if (m_addr_fetches.empty() && m_added_nodes.empty()) { add_fixed_seeds_now = true; LogPrintf("Adding fixed seeds as -dnsseed=0, -addnode is not provided and all -seednode(s) attempted\n"); } @@ -1926,8 +1937,8 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) std::set<std::vector<unsigned char> > setConnected; { - LOCK(cs_vNodes); - for (const CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (const CNode* pnode : m_nodes) { if (pnode->IsFullOutboundConn()) nOutboundFullRelay++; if (pnode->IsBlockOnlyConn()) nOutboundBlockRelay++; @@ -2115,8 +2126,8 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) std::vector<CAddress> CConnman::GetCurrentBlockRelayOnlyConns() const { std::vector<CAddress> ret; - LOCK(cs_vNodes); - for (const CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (const CNode* pnode : m_nodes) { if (pnode->IsBlockOnlyConn()) { ret.push_back(pnode->addr); } @@ -2131,9 +2142,9 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const std::list<std::string> lAddresses(0); { - LOCK(cs_vAddedNodes); - ret.reserve(vAddedNodes.size()); - std::copy(vAddedNodes.cbegin(), vAddedNodes.cend(), std::back_inserter(lAddresses)); + LOCK(m_added_nodes_mutex); + ret.reserve(m_added_nodes.size()); + std::copy(m_added_nodes.cbegin(), m_added_nodes.cend(), std::back_inserter(lAddresses)); } @@ -2141,8 +2152,8 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const std::map<CService, bool> mapConnected; std::map<std::string, std::pair<bool, CService>> mapConnectedByName; { - LOCK(cs_vNodes); - for (const CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (const CNode* pnode : m_nodes) { if (pnode->addr.IsValid()) { mapConnected[pnode->addr] = pnode->IsInboundConn(); } @@ -2238,57 +2249,42 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai m_msgproc->InitializeNode(pnode); { - LOCK(cs_vNodes); - vNodes.push_back(pnode); + LOCK(m_nodes_mutex); + m_nodes.push_back(pnode); } } void CConnman::ThreadMessageHandler() { SetSyscallSandboxPolicy(SyscallSandboxPolicy::MESSAGE_HANDLER); - FastRandomContext rng; while (!flagInterruptMsgProc) { - std::vector<CNode*> vNodesCopy; - { - LOCK(cs_vNodes); - vNodesCopy = vNodes; - for (CNode* pnode : vNodesCopy) { - pnode->AddRef(); - } - } - bool fMoreWork = false; - // Randomize the order in which we process messages from/to our peers. - // This prevents attacks in which an attacker exploits having multiple - // consecutive connections in the vNodes list. - Shuffle(vNodesCopy.begin(), vNodesCopy.end(), rng); - - for (CNode* pnode : vNodesCopy) { - if (pnode->fDisconnect) - continue; + // Randomize the order in which we process messages from/to our peers. + // This prevents attacks in which an attacker exploits having multiple + // consecutive connections in the m_nodes list. + const NodesSnapshot snap{*this, /*shuffle=*/true}; - // Receive messages - bool fMoreNodeWork = m_msgproc->ProcessMessages(pnode, flagInterruptMsgProc); - fMoreWork |= (fMoreNodeWork && !pnode->fPauseSend); - if (flagInterruptMsgProc) - return; - // Send messages - { - LOCK(pnode->cs_sendProcessing); - m_msgproc->SendMessages(pnode); - } + for (CNode* pnode : snap.Nodes()) { + if (pnode->fDisconnect) + continue; - if (flagInterruptMsgProc) - return; - } + // Receive messages + bool fMoreNodeWork = m_msgproc->ProcessMessages(pnode, flagInterruptMsgProc); + fMoreWork |= (fMoreNodeWork && !pnode->fPauseSend); + if (flagInterruptMsgProc) + return; + // Send messages + { + LOCK(pnode->cs_sendProcessing); + m_msgproc->SendMessages(pnode); + } - { - LOCK(cs_vNodes); - for (CNode* pnode : vNodesCopy) - pnode->Release(); + if (flagInterruptMsgProc) + return; + } } WAIT_LOCK(mutexMsgProc, lock); @@ -2698,7 +2694,7 @@ void CConnman::StopNodes() // Delete peer connections. std::vector<CNode*> nodes; - WITH_LOCK(cs_vNodes, nodes.swap(vNodes)); + WITH_LOCK(m_nodes_mutex, nodes.swap(m_nodes)); for (CNode* pnode : nodes) { pnode->CloseSocketDisconnect(); DeleteNode(pnode); @@ -2713,10 +2709,10 @@ void CConnman::StopNodes() } } - for (CNode* pnode : vNodesDisconnected) { + for (CNode* pnode : m_nodes_disconnected) { DeleteNode(pnode); } - vNodesDisconnected.clear(); + m_nodes_disconnected.clear(); vhListenSocket.clear(); semOutbound.reset(); semAddnode.reset(); @@ -2789,21 +2785,21 @@ std::vector<CAddress> CConnman::GetAddresses(CNode& requestor, size_t max_addres bool CConnman::AddNode(const std::string& strNode) { - LOCK(cs_vAddedNodes); - for (const std::string& it : vAddedNodes) { + LOCK(m_added_nodes_mutex); + for (const std::string& it : m_added_nodes) { if (strNode == it) return false; } - vAddedNodes.push_back(strNode); + m_added_nodes.push_back(strNode); return true; } bool CConnman::RemoveAddedNode(const std::string& strNode) { - LOCK(cs_vAddedNodes); - for(std::vector<std::string>::iterator it = vAddedNodes.begin(); it != vAddedNodes.end(); ++it) { + LOCK(m_added_nodes_mutex); + for(std::vector<std::string>::iterator it = m_added_nodes.begin(); it != m_added_nodes.end(); ++it) { if (strNode == *it) { - vAddedNodes.erase(it); + m_added_nodes.erase(it); return true; } } @@ -2812,12 +2808,12 @@ bool CConnman::RemoveAddedNode(const std::string& strNode) size_t CConnman::GetNodeCount(ConnectionDirection flags) const { - LOCK(cs_vNodes); + LOCK(m_nodes_mutex); if (flags == ConnectionDirection::Both) // Shortcut if we want total - return vNodes.size(); + return m_nodes.size(); int nNum = 0; - for (const auto& pnode : vNodes) { + for (const auto& pnode : m_nodes) { if (flags & (pnode->IsInboundConn() ? ConnectionDirection::In : ConnectionDirection::Out)) { nNum++; } @@ -2829,9 +2825,9 @@ size_t CConnman::GetNodeCount(ConnectionDirection flags) const void CConnman::GetNodeStats(std::vector<CNodeStats>& vstats) const { vstats.clear(); - LOCK(cs_vNodes); - vstats.reserve(vNodes.size()); - for (CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + vstats.reserve(m_nodes.size()); + for (CNode* pnode : m_nodes) { vstats.emplace_back(); pnode->CopyStats(vstats.back()); vstats.back().m_mapped_as = pnode->addr.GetMappedAS(addrman.GetAsmap()); @@ -2840,7 +2836,7 @@ void CConnman::GetNodeStats(std::vector<CNodeStats>& vstats) const bool CConnman::DisconnectNode(const std::string& strNode) { - LOCK(cs_vNodes); + LOCK(m_nodes_mutex); if (CNode* pnode = FindNode(strNode)) { LogPrint(BCLog::NET, "disconnect by address%s matched peer=%d; disconnecting\n", (fLogIPs ? strprintf("=%s", strNode) : ""), pnode->GetId()); pnode->fDisconnect = true; @@ -2852,8 +2848,8 @@ bool CConnman::DisconnectNode(const std::string& strNode) bool CConnman::DisconnectNode(const CSubNet& subnet) { bool disconnected = false; - LOCK(cs_vNodes); - for (CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for (CNode* pnode : m_nodes) { if (subnet.Match(pnode->addr)) { LogPrint(BCLog::NET, "disconnect by subnet%s matched peer=%d; disconnecting\n", (fLogIPs ? strprintf("=%s", subnet.ToString()) : ""), pnode->GetId()); pnode->fDisconnect = true; @@ -2870,8 +2866,8 @@ bool CConnman::DisconnectNode(const CNetAddr& addr) bool CConnman::DisconnectNode(NodeId id) { - LOCK(cs_vNodes); - for(CNode* pnode : vNodes) { + LOCK(m_nodes_mutex); + for(CNode* pnode : m_nodes) { if (id == pnode->GetId()) { LogPrint(BCLog::NET, "disconnect by id peer=%d; disconnecting\n", pnode->GetId()); pnode->fDisconnect = true; @@ -2883,7 +2879,6 @@ bool CConnman::DisconnectNode(NodeId id) void CConnman::RecordBytesRecv(uint64_t bytes) { - LOCK(cs_totalBytesRecv); nTotalBytesRecv += bytes; } @@ -2960,7 +2955,6 @@ uint64_t CConnman::GetOutboundTargetBytesLeft() const uint64_t CConnman::GetTotalBytesRecv() const { - LOCK(cs_totalBytesRecv); return nTotalBytesRecv; } @@ -3024,7 +3018,7 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg) size_t nMessageSize = msg.data.size(); LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n", msg.m_type, nMessageSize, pnode->GetId()); if (gArgs.GetBoolArg("-capturemessages", false)) { - CaptureMessage(pnode->addr, msg.m_type, msg.data, /* incoming */ false); + CaptureMessage(pnode->addr, msg.m_type, msg.data, /*is_incoming=*/false); } TRACE6(net, outbound_message, @@ -3063,8 +3057,8 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg) bool CConnman::ForNode(NodeId id, std::function<bool(CNode* pnode)> func) { CNode* found = nullptr; - LOCK(cs_vNodes); - for (auto&& pnode : vNodes) { + LOCK(m_nodes_mutex); + for (auto&& pnode : m_nodes) { if(pnode->GetId() == id) { found = pnode; break; @@ -70,7 +70,7 @@ static const bool DEFAULT_LISTEN = true; /** The maximum number of peer connections to maintain. */ static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS = 125; /** The default for -maxuploadtarget. 0 = Unlimited */ -static constexpr uint64_t DEFAULT_MAX_UPLOAD_TARGET = 0; +static const std::string DEFAULT_MAX_UPLOAD_TARGET{"0M"}; /** Default for blocks only*/ static const bool DEFAULT_BLOCKSONLY = false; /** -peertimeout default */ @@ -791,8 +791,8 @@ public: } vWhitelistedRange = connOptions.vWhitelistedRange; { - LOCK(cs_vAddedNodes); - vAddedNodes = connOptions.m_added_nodes; + LOCK(m_added_nodes_mutex); + m_added_nodes = connOptions.m_added_nodes; } m_onion_binds = connOptions.onion_binds; } @@ -823,8 +823,8 @@ public: using NodeFn = std::function<void(CNode*)>; void ForEachNode(const NodeFn& func) { - LOCK(cs_vNodes); - for (auto&& node : vNodes) { + LOCK(m_nodes_mutex); + for (auto&& node : m_nodes) { if (NodeFullyConnected(node)) func(node); } @@ -832,8 +832,8 @@ public: void ForEachNode(const NodeFn& func) const { - LOCK(cs_vNodes); - for (auto&& node : vNodes) { + LOCK(m_nodes_mutex); + for (auto&& node : m_nodes) { if (NodeFullyConnected(node)) func(node); } @@ -968,7 +968,7 @@ private: /** * Create a `CNode` object from a socket that has just been accepted and add the node to - * the `vNodes` member. + * the `m_nodes` member. * @param[in] hSocket Connected socket to communicate with the peer. * @param[in] permissionFlags The peer's permissions. * @param[in] addr_bind The address and port at our side of the connection. @@ -983,9 +983,57 @@ private: void NotifyNumConnectionsChanged(); /** Return true if the peer is inactive and should be disconnected. */ bool InactivityCheck(const CNode& node) const; - bool GenerateSelectSet(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set); - void SocketEvents(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set); + + /** + * Generate a collection of sockets to check for IO readiness. + * @param[in] nodes Select from these nodes' sockets. + * @param[out] recv_set Sockets to check for read readiness. + * @param[out] send_set Sockets to check for write readiness. + * @param[out] error_set Sockets to check for errors. + * @return true if at least one socket is to be checked (the returned set is not empty) + */ + bool GenerateSelectSet(const std::vector<CNode*>& nodes, + std::set<SOCKET>& recv_set, + std::set<SOCKET>& send_set, + std::set<SOCKET>& error_set); + + /** + * Check which sockets are ready for IO. + * @param[in] nodes Select from these nodes' sockets. + * @param[out] recv_set Sockets which are ready for read. + * @param[out] send_set Sockets which are ready for write. + * @param[out] error_set Sockets which have errors. + * This calls `GenerateSelectSet()` to gather a list of sockets to check. + */ + void SocketEvents(const std::vector<CNode*>& nodes, + std::set<SOCKET>& recv_set, + std::set<SOCKET>& send_set, + std::set<SOCKET>& error_set); + + /** + * Check connected and listening sockets for IO readiness and process them accordingly. + */ void SocketHandler(); + + /** + * Do the read/write for connected sockets that are ready for IO. + * @param[in] nodes Nodes to process. The socket of each node is checked against + * `recv_set`, `send_set` and `error_set`. + * @param[in] recv_set Sockets that are ready for read. + * @param[in] send_set Sockets that are ready for send. + * @param[in] error_set Sockets that have an exceptional condition (error). + */ + void SocketHandlerConnected(const std::vector<CNode*>& nodes, + const std::set<SOCKET>& recv_set, + const std::set<SOCKET>& send_set, + const std::set<SOCKET>& error_set); + + /** + * Accept incoming connections, one from each read-ready listening socket. + * @param[in] recv_set Sockets that are ready for read. + */ + void SocketHandlerListening(const std::set<SOCKET>& recv_set); + void ThreadSocketHandler(); void ThreadDNSAddressSeed(); @@ -1026,9 +1074,8 @@ private: static bool NodeFullyConnected(const CNode* pnode); // Network usage totals - mutable RecursiveMutex cs_totalBytesRecv; mutable RecursiveMutex cs_totalBytesSent; - uint64_t nTotalBytesRecv GUARDED_BY(cs_totalBytesRecv) {0}; + std::atomic<uint64_t> nTotalBytesRecv{0}; uint64_t nTotalBytesSent GUARDED_BY(cs_totalBytesSent) {0}; // outbound limit & stats @@ -1051,12 +1098,12 @@ private: bool fAddressesInitialized{false}; AddrMan& addrman; std::deque<std::string> m_addr_fetches GUARDED_BY(m_addr_fetches_mutex); - RecursiveMutex m_addr_fetches_mutex; - std::vector<std::string> vAddedNodes GUARDED_BY(cs_vAddedNodes); - mutable RecursiveMutex cs_vAddedNodes; - std::vector<CNode*> vNodes GUARDED_BY(cs_vNodes); - std::list<CNode*> vNodesDisconnected; - mutable RecursiveMutex cs_vNodes; + Mutex m_addr_fetches_mutex; + std::vector<std::string> m_added_nodes GUARDED_BY(m_added_nodes_mutex); + mutable Mutex m_added_nodes_mutex; + std::vector<CNode*> m_nodes GUARDED_BY(m_nodes_mutex); + std::list<CNode*> m_nodes_disconnected; + mutable RecursiveMutex m_nodes_mutex; std::atomic<NodeId> nLastNodeId{0}; unsigned int nPrevNodeCount{0}; @@ -1177,6 +1224,43 @@ private: */ std::vector<CService> m_onion_binds; + /** + * RAII helper to atomically create a copy of `m_nodes` and add a reference + * to each of the nodes. The nodes are released when this object is destroyed. + */ + class NodesSnapshot + { + public: + explicit NodesSnapshot(const CConnman& connman, bool shuffle) + { + { + LOCK(connman.m_nodes_mutex); + m_nodes_copy = connman.m_nodes; + for (auto& node : m_nodes_copy) { + node->AddRef(); + } + } + if (shuffle) { + Shuffle(m_nodes_copy.begin(), m_nodes_copy.end(), FastRandomContext{}); + } + } + + ~NodesSnapshot() + { + for (auto& node : m_nodes_copy) { + node->Release(); + } + } + + const std::vector<CNode*>& Nodes() const + { + return m_nodes_copy; + } + + private: + std::vector<CNode*> m_nodes_copy; + }; + friend struct CConnmanTest; friend struct ConnmanTestMsg; }; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 2185ccc700..a896bb76ae 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -4105,7 +4105,7 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt ); if (gArgs.GetBoolArg("-capturemessages", false)) { - CaptureMessage(pfrom->addr, msg.m_command, MakeUCharSpan(msg.m_recv), /* incoming */ true); + CaptureMessage(pfrom->addr, msg.m_command, MakeUCharSpan(msg.m_recv), /*is_incoming=*/true); } msg.SetVersion(pfrom->GetCommonVersion()); diff --git a/src/netaddress.h b/src/netaddress.h index b0b1c5ca9e..a5b74eb35b 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -385,6 +385,12 @@ private: /** * Unserialize from a pre-ADDRv2/BIP155 format from an array. + * + * This function is only called from UnserializeV1Stream() and is a wrapper + * for SetLegacyIPv6(); however, we keep it for symmetry with + * SerializeV1Array() to have pairs of ser/unser functions and to make clear + * that if one is altered, a corresponding reverse modification should be + * applied to the other. */ void UnserializeV1Array(uint8_t (&arr)[V1_SERIALIZATION_SIZE]) { diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 9c2d23f839..e5f2753123 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -540,8 +540,11 @@ public: const CBlockIndex* block2 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash2); const CBlockIndex* ancestor = block1 && block2 ? LastCommonAncestor(block1, block2) : nullptr; // Using & instead of && below to avoid short circuiting and leaving - // output uninitialized. - return FillBlock(ancestor, ancestor_out, lock, active) & FillBlock(block1, block1_out, lock, active) & FillBlock(block2, block2_out, lock, active); + // output uninitialized. Cast bool to int to avoid -Wbitwise-instead-of-logical + // compiler warnings. + return int{FillBlock(ancestor, ancestor_out, lock, active)} & + int{FillBlock(block1, block1_out, lock, active)} & + int{FillBlock(block2, block2_out, lock, active)}; } void findCoins(std::map<COutPoint, Coin>& coins) override { return FindCoins(m_node, coins); } double guessVerificationProgress(const uint256& block_hash) override @@ -721,12 +724,6 @@ public: notifications.transactionAddedToMempool(entry.GetSharedTx(), 0 /* mempool_sequence */); } } - bool isTaprootActive() override - { - LOCK(::cs_main); - const CBlockIndex* tip = Assert(m_node.chainman)->ActiveChain().Tip(); - return DeploymentActiveAfter(tip, Params().GetConsensus(), Consensus::DEPLOYMENT_TAPROOT); - } NodeContext& m_node; }; } // namespace diff --git a/src/policy/feerate.h b/src/policy/feerate.h index 8ba896bb01..13c7ec2002 100644 --- a/src/policy/feerate.h +++ b/src/policy/feerate.h @@ -67,4 +67,4 @@ public: SERIALIZE_METHODS(CFeeRate, obj) { READWRITE(obj.nSatoshisPerK); } }; -#endif // BITCOIN_POLICY_FEERATE_H +#endif // BITCOIN_POLICY_FEERATE_H diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index fced397e51..2c30b20d5b 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -161,13 +161,13 @@ bool IsStandardTx(const CTransaction& tx, bool permit_bare_multisig, const CFeeR * * Note that only the non-witness portion of the transaction is checked here. */ -bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, bool taproot_active) +bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) { - if (tx.IsCoinBase()) + if (tx.IsCoinBase()) { return true; // Coinbases don't use vin normally + } - for (unsigned int i = 0; i < tx.vin.size(); i++) - { + for (unsigned int i = 0; i < tx.vin.size(); i++) { const CTxOut& prev = mapInputs.AccessCoin(tx.vin[i].prevout).out; std::vector<std::vector<unsigned char> > vSolutions; @@ -189,9 +189,6 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, if (subscript.GetSigOpCount(true) > MAX_P2SH_SIGOPS) { return false; } - } else if (whichType == TxoutType::WITNESS_V1_TAPROOT) { - // Don't allow Taproot spends unless Taproot is active. - if (!taproot_active) return false; } } diff --git a/src/policy/policy.h b/src/policy/policy.h index f2a3f35546..f6ac6500f6 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -105,10 +105,9 @@ bool IsStandardTx(const CTransaction& tx, bool permit_bare_multisig, const CFeeR /** * Check for standard transaction types * @param[in] mapInputs Map of previous transactions that have outputs we're spending -* @param[in] taproot_active Whether or taproot consensus rules are active (used to decide whether spends of them are permitted) * @return True if all inputs (scriptSigs) use only standard transaction forms */ -bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, bool taproot_active); +bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs); /** * Check if the transaction is over standard P2WSH resources limit: * 3600bytes witnessScript size, 80bytes per witness stack element, 100 witness stack elements diff --git a/src/psbt.cpp b/src/psbt.cpp index 5445bc8aa1..b3d8e052bc 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -223,7 +223,7 @@ void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransactio // Construct a would-be spend of this output, to update sigdata with. // Note that ProduceSignature is used to fill in metadata (not actual signatures), // so provider does not need to provide any private keys (it can be a HidingSigningProvider). - MutableTransactionSignatureCreator creator(&tx, /* index */ 0, out.nValue, SIGHASH_ALL); + MutableTransactionSignatureCreator creator(&tx, /*input_idx=*/0, out.nValue, SIGHASH_ALL); ProduceSignature(provider, creator, out.scriptPubKey, sigdata); // Put redeem_script, witness_script, key paths, into PSBTOutput. diff --git a/src/pubkey.h b/src/pubkey.h index f174ad8d85..ae6356c246 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -141,7 +141,7 @@ public: template <typename Stream> void Unserialize(Stream& s) { - unsigned int len = ::ReadCompactSize(s); + const unsigned int len(::ReadCompactSize(s)); if (len <= SIZE) { s.read((char*)vch, len); if (len != size()) { @@ -149,9 +149,7 @@ public: } } else { // invalid pubkey, skip available data - char dummy; - while (len--) - s.read(&dummy, 1); + s.ignore(len); Invalidate(); } } diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index b68ce39b53..81a1d88d20 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -870,7 +870,7 @@ void BitcoinGUI::showHelpMessageClicked() #ifdef ENABLE_WALLET void BitcoinGUI::openClicked() { - OpenURIDialog dlg(this); + OpenURIDialog dlg(platformStyle, this); if(dlg.exec()) { Q_EMIT receivedURI(dlg.getURI()); diff --git a/src/qt/forms/openuridialog.ui b/src/qt/forms/openuridialog.ui index 1b7291ab9d..97399e59a2 100644 --- a/src/qt/forms/openuridialog.ui +++ b/src/qt/forms/openuridialog.ui @@ -30,6 +30,27 @@ </property> </widget> </item> + <item> + <widget class="QToolButton" name="pasteButton"> + <property name="toolTip"> + <string extracomment="Tooltip text for button that allows you to paste an address that is in your clipboard.">Paste address from clipboard</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../bitcoin.qrc"> + <normaloff>:/icons/editpaste</normaloff>:/icons/editpaste + </iconset> + </property> + <property name="iconSize"> + <size> + <width>22</width> + <height>22</height> + </size> + </property> + </widget> + </item> </layout> </item> <item> diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index 1c22124616..6b3a4630a3 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -33,7 +33,7 @@ <string>Automatically start %1 after logging in to the system.</string> </property> <property name="text"> - <string>Start %1 on system &login</string> + <string>&Start %1 on system login</string> </property> </widget> </item> @@ -192,7 +192,7 @@ <string extracomment="Tooltip text for Options window setting that enables the RPC server.">This allows you or a third party tool to communicate with the node through command-line and JSON-RPC commands.</string> </property> <property name="text"> - <string extracomment="An Options window setting to enable the RPC server.">Enable RPC &server</string> + <string extracomment="An Options window setting to enable the RPC server.">Enable R&PC server</string> </property> </widget> </item> diff --git a/src/qt/openuridialog.cpp b/src/qt/openuridialog.cpp index 10bf82d532..a68eee718e 100644 --- a/src/qt/openuridialog.cpp +++ b/src/qt/openuridialog.cpp @@ -6,15 +6,20 @@ #include <qt/forms/ui_openuridialog.h> #include <qt/guiutil.h> +#include <qt/platformstyle.h> #include <qt/sendcoinsrecipient.h> +#include <QAbstractButton> +#include <QLineEdit> #include <QUrl> -OpenURIDialog::OpenURIDialog(QWidget *parent) : - QDialog(parent, GUIUtil::dialog_flags), - ui(new Ui::OpenURIDialog) +OpenURIDialog::OpenURIDialog(const PlatformStyle* platformStyle, QWidget* parent) : QDialog(parent, GUIUtil::dialog_flags), + ui(new Ui::OpenURIDialog), + m_platform_style(platformStyle) { ui->setupUi(this); + ui->pasteButton->setIcon(m_platform_style->SingleColorIcon(":/icons/editpaste")); + QObject::connect(ui->pasteButton, &QAbstractButton::clicked, ui->uriEdit, &QLineEdit::paste); GUIUtil::handleCloseWindowShortcut(this); } @@ -32,11 +37,19 @@ QString OpenURIDialog::getURI() void OpenURIDialog::accept() { SendCoinsRecipient rcp; - if(GUIUtil::parseBitcoinURI(getURI(), &rcp)) - { + if (GUIUtil::parseBitcoinURI(getURI(), &rcp)) { /* Only accept value URIs */ QDialog::accept(); } else { ui->uriEdit->setValid(false); } } + +void OpenURIDialog::changeEvent(QEvent* e) +{ + if (e->type() == QEvent::PaletteChange) { + ui->pasteButton->setIcon(m_platform_style->SingleColorIcon(":/icons/editpaste")); + } + + QDialog::changeEvent(e); +} diff --git a/src/qt/openuridialog.h b/src/qt/openuridialog.h index efe4b86f37..f3a8b0ba22 100644 --- a/src/qt/openuridialog.h +++ b/src/qt/openuridialog.h @@ -7,6 +7,8 @@ #include <QDialog> +class PlatformStyle; + namespace Ui { class OpenURIDialog; } @@ -16,16 +18,19 @@ class OpenURIDialog : public QDialog Q_OBJECT public: - explicit OpenURIDialog(QWidget *parent); + explicit OpenURIDialog(const PlatformStyle* platformStyle, QWidget* parent); ~OpenURIDialog(); QString getURI(); protected Q_SLOTS: void accept() override; + void changeEvent(QEvent* e) override; private: - Ui::OpenURIDialog *ui; + Ui::OpenURIDialog* ui; + + const PlatformStyle* m_platform_style; }; #endif // BITCOIN_QT_OPENURIDIALOG_H diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 0c3332ab76..6bcdcc4c29 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -866,7 +866,11 @@ void RPCConsole::clear(bool keep_prompt) } // Set default style sheet +#ifdef Q_OS_MAC + QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont(/*use_embedded_font=*/true)); +#else QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont()); +#endif ui->messagesWidget->document()->setDefaultStyleSheet( QString( "table { }" diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index b26cddf4ae..e7a3d724bb 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -69,7 +69,7 @@ int main(int argc, char* argv[]) #if defined(WIN32) if (getenv("QT_QPA_PLATFORM") == nullptr) _putenv_s("QT_QPA_PLATFORM", "minimal"); #else - setenv("QT_QPA_PLATFORM", "minimal", /* overwrite */ 0); + setenv("QT_QPA_PLATFORM", "minimal", 0 /* overwrite */); #endif // Don't remove this, it's needed to access diff --git a/src/randomenv.h b/src/randomenv.h index 46cea6f6f2..746516b79b 100644 --- a/src/randomenv.h +++ b/src/randomenv.h @@ -14,4 +14,4 @@ void RandAddDynamicEnv(CSHA512& hasher); /** Gather non-cryptographic environment data that does not change over time. */ void RandAddStaticEnv(CSHA512& hasher); -#endif +#endif // BITCOIN_RANDOMENV_H diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index d9c6761f47..65f6b1429e 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -67,4 +67,4 @@ CBlockPolicyEstimator& EnsureAnyFeeEstimator(const std::any& context); */ UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFile& afile); -#endif +#endif // BITCOIN_RPC_BLOCKCHAIN_H diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 2942c160e1..89abdd057c 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1010,7 +1010,7 @@ static RPCHelpMan submitblock() bool new_block; auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash()); RegisterSharedValidationInterface(sc); - bool accepted = chainman.ProcessNewBlock(Params(), blockptr, /* fForceProcessing */ true, /* fNewBlock */ &new_block); + bool accepted = chainman.ProcessNewBlock(Params(), blockptr, /*force_processing=*/true, /*new_block=*/&new_block); UnregisterSharedValidationInterface(sc); if (!new_block && accepted) { return "duplicate"; diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 39bd9c6091..2bd8a6b050 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -52,6 +52,10 @@ static RPCHelpMan validateaddress() {RPCResult::Type::NUM, "witness_version", /* optional */ true, "The version number of the witness program"}, {RPCResult::Type::STR_HEX, "witness_program", /* optional */ true, "The hex value of the witness program"}, {RPCResult::Type::STR, "error", /* optional */ true, "Error message, if any"}, + {RPCResult::Type::ARR, "error_locations", "Indices of likely error locations in address, if known (e.g. Bech32 errors)", + { + {RPCResult::Type::NUM, "index", "index of a potential error"}, + }}, } }, RPCExamples{ @@ -61,7 +65,8 @@ static RPCHelpMan validateaddress() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::string error_msg; - CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg); + std::vector<int> error_locations; + CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg, &error_locations); const bool isValid = IsValidDestination(dest); CHECK_NONFATAL(isValid == error_msg.empty()); @@ -77,6 +82,9 @@ static RPCHelpMan validateaddress() UniValue detail = DescribeAddress(dest); ret.pushKVs(detail); } else { + UniValue error_indices(UniValue::VARR); + for (int i : error_locations) error_indices.push_back(i); + ret.pushKV("error_locations", error_indices); ret.pushKV("error", error_msg); } diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 89f2309cb7..2dd121c6f6 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1619,7 +1619,7 @@ static RPCHelpMan utxoupdatepsbt() } } // We don't actually need private keys further on; hide them as a precaution. - HidingSigningProvider public_provider(&provider, /* nosign */ true, /* nobip32derivs */ false); + HidingSigningProvider public_provider(&provider, /*hide_secret=*/true, /*hide_origin=*/false); // Fetch previous transactions (inputs): CCoinsView viewDummy; @@ -1658,7 +1658,7 @@ static RPCHelpMan utxoupdatepsbt() // Update script/keypath information using descriptor data. // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures // we don't actually care about those here, in fact. - SignPSBTInput(public_provider, psbtx, i, &txdata, /* sighash_type */ 1); + SignPSBTInput(public_provider, psbtx, i, &txdata, /*sighash=*/1); } // Update script/keypath information using descriptor data. diff --git a/src/scheduler.h b/src/scheduler.h index 9eec8c0fa0..be878661db 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -146,4 +146,4 @@ public: size_t CallbacksPending(); }; -#endif +#endif // BITCOIN_SCHEDULER_H diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 621a1b9fd6..c3b4d1ddaa 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -851,6 +851,7 @@ protected: builder.Finalize(xpk); WitnessV1Taproot output = builder.GetOutput(); out.tr_spenddata[output].Merge(builder.GetSpendData()); + out.pubkeys.emplace(keys[0].GetID(), keys[0]); return Vector(GetScriptForDestination(output)); } bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr) const override diff --git a/src/script/sign.cpp b/src/script/sign.cpp index b282f39e6d..8e044b1e00 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -17,16 +17,16 @@ typedef std::vector<unsigned char> valtype; -MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) - : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL), +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* tx, unsigned int input_idx, const CAmount& amount, int hash_type) + : txTo{tx}, nIn{input_idx}, nHashType{hash_type}, amount{amount}, checker{txTo, nIn, amount, MissingDataBehavior::FAIL}, m_txdata(nullptr) { } -MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn) - : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), - checker(txdata ? MutableTransactionSignatureChecker(txTo, nIn, amount, *txdata, MissingDataBehavior::FAIL) : - MutableTransactionSignatureChecker(txTo, nIn, amount, MissingDataBehavior::FAIL)), +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* tx, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, int hash_type) + : txTo{tx}, nIn{input_idx}, nHashType{hash_type}, amount{amount}, + checker{txdata ? MutableTransactionSignatureChecker{txTo, nIn, amount, *txdata, MissingDataBehavior::FAIL} : + MutableTransactionSignatureChecker{txTo, nIn, amount, MissingDataBehavior::FAIL}}, m_txdata(txdata) { } diff --git a/src/script/sign.h b/src/script/sign.h index 6d3479c143..62335b644a 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -45,8 +45,8 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator { const PrecomputedTransactionData* m_txdata; public: - MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn); - MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn); + MutableTransactionSignatureCreator(const CMutableTransaction* tx, unsigned int input_idx, const CAmount& amount, int hash_type); + MutableTransactionSignatureCreator(const CMutableTransaction* tx, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, int hash_type); const BaseSignatureChecker& Checker() const override { return checker; } bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override; bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const override; diff --git a/src/script/signingprovider.cpp b/src/script/signingprovider.cpp index b80fbe22ce..17f97fa30c 100644 --- a/src/script/signingprovider.cpp +++ b/src/script/signingprovider.cpp @@ -190,8 +190,8 @@ bool FillableSigningProvider::GetCScript(const CScriptID &hash, CScript& redeemS CKeyID GetKeyForDestination(const SigningProvider& store, const CTxDestination& dest) { - // Only supports destinations which map to single public keys, i.e. P2PKH, - // P2WPKH, and P2SH-P2WPKH. + // Only supports destinations which map to single public keys: + // P2PKH, P2WPKH, P2SH-P2WPKH, P2TR if (auto id = std::get_if<PKHash>(&dest)) { return ToKeyID(*id); } @@ -208,5 +208,15 @@ CKeyID GetKeyForDestination(const SigningProvider& store, const CTxDestination& } } } + if (auto output_key = std::get_if<WitnessV1Taproot>(&dest)) { + TaprootSpendData spenddata; + CPubKey pub; + if (store.GetTaprootSpendData(*output_key, spenddata) + && !spenddata.internal_key.IsNull() + && spenddata.merkle_root.IsNull() + && store.GetPubKeyByXOnly(spenddata.internal_key, pub)) { + return pub.GetID(); + } + } return CKeyID(); } diff --git a/src/shutdown.h b/src/shutdown.h index ff56c6bd87..9df601d478 100644 --- a/src/shutdown.h +++ b/src/shutdown.h @@ -32,4 +32,4 @@ bool ShutdownRequested(); */ void WaitForShutdown(); -#endif +#endif // BITCOIN_SHUTDOWN_H diff --git a/src/span.h b/src/span.h index 830164514b..746e41f2f4 100644 --- a/src/span.h +++ b/src/span.h @@ -30,7 +30,11 @@ /** A Span is an object that can refer to a contiguous sequence of objects. * - * It implements a subset of C++20's std::span. + * This file implements a subset of C++20's std::span. It can be considered + * temporary compatibility code until C++20 and is designed to be a + * self-contained abstraction without depending on other project files. For this + * reason, Clang lifetimebound is defined here instead of including + * <attributes.h>, which also defines it. * * Things to be aware of when writing code that deals with Spans: * @@ -60,7 +64,7 @@ * types that expose a data() and size() member function), functions that * accept a Span as input parameter can be called with any compatible * range-like object. For example, this works: -* + * * void Foo(Span<const int> arg); * * Foo(std::vector<int>{1, 2, 3}); // Works @@ -180,6 +184,7 @@ public: return m_data[m_size - 1]; } constexpr std::size_t size() const noexcept { return m_size; } + constexpr std::size_t size_bytes() const noexcept { return sizeof(C) * m_size; } constexpr bool empty() const noexcept { return size() == 0; } CONSTEXPR_IF_NOT_DEBUG C& operator[](std::size_t pos) const noexcept { @@ -236,11 +241,35 @@ T& SpanPopBack(Span<T>& span) return back; } +// From C++20 as_bytes and as_writeable_bytes +template <typename T> +Span<const std::byte> AsBytes(Span<T> s) noexcept +{ + return {reinterpret_cast<const std::byte*>(s.data()), s.size_bytes()}; +} +template <typename T> +Span<std::byte> AsWritableBytes(Span<T> s) noexcept +{ + return {reinterpret_cast<std::byte*>(s.data()), s.size_bytes()}; +} + +template <typename V> +Span<const std::byte> MakeByteSpan(V&& v) noexcept +{ + return AsBytes(MakeSpan(std::forward<V>(v))); +} +template <typename V> +Span<std::byte> MakeWritableByteSpan(V&& v) noexcept +{ + return AsWritableBytes(MakeSpan(std::forward<V>(v))); +} + // Helper functions to safely cast to unsigned char pointers. inline unsigned char* UCharCast(char* c) { return (unsigned char*)c; } inline unsigned char* UCharCast(unsigned char* c) { return c; } inline const unsigned char* UCharCast(const char* c) { return (unsigned char*)c; } inline const unsigned char* UCharCast(const unsigned char* c) { return c; } +inline const unsigned char* UCharCast(const std::byte* c) { return reinterpret_cast<const unsigned char*>(c); } // Helper function to safely convert a Span to a Span<[const] unsigned char>. template <typename T> constexpr auto UCharSpanCast(Span<T> s) -> Span<typename std::remove_pointer<decltype(UCharCast(s.data()))>::type> { return {UCharCast(s.data()), s.size()}; } @@ -248,4 +277,4 @@ template <typename T> constexpr auto UCharSpanCast(Span<T> s) -> Span<typename s /** Like MakeSpan, but for (const) unsigned char member types only. Only works for (un)signed char containers. */ template <typename V> constexpr auto MakeUCharSpan(V&& v) -> decltype(UCharSpanCast(MakeSpan(std::forward<V>(v)))) { return UCharSpanCast(MakeSpan(std::forward<V>(v))); } -#endif +#endif // BITCOIN_SPAN_H diff --git a/src/streams.h b/src/streams.h index 31407287ae..9e8f379cd2 100644 --- a/src/streams.h +++ b/src/streams.h @@ -226,7 +226,7 @@ public: : nType{nTypeIn}, nVersion{nVersionIn} {} - explicit CDataStream(Span<const uint8_t> sp, int nTypeIn, int nVersionIn) + explicit CDataStream(Span<const value_type> sp, int nTypeIn, int nVersionIn) : vch(sp.data(), sp.data() + sp.size()), nType{nTypeIn}, nVersion{nVersionIn} {} @@ -254,17 +254,17 @@ public: iterator end() { return vch.end(); } size_type size() const { return vch.size() - nReadPos; } bool empty() const { return vch.size() == nReadPos; } - void resize(size_type n, value_type c=0) { vch.resize(n + nReadPos, c); } + void resize(size_type n, value_type c = value_type{}) { vch.resize(n + nReadPos, c); } void reserve(size_type n) { vch.reserve(n + nReadPos); } const_reference operator[](size_type pos) const { return vch[pos + nReadPos]; } reference operator[](size_type pos) { return vch[pos + nReadPos]; } void clear() { vch.clear(); nReadPos = 0; } - iterator insert(iterator it, const uint8_t x) { return vch.insert(it, x); } - void insert(iterator it, size_type n, const uint8_t x) { vch.insert(it, n, x); } + iterator insert(iterator it, const value_type x) { return vch.insert(it, x); } + void insert(iterator it, size_type n, const value_type x) { vch.insert(it, n, x); } value_type* data() { return vch.data() + nReadPos; } const value_type* data() const { return vch.data() + nReadPos; } - void insert(iterator it, std::vector<uint8_t>::const_iterator first, std::vector<uint8_t>::const_iterator last) + void insert(iterator it, std::vector<value_type>::const_iterator first, std::vector<value_type>::const_iterator last) { if (last == first) return; assert(last - first > 0); @@ -278,7 +278,7 @@ public: vch.insert(it, first, last); } - void insert(iterator it, const char* first, const char* last) + void insert(iterator it, const value_type* first, const value_type* last) { if (last == first) return; assert(last - first > 0); diff --git a/src/test/base64_tests.cpp b/src/test/base64_tests.cpp index 9d1dfd46f1..c5fce7bec0 100644 --- a/src/test/base64_tests.cpp +++ b/src/test/base64_tests.cpp @@ -23,6 +23,16 @@ BOOST_AUTO_TEST_CASE(base64_testvectors) BOOST_CHECK_EQUAL(strDec, vstrIn[i]); } + { + const std::vector<uint8_t> in_u{0xff, 0x01, 0xff}; + const std::vector<std::byte> in_b{std::byte{0xff}, std::byte{0x01}, std::byte{0xff}}; + const std::string in_s{"\xff\x01\xff"}; + const std::string out_exp{"/wH/"}; + BOOST_CHECK_EQUAL(EncodeBase64(in_u), out_exp); + BOOST_CHECK_EQUAL(EncodeBase64(in_b), out_exp); + BOOST_CHECK_EQUAL(EncodeBase64(in_s), out_exp); + } + // Decoding strings with embedded NUL characters should fail bool failure; (void)DecodeBase64("invalid\0"s, &failure); diff --git a/src/test/bech32_tests.cpp b/src/test/bech32_tests.cpp index c0344b3cbb..8eed959228 100644 --- a/src/test/bech32_tests.cpp +++ b/src/test/bech32_tests.cpp @@ -1,4 +1,5 @@ // Copyright (c) 2017 Pieter Wuille +// Copyright (c) 2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -68,10 +69,36 @@ BOOST_AUTO_TEST_CASE(bech32_testvectors_invalid) "1qzzfhee", "a12UEL5L", "A12uEL5L", + "abcdef1qpzrz9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", }; + static const std::pair<std::string, int> ERRORS[] = { + {"Invalid character or mixed case", 0}, + {"Invalid character or mixed case", 0}, + {"Invalid character or mixed case", 0}, + {"Bech32 string too long", 90}, + {"Missing separator", -1}, + {"Invalid separator position", 0}, + {"Invalid Base 32 character", 2}, + {"Invalid separator position", 2}, + {"Invalid character or mixed case", 8}, + {"Invalid checksum", -1}, // The checksum is calculated using the uppercase form so the entire string is invalid, not just a few characters + {"Invalid separator position", 0}, + {"Invalid separator position", 0}, + {"Invalid character or mixed case", 3}, + {"Invalid character or mixed case", 3}, + {"Invalid checksum", 11} + }; + int i = 0; for (const std::string& str : CASES) { + const auto& err = ERRORS[i]; const auto dec = bech32::Decode(str); BOOST_CHECK(dec.encoding == bech32::Encoding::INVALID); + std::vector<int> error_locations; + std::string error = bech32::LocateErrors(str, error_locations); + BOOST_CHECK_EQUAL(err.first, error); + if (err.second == -1) BOOST_CHECK(error_locations.empty()); + else BOOST_CHECK_EQUAL(err.second, error_locations[0]); + i++; } } @@ -91,11 +118,37 @@ BOOST_AUTO_TEST_CASE(bech32m_testvectors_invalid) "au1s5cgom", "M1VUXWEZ", "16plkw9", - "1p2gdwpf" + "1p2gdwpf", + "abcdef1l7aum6echk45nj2s0wdvt2fg8x9yrzpqzd3ryx", + }; + static const std::pair<std::string, int> ERRORS[] = { + {"Invalid character or mixed case", 0}, + {"Invalid character or mixed case", 0}, + {"Invalid character or mixed case", 0}, + {"Bech32 string too long", 90}, + {"Missing separator", -1}, + {"Invalid separator position", 0}, + {"Invalid Base 32 character", 2}, + {"Invalid Base 32 character", 3}, + {"Invalid separator position", 2}, + {"Invalid Base 32 character", 8}, + {"Invalid Base 32 character", 7}, + {"Invalid checksum", -1}, + {"Invalid separator position", 0}, + {"Invalid separator position", 0}, + {"Invalid checksum", 21}, }; + int i = 0; for (const std::string& str : CASES) { + const auto& err = ERRORS[i]; const auto dec = bech32::Decode(str); BOOST_CHECK(dec.encoding == bech32::Encoding::INVALID); + std::vector<int> error_locations; + std::string error = bech32::LocateErrors(str, error_locations); + BOOST_CHECK_EQUAL(err.first, error); + if (err.second == -1) BOOST_CHECK(error_locations.empty()); + else BOOST_CHECK_EQUAL(err.second, error_locations[0]); + i++; } } diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 668ff150ee..765663e0ef 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -59,7 +59,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) // Mock an outbound peer CAddress addr1(ip(0xa0b0c001), NODE_NONE); - CNode dummyNode1(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), INVALID_SOCKET, addr1, /* nKeyedNetGroupIn */ 0, /* nLocalHostNonceIn */ 0, CAddress(), /* pszDest */ "", ConnectionType::OUTBOUND_FULL_RELAY, /* inbound_onion */ false); + CNode dummyNode1(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), INVALID_SOCKET, addr1, /*nKeyedNetGroupIn=*/0, /*nLocalHostNonceIn=*/0, CAddress(), /*addrNameIn=*/"", ConnectionType::OUTBOUND_FULL_RELAY, /*inbound_onion=*/false); dummyNode1.SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(&dummyNode1); @@ -108,7 +108,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) static void AddRandomOutboundPeer(std::vector<CNode*>& vNodes, PeerManager& peerLogic, ConnmanTestMsg& connman) { CAddress addr(ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE); - vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), INVALID_SOCKET, addr, /* nKeyedNetGroupIn */ 0, /* nLocalHostNonceIn */ 0, CAddress(), /* pszDest */ "", ConnectionType::OUTBOUND_FULL_RELAY, /* inbound_onion */ false)); + vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), INVALID_SOCKET, addr, /*nKeyedNetGroupIn=*/0, /*nLocalHostNonceIn=*/0, CAddress(), /*addrNameIn=*/"", ConnectionType::OUTBOUND_FULL_RELAY, /*inbound_onion=*/false)); CNode &node = *vNodes.back(); node.SetCommonVersion(PROTOCOL_VERSION); @@ -212,9 +212,9 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) std::array<CNode*, 3> nodes; banman->ClearBanned(); - nodes[0] = new CNode{id++, NODE_NETWORK, INVALID_SOCKET, addr[0], /* nKeyedNetGroupIn */ 0, - /* nLocalHostNonceIn */ 0, CAddress(), /* pszDest */ "", - ConnectionType::INBOUND, /* inbound_onion */ false}; + nodes[0] = new CNode{id++, NODE_NETWORK, INVALID_SOCKET, addr[0], /*nKeyedNetGroupIn=*/0, + /*nLocalHostNonceIn */ 0, CAddress(), /*addrNameIn=*/"", + ConnectionType::INBOUND, /*inbound_onion=*/false}; nodes[0]->SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(nodes[0]); nodes[0]->fSuccessfullyConnected = true; @@ -228,9 +228,9 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) BOOST_CHECK(nodes[0]->fDisconnect); BOOST_CHECK(!banman->IsDiscouraged(other_addr)); // Different address, not discouraged - nodes[1] = new CNode{id++, NODE_NETWORK, INVALID_SOCKET, addr[1], /* nKeyedNetGroupIn */ 1, - /* nLocalHostNonceIn */ 1, CAddress(), /* pszDest */ "", - ConnectionType::INBOUND, /* inbound_onion */ false}; + nodes[1] = new CNode{id++, NODE_NETWORK, INVALID_SOCKET, addr[1], /*nKeyedNetGroupIn=*/1, + /*nLocalHostNonceIn */ 1, CAddress(), /*addrNameIn=*/"", + ConnectionType::INBOUND, /*inbound_onion=*/false}; nodes[1]->SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(nodes[1]); nodes[1]->fSuccessfullyConnected = true; @@ -259,9 +259,9 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) // Make sure non-IP peers are discouraged and disconnected properly. - nodes[2] = new CNode{id++, NODE_NETWORK, INVALID_SOCKET, addr[2], /* nKeyedNetGroupIn */ 1, - /* nLocalHostNonceIn */ 1, CAddress(), /* pszDest */ "", - ConnectionType::OUTBOUND_FULL_RELAY, /* inbound_onion */ false}; + nodes[2] = new CNode{id++, NODE_NETWORK, INVALID_SOCKET, addr[2], /*nKeyedNetGroupIn=*/1, + /*nLocalHostNonceIn */ 1, CAddress(), /*addrNameIn=*/"", + ConnectionType::OUTBOUND_FULL_RELAY, /*inbound_onion=*/false}; nodes[2]->SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(nodes[2]); nodes[2]->fSuccessfullyConnected = true; @@ -297,7 +297,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) SetMockTime(nStartTime); // Overrides future calls to GetTime() CAddress addr(ip(0xa0b0c001), NODE_NONE); - CNode dummyNode(id++, NODE_NETWORK, INVALID_SOCKET, addr, /* nKeyedNetGroupIn */ 4, /* nLocalHostNonceIn */ 4, CAddress(), /* pszDest */ "", ConnectionType::INBOUND, /* inbound_onion */ false); + CNode dummyNode(id++, NODE_NETWORK, INVALID_SOCKET, addr, /*nKeyedNetGroupIn=*/4, /*nLocalHostNonceIn=*/4, CAddress(), /*addrNameIn=*/"", ConnectionType::INBOUND, /*inbound_onion=*/false); dummyNode.SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(&dummyNode); dummyNode.fSuccessfullyConnected = true; diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index 325a9a170e..2f33598348 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -221,8 +221,7 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view) assert(expected_code_path); }, [&] { - (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache, false); - (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache, true); + (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache); }, [&] { TxValidationState state; diff --git a/src/test/fuzz/minisketch.cpp b/src/test/fuzz/minisketch.cpp new file mode 100644 index 0000000000..93954bd3cf --- /dev/null +++ b/src/test/fuzz/minisketch.cpp @@ -0,0 +1,64 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <minisketch.h> +#include <node/minisketchwrapper.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <util/check.h> + +#include <map> +#include <numeric> + +FUZZ_TARGET(minisketch) +{ + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + const auto capacity{fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 200)}; + Minisketch sketch_a{Assert(MakeMinisketch32(capacity))}; + Minisketch sketch_b{Assert(MakeMinisketch32(capacity))}; + + // Fill two sets and keep the difference in a map + std::map<uint32_t, bool> diff; + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) + { + const auto entry{fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, std::numeric_limits<uint32_t>::max() - 1)}; + const auto KeepDiff{[&] { + bool& mut{diff[entry]}; + mut = !mut; + }}; + CallOneOf( + fuzzed_data_provider, + [&] { + sketch_a.Add(entry); + KeepDiff(); + }, + [&] { + sketch_b.Add(entry); + KeepDiff(); + }, + [&] { + sketch_a.Add(entry); + sketch_b.Add(entry); + }); + } + const auto num_diff{std::accumulate(diff.begin(), diff.end(), size_t{0}, [](auto n, const auto& e) { return n + e.second; })}; + + Minisketch sketch_ar{MakeMinisketch32(capacity)}; + Minisketch sketch_br{MakeMinisketch32(capacity)}; + sketch_ar.Deserialize(sketch_a.Serialize()); + sketch_br.Deserialize(sketch_b.Serialize()); + + Minisketch sketch_diff{std::move(fuzzed_data_provider.ConsumeBool() ? sketch_a : sketch_ar)}; + sketch_diff.Merge(fuzzed_data_provider.ConsumeBool() ? sketch_b : sketch_br); + + if (capacity >= num_diff) { + const auto max_elements{fuzzed_data_provider.ConsumeIntegralInRange<size_t>(num_diff, capacity)}; + const auto dec{*Assert(sketch_diff.Decode(max_elements))}; + Assert(dec.size() == num_diff); + for (auto d : dec) { + Assert(diff.at(d)); + } + } +} diff --git a/src/test/fuzz/transaction.cpp b/src/test/fuzz/transaction.cpp index a21e5cea0c..ba6c500543 100644 --- a/src/test/fuzz/transaction.cpp +++ b/src/test/fuzz/transaction.cpp @@ -98,8 +98,7 @@ FUZZ_TARGET_INIT(transaction, initialize_transaction) CCoinsView coins_view; const CCoinsViewCache coins_view_cache(&coins_view); - (void)AreInputsStandard(tx, coins_view_cache, false); - (void)AreInputsStandard(tx, coins_view_cache, true); + (void)AreInputsStandard(tx, coins_view_cache); (void)IsWitnessStandard(tx, coins_view_cache); UniValue u(UniValue::VOBJ); diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp index 0a34eb14f5..ac1fb657f1 100644 --- a/src/test/fuzz/tx_pool.cpp +++ b/src/test/fuzz/tx_pool.cpp @@ -93,7 +93,7 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, CCh const auto info_all = tx_pool.infoAll(); if (!info_all.empty()) { const auto& tx_to_remove = *PickValue(fuzzed_data_provider, info_all).tx; - WITH_LOCK(tx_pool.cs, tx_pool.removeRecursive(tx_to_remove, /* dummy */ MemPoolRemovalReason::BLOCK)); + WITH_LOCK(tx_pool.cs, tx_pool.removeRecursive(tx_to_remove, MemPoolRemovalReason::BLOCK /* dummy */)); std::vector<uint256> all_txids; tx_pool.queryHashes(all_txids); assert(all_txids.size() < info_all.size()); diff --git a/src/test/script_p2sh_tests.cpp b/src/test/script_p2sh_tests.cpp index d8a44a65dd..17b3359624 100644 --- a/src/test/script_p2sh_tests.cpp +++ b/src/test/script_p2sh_tests.cpp @@ -343,7 +343,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) txTo.vin[3].scriptSig << OP_11 << OP_11 << std::vector<unsigned char>(oneAndTwo.begin(), oneAndTwo.end()); txTo.vin[4].scriptSig << std::vector<unsigned char>(fifteenSigops.begin(), fifteenSigops.end()); - BOOST_CHECK(::AreInputsStandard(CTransaction(txTo), coins, false)); + BOOST_CHECK(::AreInputsStandard(CTransaction(txTo), coins)); // 22 P2SH sigops for all inputs (1 for vin[0], 6 for vin[3], 15 for vin[4] BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txTo), coins), 22U); @@ -356,7 +356,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) txToNonStd1.vin[0].prevout.hash = txFrom.GetHash(); txToNonStd1.vin[0].scriptSig << std::vector<unsigned char>(sixteenSigops.begin(), sixteenSigops.end()); - BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd1), coins, false)); + BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd1), coins)); BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd1), coins), 16U); CMutableTransaction txToNonStd2; @@ -368,7 +368,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) txToNonStd2.vin[0].prevout.hash = txFrom.GetHash(); txToNonStd2.vin[0].scriptSig << std::vector<unsigned char>(twentySigops.begin(), twentySigops.end()); - BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd2), coins, false)); + BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd2), coins)); BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd2), coins), 20U); } diff --git a/src/test/scriptnum10.h b/src/test/scriptnum10.h index 352797f18d..3937366f01 100644 --- a/src/test/scriptnum10.h +++ b/src/test/scriptnum10.h @@ -179,4 +179,4 @@ private: }; -#endif // BITCOIN_TEST_BIGNUM_H +#endif // BITCOIN_TEST_SCRIPTNUM10_H diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index 54f04d2e67..b8d76c9608 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -172,7 +172,7 @@ BOOST_AUTO_TEST_CASE(streams_serializedata_xor) ds.Xor(key); BOOST_CHECK_EQUAL( std::string(expected_xor.begin(), expected_xor.end()), - std::string(ds.begin(), ds.end())); + ds.str()); in.push_back('\x0f'); in.push_back('\xf0'); @@ -189,7 +189,7 @@ BOOST_AUTO_TEST_CASE(streams_serializedata_xor) ds.Xor(key); BOOST_CHECK_EQUAL( std::string(expected_xor.begin(), expected_xor.end()), - std::string(ds.begin(), ds.end())); + ds.str()); // Multi character key @@ -210,7 +210,7 @@ BOOST_AUTO_TEST_CASE(streams_serializedata_xor) ds.Xor(key); BOOST_CHECK_EQUAL( std::string(expected_xor.begin(), expected_xor.end()), - std::string(ds.begin(), ds.end())); + ds.str()); } BOOST_AUTO_TEST_CASE(streams_buffered_file) diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 252a85c282..e05781a6f0 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -406,7 +406,7 @@ BOOST_AUTO_TEST_CASE(test_Get) t1.vout[0].nValue = 90*CENT; t1.vout[0].scriptPubKey << OP_1; - BOOST_CHECK(AreInputsStandard(CTransaction(t1), coins, false)); + BOOST_CHECK(AreInputsStandard(CTransaction(t1), coins)); } static void CreateCreditAndSpend(const FillableSigningProvider& keystore, const CScript& outscript, CTransactionRef& output, CMutableTransaction& input, bool success = true) diff --git a/src/test/txpackage_tests.cpp b/src/test/txpackage_tests.cpp index 537a6ccea1..8d92bee221 100644 --- a/src/test/txpackage_tests.cpp +++ b/src/test/txpackage_tests.cpp @@ -73,19 +73,19 @@ BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup) CKey parent_key; parent_key.MakeNewKey(true); CScript parent_locking_script = GetScriptForDestination(PKHash(parent_key.GetPubKey())); - auto mtx_parent = CreateValidMempoolTransaction(/* input_transaction */ m_coinbase_txns[0], /* vout */ 0, - /* input_height */ 0, /* input_signing_key */ coinbaseKey, - /* output_destination */ parent_locking_script, - /* output_amount */ CAmount(49 * COIN), /* submit */ false); + auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/ m_coinbase_txns[0], /*input_vout=*/0, + /*input_height=*/ 0, /*input_signing_key=*/coinbaseKey, + /*output_destination=*/ parent_locking_script, + /*output_amount=*/ CAmount(49 * COIN), /*submit=*/false); CTransactionRef tx_parent = MakeTransactionRef(mtx_parent); CKey child_key; child_key.MakeNewKey(true); CScript child_locking_script = GetScriptForDestination(PKHash(child_key.GetPubKey())); - auto mtx_child = CreateValidMempoolTransaction(/* input_transaction */ tx_parent, /* vout */ 0, - /* input_height */ 101, /* input_signing_key */ parent_key, - /* output_destination */ child_locking_script, - /* output_amount */ CAmount(48 * COIN), /* submit */ false); + auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/ tx_parent, /*input_vout=*/0, + /*input_height=*/ 101, /*input_signing_key=*/parent_key, + /*output_destination */ child_locking_script, + /*output_amount=*/ CAmount(48 * COIN), /*submit=*/false); CTransactionRef tx_child = MakeTransactionRef(mtx_child); const auto result_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, {tx_parent, tx_child}, /* test_accept */ true); BOOST_CHECK_MESSAGE(result_parent_child.m_state.IsValid(), diff --git a/src/test/util/net.h b/src/test/util/net.h index d89fc34b75..2de6e712a2 100644 --- a/src/test/util/net.h +++ b/src/test/util/net.h @@ -25,16 +25,16 @@ struct ConnmanTestMsg : public CConnman { void AddTestNode(CNode& node) { - LOCK(cs_vNodes); - vNodes.push_back(&node); + LOCK(m_nodes_mutex); + m_nodes.push_back(&node); } void ClearTestNodes() { - LOCK(cs_vNodes); - for (CNode* node : vNodes) { + LOCK(m_nodes_mutex); + for (CNode* node : m_nodes) { delete node; } - vNodes.clear(); + m_nodes.clear(); } void ProcessMessagesOnce(CNode& node) { m_msgproc->ProcessMessages(&node, flagInterruptMsgProc); } diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 7518cdb042..eb7bc071e5 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -227,4 +227,4 @@ private: const std::string m_reason; }; -#endif +#endif // BITCOIN_TEST_UTIL_SETUP_COMMON_H diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index b1300d06ba..76a690fd28 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -151,12 +151,25 @@ BOOST_AUTO_TEST_CASE(util_HexStr) HexStr(Span<const unsigned char>(ParseHex_expected, ParseHex_expected)), ""); - std::vector<unsigned char> ParseHex_vec(ParseHex_expected, ParseHex_expected + 5); + { + const std::vector<char> in_s{ParseHex_expected, ParseHex_expected + 5}; + const Span<const uint8_t> in_u{MakeUCharSpan(in_s)}; + const Span<const std::byte> in_b{MakeByteSpan(in_s)}; + const std::string out_exp{"04678afdb0"}; + + BOOST_CHECK_EQUAL(HexStr(in_u), out_exp); + BOOST_CHECK_EQUAL(HexStr(in_s), out_exp); + BOOST_CHECK_EQUAL(HexStr(in_b), out_exp); + } +} - BOOST_CHECK_EQUAL( - HexStr(ParseHex_vec), - "04678afdb0" - ); +BOOST_AUTO_TEST_CASE(span_write_bytes) +{ + std::array mut_arr{uint8_t{0xaa}, uint8_t{0xbb}}; + const auto mut_bytes{MakeWritableByteSpan(mut_arr)}; + mut_bytes[1] = std::byte{0x11}; + BOOST_CHECK_EQUAL(mut_arr.at(0), 0xaa); + BOOST_CHECK_EQUAL(mut_arr.at(1), 0x11); } BOOST_AUTO_TEST_CASE(util_Join) @@ -2456,4 +2469,52 @@ BOOST_AUTO_TEST_CASE(remove_prefix) BOOST_CHECK_EQUAL(RemovePrefix("", ""), ""); } +BOOST_AUTO_TEST_CASE(util_ParseByteUnits) +{ + auto noop = ByteUnit::NOOP; + + // no multiplier + BOOST_CHECK_EQUAL(ParseByteUnits("1", noop).value(), 1); + BOOST_CHECK_EQUAL(ParseByteUnits("0", noop).value(), 0); + + BOOST_CHECK_EQUAL(ParseByteUnits("1k", noop).value(), 1000ULL); + BOOST_CHECK_EQUAL(ParseByteUnits("1K", noop).value(), 1ULL << 10); + + BOOST_CHECK_EQUAL(ParseByteUnits("2m", noop).value(), 2'000'000ULL); + BOOST_CHECK_EQUAL(ParseByteUnits("2M", noop).value(), 2ULL << 20); + + BOOST_CHECK_EQUAL(ParseByteUnits("3g", noop).value(), 3'000'000'000ULL); + BOOST_CHECK_EQUAL(ParseByteUnits("3G", noop).value(), 3ULL << 30); + + BOOST_CHECK_EQUAL(ParseByteUnits("4t", noop).value(), 4'000'000'000'000ULL); + BOOST_CHECK_EQUAL(ParseByteUnits("4T", noop).value(), 4ULL << 40); + + // check default multiplier + BOOST_CHECK_EQUAL(ParseByteUnits("5", ByteUnit::K).value(), 5ULL << 10); + + // NaN + BOOST_CHECK(!ParseByteUnits("", noop)); + BOOST_CHECK(!ParseByteUnits("foo", noop)); + + // whitespace + BOOST_CHECK(!ParseByteUnits("123m ", noop)); + BOOST_CHECK(!ParseByteUnits(" 123m", noop)); + + // no +- + BOOST_CHECK(!ParseByteUnits("-123m", noop)); + BOOST_CHECK(!ParseByteUnits("+123m", noop)); + + // zero padding + BOOST_CHECK_EQUAL(ParseByteUnits("020M", noop).value(), 20ULL << 20); + + // fractions not allowed + BOOST_CHECK(!ParseByteUnits("0.5T", noop)); + + // overflow + BOOST_CHECK(!ParseByteUnits("18446744073709551615g", noop)); + + // invalid unit + BOOST_CHECK(!ParseByteUnits("1x", noop)); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 22f7136337..3efa74fcc3 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -222,7 +222,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) { bool ignored; auto ProcessBlock = [&](std::shared_ptr<const CBlock> block) -> bool { - return Assert(m_node.chainman)->ProcessNewBlock(Params(), block, /* fForceProcessing */ true, /* fNewBlock */ &ignored); + return Assert(m_node.chainman)->ProcessNewBlock(Params(), block, /*force_processing=*/true, /*new_block=*/&ignored); }; // Process all mined blocks diff --git a/src/threadinterrupt.h b/src/threadinterrupt.h index 2665f8a5be..42470c70ee 100644 --- a/src/threadinterrupt.h +++ b/src/threadinterrupt.h @@ -33,4 +33,4 @@ private: std::atomic<bool> flag; }; -#endif //BITCOIN_THREADINTERRUPT_H +#endif // BITCOIN_THREADINTERRUPT_H diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 55618a5c57..776981b7f7 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -22,19 +22,17 @@ #include <deque> #include <functional> #include <set> -#include <stdlib.h> #include <vector> -#include <boost/signals2/signal.hpp> -#include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/replace.hpp> +#include <boost/algorithm/string/split.hpp> -#include <event2/bufferevent.h> #include <event2/buffer.h> -#include <event2/util.h> +#include <event2/bufferevent.h> #include <event2/event.h> #include <event2/thread.h> +#include <event2/util.h> /** Default control port */ const std::string DEFAULT_TOR_CONTROL = "127.0.0.1:9051"; @@ -277,9 +275,15 @@ std::map<std::string,std::string> ParseTorReplyMapping(const std::string &s) if (j == 3 && value[i] > '3') { j--; } - escaped_value.push_back(strtol(value.substr(i, j).c_str(), nullptr, 8)); + const auto end{i + j}; + uint8_t val{0}; + while (i < end) { + val *= 8; + val += value[i++] - '0'; + } + escaped_value.push_back(char(val)); // Account for automatic incrementing at loop end - i += j - 1; + --i; } else { escaped_value.push_back(value[i]); } diff --git a/src/torcontrol.h b/src/torcontrol.h index 7258f27cb6..8fc852f843 100644 --- a/src/torcontrol.h +++ b/src/torcontrol.h @@ -157,4 +157,4 @@ public: static void reconnect_cb(evutil_socket_t fd, short what, void *arg); }; -#endif /* BITCOIN_TORCONTROL_H */ +#endif // BITCOIN_TORCONTROL_H diff --git a/src/util/overloaded.h b/src/util/overloaded.h new file mode 100644 index 0000000000..6be7453f81 --- /dev/null +++ b/src/util/overloaded.h @@ -0,0 +1,22 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_OVERLOADED_H +#define BITCOIN_UTIL_OVERLOADED_H + +namespace util { +//! Overloaded helper for std::visit. This helper and std::visit in general are +//! useful to write code that switches on a variant type. Unlike if/else-if and +//! switch/case statements, std::visit will trigger compile errors if there are +//! unhandled cases. +//! +//! Implementation comes from and example usage can be found at +//! https://en.cppreference.com/w/cpp/utility/variant/visit#Example +template<class... Ts> struct Overloaded : Ts... { using Ts::operator()...; }; + +//! Explicit deduction guide (not needed as of C++20) +template<class... Ts> Overloaded(Ts...) -> Overloaded<Ts...>; +} // namespace util + +#endif // BITCOIN_UTIL_OVERLOADED_H diff --git a/src/util/readwritefile.h b/src/util/readwritefile.h index 1dab874b38..a59d0be131 100644 --- a/src/util/readwritefile.h +++ b/src/util/readwritefile.h @@ -25,4 +25,4 @@ std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxs */ bool WriteBinaryFile(const fs::path &filename, const std::string &data); -#endif /* BITCOIN_UTIL_READWRITEFILE_H */ +#endif // BITCOIN_UTIL_READWRITEFILE_H diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 15bd07b374..430f1963ea 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -11,6 +11,7 @@ #include <algorithm> #include <cstdlib> #include <cstring> +#include <limits> #include <optional> static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @@ -138,11 +139,6 @@ std::string EncodeBase64(Span<const unsigned char> input) return str; } -std::string EncodeBase64(const std::string& str) -{ - return EncodeBase64(MakeUCharSpan(str)); -} - std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid) { static const int decode64_table[256] = @@ -526,3 +522,48 @@ std::string HexStr(const Span<const uint8_t> s) assert(it == rv.end()); return rv; } + +std::optional<uint64_t> ParseByteUnits(const std::string& str, ByteUnit default_multiplier) +{ + if (str.empty()) { + return std::nullopt; + } + auto multiplier = default_multiplier; + char unit = str.back(); + switch (unit) { + case 'k': + multiplier = ByteUnit::k; + break; + case 'K': + multiplier = ByteUnit::K; + break; + case 'm': + multiplier = ByteUnit::m; + break; + case 'M': + multiplier = ByteUnit::M; + break; + case 'g': + multiplier = ByteUnit::g; + break; + case 'G': + multiplier = ByteUnit::G; + break; + case 't': + multiplier = ByteUnit::t; + break; + case 'T': + multiplier = ByteUnit::T; + break; + default: + unit = 0; + break; + } + + uint64_t unit_amount = static_cast<uint64_t>(multiplier); + auto parsed_num = ToIntegral<uint64_t>(unit ? str.substr(0, str.size() - 1) : str); + if (!parsed_num || parsed_num > std::numeric_limits<uint64_t>::max() / unit_amount) { // check overflow + return std::nullopt; + } + return *parsed_num * unit_amount; +} diff --git a/src/util/strencodings.h b/src/util/strencodings.h index eedb5ec2f8..08a5465de1 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -30,6 +30,23 @@ enum SafeChars }; /** + * Used by ParseByteUnits() + * Lowercase base 1000 + * Uppercase base 1024 +*/ +enum class ByteUnit : uint64_t { + NOOP = 1ULL, + k = 1000ULL, + K = 1024ULL, + m = 1'000'000ULL, + M = 1ULL << 20, + g = 1'000'000'000ULL, + G = 1ULL << 30, + t = 1'000'000'000'000ULL, + T = 1ULL << 40, +}; + +/** * Remove unsafe chars. Safe chars chosen to allow simple messages/URLs/email * addresses, but avoid anything even possibly remotely dangerous like & or > * @param[in] str The string to sanitize @@ -50,7 +67,8 @@ bool IsHexNumber(const std::string& str); std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid = nullptr); std::string DecodeBase64(const std::string& str, bool* pf_invalid = nullptr); std::string EncodeBase64(Span<const unsigned char> input); -std::string EncodeBase64(const std::string& str); +inline std::string EncodeBase64(Span<const std::byte> input) { return EncodeBase64(MakeUCharSpan(input)); } +inline std::string EncodeBase64(const std::string& str) { return EncodeBase64(MakeUCharSpan(str)); } std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid = nullptr); std::string DecodeBase32(const std::string& str, bool* pf_invalid = nullptr); @@ -189,6 +207,7 @@ std::optional<T> ToIntegral(const std::string& str) */ std::string HexStr(const Span<const uint8_t> s); inline std::string HexStr(const Span<const char> s) { return HexStr(MakeUCharSpan(s)); } +inline std::string HexStr(const Span<const std::byte> s) { return HexStr(MakeUCharSpan(s)); } /** * Format a paragraph of text to a fixed width, adding spaces for @@ -305,4 +324,17 @@ std::string ToUpper(const std::string& str); */ std::string Capitalize(std::string str); +/** + * Parse a string with suffix unit [k|K|m|M|g|G|t|T]. + * Must be a whole integer, fractions not allowed (0.5t), no whitespace or +- + * Lowercase units are 1000 base. Uppercase units are 1024 base. + * Examples: 2m,27M,19g,41T + * + * @param[in] str the string to convert into bytes + * @param[in] default_multiplier if no unit is found in str use this unit + * @returns optional uint64_t bytes from str or nullopt + * if ToIntegral is false, str is empty, trailing whitespace or overflow + */ +std::optional<uint64_t> ParseByteUnits(const std::string& str, ByteUnit default_multiplier); + #endif // BITCOIN_UTIL_STRENCODINGS_H diff --git a/src/util/string.h b/src/util/string.h index 5617e4acc1..07c87cfcda 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -103,4 +103,4 @@ template <typename T1, size_t PREFIX_LEN> std::equal(std::begin(prefix), std::end(prefix), std::begin(obj)); } -#endif // BITCOIN_UTIL_STRENCODINGS_H +#endif // BITCOIN_UTIL_STRING_H diff --git a/src/util/syscall_sandbox.cpp b/src/util/syscall_sandbox.cpp index bc69df44f4..6e1cc9b457 100644 --- a/src/util/syscall_sandbox.cpp +++ b/src/util/syscall_sandbox.cpp @@ -581,7 +581,7 @@ public: allowed_syscalls.insert(__NR_fdatasync); // synchronize a file's in-core state with storage device allowed_syscalls.insert(__NR_flock); // apply or remove an advisory lock on an open file allowed_syscalls.insert(__NR_fstat); // get file status - allowed_syscalls.insert(__NR_newfstatat); // get file status + allowed_syscalls.insert(__NR_fstatfs); // get file system status allowed_syscalls.insert(__NR_fsync); // synchronize a file's in-core state with storage device allowed_syscalls.insert(__NR_ftruncate); // truncate a file to a specified length allowed_syscalls.insert(__NR_getcwd); // get current working directory @@ -589,6 +589,7 @@ public: allowed_syscalls.insert(__NR_getdents64); // get directory entries allowed_syscalls.insert(__NR_lstat); // get file status allowed_syscalls.insert(__NR_mkdir); // create a directory + allowed_syscalls.insert(__NR_newfstatat); // get file status allowed_syscalls.insert(__NR_open); // open and possibly create a file allowed_syscalls.insert(__NR_openat); // open and possibly create a file allowed_syscalls.insert(__NR_readlink); // read value of a symbolic link diff --git a/src/util/trace.h b/src/util/trace.h index 9c92cb10e7..bb901e05da 100644 --- a/src/util/trace.h +++ b/src/util/trace.h @@ -42,4 +42,4 @@ #endif -#endif /* BITCOIN_UTIL_TRACE_H */ +#endif // BITCOIN_UTIL_TRACE_H diff --git a/src/validation.cpp b/src/validation.cpp index f163130a18..881b0abc74 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -723,8 +723,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) } // Check for non-standard pay-to-script-hash in inputs - const bool taproot_active = DeploymentActiveAfter(m_active_chainstate.m_chain.Tip(), args.m_chainparams.GetConsensus(), Consensus::DEPLOYMENT_TAPROOT); - if (fRequireStandard && !AreInputsStandard(tx, m_view, taproot_active)) { + if (fRequireStandard && !AreInputsStandard(tx, m_view)) { return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs"); } diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 7a5526a4cb..4ff049170e 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -43,9 +43,9 @@ const WalletInitInterface& g_wallet_init_interface = WalletInit(); void WalletInit::AddWalletOptions(ArgsManager& argsman) const { - argsman.AddArg("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")", FormatOutputType(DEFAULT_ADDRESS_TYPE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + argsman.AddArg("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", \"bech32\", or \"bech32m\", default: \"%s\")", FormatOutputType(DEFAULT_ADDRESS_TYPE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting many (possibly all) or none, instead of selecting on a per-output basis. Privacy is improved as addresses are mostly swept with fewer transactions and outputs are aggregated in clean change addresses. It may result in higher fees due to less optimal coin selection caused by this added limitation and possibly a larger-than-necessary number of inputs being used. Always enabled for wallets with \"avoid_reuse\" enabled, otherwise default: %u.", DEFAULT_AVOIDPARTIALSPENDS), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); - argsman.AddArg("-changetype", "What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\"). Default is same as -addresstype, except when -addresstype=p2sh-segwit a native segwit output is used when sending to a native segwit address)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + argsman.AddArg("-changetype", "What type of change to use (\"legacy\", \"p2sh-segwit\", \"bech32\", or \"bech32m\"). Default is same as -addresstype, except when -addresstype=p2sh-segwit a native segwit output is used when sending to a native segwit address)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-consolidatefeerate=<amt>", strprintf("The maximum feerate (in %s/kvB) at which transaction building may use more inputs than strictly necessary so that the wallet's UTXO pool can be reduced (default: %s).", CURRENCY_UNIT, FormatMoney(DEFAULT_CONSOLIDATE_FEERATE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-discardfee=<amt>", strprintf("The fee rate (in %s/kvB) that indicates your tolerance for discarding change by adding it to the fee (default: %s). " diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 57f1a6a67a..6489e2a5fc 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -82,7 +82,10 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx) { WalletTxStatus result; - result.block_height = wtx.m_confirm.block_height > 0 ? wtx.m_confirm.block_height : std::numeric_limits<int>::max(); + result.block_height = + wtx.state<TxStateConfirmed>() ? wtx.state<TxStateConfirmed>()->confirmed_block_height : + wtx.state<TxStateConflicted>() ? wtx.state<TxStateConflicted>()->conflicting_block_height : + std::numeric_limits<int>::max(); result.blocks_to_maturity = wallet.GetTxBlocksToMaturity(wtx); result.depth_in_main_chain = wallet.GetTxDepthInMainChain(wtx); result.time_received = wtx.nTimeReceived; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 403c978680..ece75dc43f 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -369,11 +369,9 @@ RPCHelpMan importprunedfunds() unsigned int txnIndex = vIndex[it - vMatch.begin()]; - CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, merkleBlock.header.GetHash(), txnIndex); - CTransactionRef tx_ref = MakeTransactionRef(tx); if (pwallet->IsMine(*tx_ref)) { - pwallet->AddToWallet(std::move(tx_ref), confirm); + pwallet->AddToWallet(std::move(tx_ref), TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)}); return NullUniValue; } @@ -1548,18 +1546,6 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c } } - // Taproot descriptors cannot be imported if Taproot is not yet active. - // Check if this is a Taproot descriptor - CTxDestination dest; - ExtractDestination(scripts[0], dest); - if (std::holds_alternative<WitnessV1Taproot>(dest)) { - // Check if Taproot is active - if (!wallet.chain().isTaprootActive()) { - // Taproot is not active, raise an error - throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import tr() descriptor when Taproot is not active"); - } - } - // If private keys are enabled, check some things. if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (keys.keys.empty()) { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index babb61b03a..c895d5af8c 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -166,13 +166,13 @@ static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue entry.pushKV("confirmations", confirms); if (wtx.IsCoinBase()) entry.pushKV("generated", true); - if (confirms > 0) + if (auto* conf = wtx.state<TxStateConfirmed>()) { - entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex()); - entry.pushKV("blockheight", wtx.m_confirm.block_height); - entry.pushKV("blockindex", wtx.m_confirm.nIndex); + entry.pushKV("blockhash", conf->confirmed_block_hash.GetHex()); + entry.pushKV("blockheight", conf->confirmed_block_height); + entry.pushKV("blockindex", conf->position_in_block); int64_t block_time; - CHECK_NONFATAL(chain.findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(block_time))); + CHECK_NONFATAL(chain.findBlock(conf->confirmed_block_hash, FoundBlock().time(block_time))); entry.pushKV("blocktime", block_time); } else { entry.pushKV("trusted", CachedTxIsTrusted(wallet, wtx)); @@ -2522,7 +2522,6 @@ static RPCHelpMan getwalletinfo() size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); const auto bal = GetBalance(*pwallet); - int64_t kp_oldest = pwallet->GetOldestKeyPoolTime(); obj.pushKV("walletname", pwallet->GetName()); obj.pushKV("walletversion", pwallet->GetVersion()); obj.pushKV("format", pwallet->GetDatabase().Format()); @@ -2530,8 +2529,9 @@ static RPCHelpMan getwalletinfo() obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending)); obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature)); obj.pushKV("txcount", (int)pwallet->mapWallet.size()); - if (kp_oldest > 0) { - obj.pushKV("keypoololdest", kp_oldest); + const auto kp_oldest = pwallet->GetOldestKeyPoolTime(); + if (kp_oldest.has_value()) { + obj.pushKV("keypoololdest", kp_oldest.value()); } obj.pushKV("keypoolsize", (int64_t)kpExternalSize); diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index 3a85fc0c64..40eb49cf87 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -38,4 +38,4 @@ const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wal RPCHelpMan getaddressinfo(); RPCHelpMan signrawtransactionwithwallet(); -#endif //BITCOIN_WALLET_RPCWALLET_H +#endif // BITCOIN_WALLET_RPCWALLET_H diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 9173c790d4..0b4632bd99 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -528,7 +528,7 @@ static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, Walle return keypool.nTime; } -int64_t LegacyScriptPubKeyMan::GetOldestKeyPoolTime() const +std::optional<int64_t> LegacyScriptPubKeyMan::GetOldestKeyPoolTime() const { LOCK(cs_KeyStore); @@ -1876,12 +1876,6 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type, bool internal) { - if (addr_type == OutputType::BECH32M) { - // Don't allow setting up taproot descriptors yet - // TODO: Allow setting up taproot descriptors - return false; - } - LOCK(cs_desc_man); assert(m_storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); @@ -1911,7 +1905,10 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_ desc_prefix = "wpkh(" + xpub + "/84'"; break; } - case OutputType::BECH32M: assert(false); // TODO: Setup taproot descriptor + case OutputType::BECH32M: { + desc_prefix = "tr(" + xpub + "/86'"; + break; + } } // no default case, so the compiler can warn about missing cases assert(!desc_prefix.empty()); @@ -1970,11 +1967,10 @@ bool DescriptorScriptPubKeyMan::HavePrivateKeys() const return m_map_keys.size() > 0 || m_map_crypted_keys.size() > 0; } -int64_t DescriptorScriptPubKeyMan::GetOldestKeyPoolTime() const +std::optional<int64_t> DescriptorScriptPubKeyMan::GetOldestKeyPoolTime() const { // This is only used for getwalletinfo output and isn't relevant to descriptor wallets. - // The magic number 0 indicates that it shouldn't be displayed so that's what we return. - return 0; + return std::nullopt; } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index ef74638751..2d447f1d67 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -19,6 +19,7 @@ #include <boost/signals2/signal.hpp> +#include <optional> #include <unordered_map> enum class OutputType; @@ -203,7 +204,7 @@ public: //! The action to do when the DB needs rewrite virtual void RewriteDB() {} - virtual int64_t GetOldestKeyPoolTime() const { return GetTime(); } + virtual std::optional<int64_t> GetOldestKeyPoolTime() const { return GetTime(); } virtual unsigned int GetKeyPoolSize() const { return 0; } @@ -371,7 +372,7 @@ public: void RewriteDB() override; - int64_t GetOldestKeyPoolTime() const override; + std::optional<int64_t> GetOldestKeyPoolTime() const override; size_t KeypoolCountExternalKeys() const; unsigned int GetKeyPoolSize() const override; @@ -577,7 +578,7 @@ public: bool HavePrivateKeys() const override; - int64_t GetOldestKeyPoolTime() const override; + std::optional<int64_t> GetOldestKeyPoolTime() const override; unsigned int GetKeyPoolSize() const override; int64_t GetTimeFirstKey() const override; diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index a8a7e12a32..8f985f31ee 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -74,7 +74,7 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount uint256 txid = tx.GetHash(); LOCK(wallet.cs_wallet); - auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)))); + auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{})); assert(ret.second); CWalletTx& wtx = (*ret.first).second; if (fIsFromMe) diff --git a/src/wallet/test/fuzz/notifications.cpp b/src/wallet/test/fuzz/notifications.cpp index e8b49f1220..0601c492cd 100644 --- a/src/wallet/test/fuzz/notifications.cpp +++ b/src/wallet/test/fuzz/notifications.cpp @@ -68,9 +68,6 @@ struct FuzzedWallet { CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider) { auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)}; - if (type == OutputType::BECH32M) { - type = OutputType::BECH32; // TODO: Setup taproot descriptor and remove this line - } CTxDestination dest; bilingual_str error; if (fuzzed_data_provider.ConsumeBool()) { diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index dd24fa2c19..7bc2bb5583 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -34,12 +34,12 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test) CDataStream s_prev_tx1(ParseHex("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000"), SER_NETWORK, PROTOCOL_VERSION); CTransactionRef prev_tx1; s_prev_tx1 >> prev_tx1; - m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx1->GetHash()), std::forward_as_tuple(prev_tx1)); + m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx1->GetHash()), std::forward_as_tuple(prev_tx1, TxStateInactive{})); CDataStream s_prev_tx2(ParseHex("0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000"), SER_NETWORK, PROTOCOL_VERSION); CTransactionRef prev_tx2; s_prev_tx2 >> prev_tx2; - m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx2->GetHash()), std::forward_as_tuple(prev_tx2)); + m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx2->GetHash()), std::forward_as_tuple(prev_tx2, TxStateInactive{})); // Import descriptors for keys and scripts import_descriptor(m_wallet, "sh(multi(2,xprv9s21ZrQH143K2LE7W4Xf3jATf9jECxSb7wj91ZnmY4qEJrS66Qru9RFqq8xbkgT32ya6HqYJweFdJUEDf5Q6JFV7jMiUws7kQfe6Tv4RbfN/0h/0h/0h,xprv9s21ZrQH143K2LE7W4Xf3jATf9jECxSb7wj91ZnmY4qEJrS66Qru9RFqq8xbkgT32ya6HqYJweFdJUEDf5Q6JFV7jMiUws7kQfe6Tv4RbfN/0h/0h/1h))"); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 4499eb5903..9a1398e970 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -330,7 +330,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); - CWalletTx wtx(m_coinbase_txns.back()); + CWalletTx wtx{m_coinbase_txns.back(), TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*position_in_block=*/0}}; LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -338,9 +338,6 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); - CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash(), 0); - wtx.m_confirm = confirm; - // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. BOOST_CHECK_EQUAL(CachedTxGetImmatureCredit(wallet, wtx), 0); @@ -355,7 +352,7 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) static int64_t AddTx(ChainstateManager& chainman, CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) { CMutableTransaction tx; - CWalletTx::Confirmation confirm; + TxState state = TxStateInactive{}; tx.nLockTime = lockTime; SetMockTime(mockTime); CBlockIndex* block = nullptr; @@ -367,13 +364,13 @@ static int64_t AddTx(ChainstateManager& chainman, CWallet& wallet, uint32_t lock block = inserted.first->second; block->nTime = blockTime; block->phashBlock = &hash; - confirm = {CWalletTx::Status::CONFIRMED, block->nHeight, hash, 0}; + state = TxStateConfirmed{hash, block->nHeight, /*position_in_block=*/0}; } - - // If transaction is already in map, to avoid inconsistencies, unconfirmation - // is needed before confirm again with different block. - return wallet.AddToWallet(MakeTransactionRef(tx), confirm, [&](CWalletTx& wtx, bool /* new_tx */) { - wtx.setUnconfirmed(); + return wallet.AddToWallet(MakeTransactionRef(tx), state, [&](CWalletTx& wtx, bool /* new_tx */) { + // Assign wtx.m_state to simplify test and avoid the need to simulate + // reorg events. Without this, AddToWallet asserts false when the same + // transaction is confirmed in different blocks. + wtx.m_state = state; return true; })->nTimeSmart; } @@ -534,8 +531,7 @@ public: wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash()); auto it = wallet->mapWallet.find(tx->GetHash()); BOOST_CHECK(it != wallet->mapWallet.end()); - CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash(), 1); - it->second.m_confirm = confirm; + it->second.m_state = TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*position_in_block=*/1}; return it->second; } diff --git a/src/wallet/test/wallet_transaction_tests.cpp b/src/wallet/test/wallet_transaction_tests.cpp new file mode 100644 index 0000000000..5ef2904f66 --- /dev/null +++ b/src/wallet/test/wallet_transaction_tests.cpp @@ -0,0 +1,24 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <wallet/transaction.h> + +#include <wallet/test/wallet_test_fixture.h> + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(wallet_transaction_tests, WalletTestingSetup) + +BOOST_AUTO_TEST_CASE(roundtrip) +{ + for (uint8_t hash = 0; hash < 5; ++hash) { + for (int index = -2; index < 3; ++index) { + TxState state = TxStateInterpretSerialized(TxStateUnrecognized{uint256{hash}, index}); + BOOST_CHECK_EQUAL(TxStateSerializedBlockHash(state), uint256{hash}); + BOOST_CHECK_EQUAL(TxStateSerializedIndex(state), index); + } + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/transaction.cpp b/src/wallet/transaction.cpp index cf98b516f1..a926c0ecc1 100644 --- a/src/wallet/transaction.cpp +++ b/src/wallet/transaction.cpp @@ -15,7 +15,7 @@ bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const bool CWalletTx::InMempool() const { - return fInMempool; + return state<TxStateInMempool>(); } int64_t CWalletTx::GetTxTime() const diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h index 1ccef31056..52d72cccf3 100644 --- a/src/wallet/transaction.h +++ b/src/wallet/transaction.h @@ -11,12 +11,102 @@ #include <wallet/ismine.h> #include <threadsafety.h> #include <tinyformat.h> +#include <util/overloaded.h> #include <util/strencodings.h> #include <util/string.h> #include <list> +#include <variant> #include <vector> +//! State of transaction confirmed in a block. +struct TxStateConfirmed { + uint256 confirmed_block_hash; + int confirmed_block_height; + int position_in_block; + + explicit TxStateConfirmed(const uint256& block_hash, int height, int index) : confirmed_block_hash(block_hash), confirmed_block_height(height), position_in_block(index) {} +}; + +//! State of transaction added to mempool. +struct TxStateInMempool { +}; + +//! State of rejected transaction that conflicts with a confirmed block. +struct TxStateConflicted { + uint256 conflicting_block_hash; + int conflicting_block_height; + + explicit TxStateConflicted(const uint256& block_hash, int height) : conflicting_block_hash(block_hash), conflicting_block_height(height) {} +}; + +//! State of transaction not confirmed or conflicting with a known block and +//! not in the mempool. May conflict with the mempool, or with an unknown block, +//! or be abandoned, never broadcast, or rejected from the mempool for another +//! reason. +struct TxStateInactive { + bool abandoned; + + explicit TxStateInactive(bool abandoned = false) : abandoned(abandoned) {} +}; + +//! State of transaction loaded in an unrecognized state with unexpected hash or +//! index values. Treated as inactive (with serialized hash and index values +//! preserved) by default, but may enter another state if transaction is added +//! to the mempool, or confirmed, or abandoned, or found conflicting. +struct TxStateUnrecognized { + uint256 block_hash; + int index; + + TxStateUnrecognized(const uint256& block_hash, int index) : block_hash(block_hash), index(index) {} +}; + +//! All possible CWalletTx states +using TxState = std::variant<TxStateConfirmed, TxStateInMempool, TxStateConflicted, TxStateInactive, TxStateUnrecognized>; + +//! Subset of states transaction sync logic is implemented to handle. +using SyncTxState = std::variant<TxStateConfirmed, TxStateInMempool, TxStateInactive>; + +//! Try to interpret deserialized TxStateUnrecognized data as a recognized state. +static inline TxState TxStateInterpretSerialized(TxStateUnrecognized data) +{ + if (data.block_hash == uint256::ZERO) { + if (data.index == 0) return TxStateInactive{}; + } else if (data.block_hash == uint256::ONE) { + if (data.index == -1) return TxStateInactive{/*abandoned=*/true}; + } else if (data.index >= 0) { + return TxStateConfirmed{data.block_hash, /*height=*/-1, data.index}; + } else if (data.index == -1) { + return TxStateConflicted{data.block_hash, /*height=*/-1}; + } + return data; +} + +//! Get TxState serialized block hash. Inverse of TxStateInterpretSerialized. +static inline uint256 TxStateSerializedBlockHash(const TxState& state) +{ + return std::visit(util::Overloaded{ + [](const TxStateInactive& inactive) { return inactive.abandoned ? uint256::ONE : uint256::ZERO; }, + [](const TxStateInMempool& in_mempool) { return uint256::ZERO; }, + [](const TxStateConfirmed& confirmed) { return confirmed.confirmed_block_hash; }, + [](const TxStateConflicted& conflicted) { return conflicted.conflicting_block_hash; }, + [](const TxStateUnrecognized& unrecognized) { return unrecognized.block_hash; } + }, state); +} + +//! Get TxState serialized block index. Inverse of TxStateInterpretSerialized. +static inline int TxStateSerializedIndex(const TxState& state) +{ + return std::visit(util::Overloaded{ + [](const TxStateInactive& inactive) { return inactive.abandoned ? -1 : 0; }, + [](const TxStateInMempool& in_mempool) { return 0; }, + [](const TxStateConfirmed& confirmed) { return confirmed.position_in_block; }, + [](const TxStateConflicted& conflicted) { return -1; }, + [](const TxStateUnrecognized& unrecognized) { return unrecognized.index; } + }, state); +} + + typedef std::map<std::string, std::string> mapValue_t; /** Legacy class used for deserializing vtxPrev for backwards compatibility. @@ -45,12 +135,6 @@ public: */ class CWalletTx { -private: - /** Constant used in hashBlock to indicate tx has been abandoned, only used at - * serialization/deserialization to avoid ambiguity with conflicted. - */ - static constexpr const uint256& ABANDON_HASH = uint256::ONE; - public: /** * Key/value map with information about the transaction. @@ -111,11 +195,9 @@ public: */ mutable bool m_is_cache_empty{true}; mutable bool fChangeCached; - mutable bool fInMempool; mutable CAmount nChangeCached; - CWalletTx(CTransactionRef arg) - : tx(std::move(arg)) + CWalletTx(CTransactionRef tx, const TxState& state) : tx(std::move(tx)), m_state(state) { Init(); } @@ -129,44 +211,12 @@ public: nTimeSmart = 0; fFromMe = false; fChangeCached = false; - fInMempool = false; nChangeCached = 0; nOrderPos = -1; - m_confirm = Confirmation{}; } CTransactionRef tx; - - /** New transactions start as UNCONFIRMED. At BlockConnected, - * they will transition to CONFIRMED. In case of reorg, at BlockDisconnected, - * they roll back to UNCONFIRMED. If we detect a conflicting transaction at - * block connection, we update conflicted tx and its dependencies as CONFLICTED. - * If tx isn't confirmed and outside of mempool, the user may switch it to ABANDONED - * by using the abandontransaction call. This last status may be override by a CONFLICTED - * or CONFIRMED transition. - */ - enum Status { - UNCONFIRMED, - CONFIRMED, - CONFLICTED, - ABANDONED - }; - - /** Confirmation includes tx status and a triplet of {block height/block hash/tx index in block} - * at which tx has been confirmed. All three are set to 0 if tx is unconfirmed or abandoned. - * Meaning of these fields changes with CONFLICTED state where they instead point to block hash - * and block height of the deepest conflicting tx. - */ - struct Confirmation { - Status status; - int block_height; - uint256 hashBlock; - int nIndex; - Confirmation(Status status = UNCONFIRMED, int block_height = 0, uint256 block_hash = uint256(), int block_index = 0) - : status{status}, block_height{block_height}, hashBlock{block_hash}, nIndex{block_index} {} - }; - - Confirmation m_confirm; + TxState m_state; template<typename Stream> void Serialize(Stream& s) const @@ -184,8 +234,8 @@ public: std::vector<uint8_t> dummy_vector1; //!< Used to be vMerkleBranch std::vector<uint8_t> dummy_vector2; //!< Used to be vtxPrev bool dummy_bool = false; //!< Used to be fSpent - uint256 serializedHash = isAbandoned() ? ABANDON_HASH : m_confirm.hashBlock; - int serializedIndex = isAbandoned() || isConflicted() ? -1 : m_confirm.nIndex; + uint256 serializedHash = TxStateSerializedBlockHash(m_state); + int serializedIndex = TxStateSerializedIndex(m_state); s << tx << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool; } @@ -197,24 +247,11 @@ public: std::vector<uint256> dummy_vector1; //!< Used to be vMerkleBranch std::vector<CMerkleTx> dummy_vector2; //!< Used to be vtxPrev bool dummy_bool; //! Used to be fSpent + uint256 serialized_block_hash; int serializedIndex; - s >> tx >> m_confirm.hashBlock >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool; - - /* At serialization/deserialization, an nIndex == -1 means that hashBlock refers to - * the earliest block in the chain we know this or any in-wallet ancestor conflicts - * with. If nIndex == -1 and hashBlock is ABANDON_HASH, it means transaction is abandoned. - * In same context, an nIndex >= 0 refers to a confirmed transaction (if hashBlock set) or - * unconfirmed one. Older clients interpret nIndex == -1 as unconfirmed for backward - * compatibility (pre-commit 9ac63d6). - */ - if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) { - setAbandoned(); - } else if (serializedIndex == -1) { - setConflicted(); - } else if (!m_confirm.hashBlock.IsNull()) { - m_confirm.nIndex = serializedIndex; - setConfirmed(); - } + s >> tx >> serialized_block_hash >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool; + + m_state = TxStateInterpretSerialized({serialized_block_hash, serializedIndex}); const auto it_op = mapValue.find("n"); nOrderPos = (it_op != mapValue.end()) ? LocaleIndependentAtoi<int64_t>(it_op->second) : -1; @@ -250,20 +287,13 @@ public: int64_t GetTxTime() const; - bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; } - void setAbandoned() - { - m_confirm.status = CWalletTx::ABANDONED; - m_confirm.hashBlock = uint256(); - m_confirm.block_height = 0; - m_confirm.nIndex = 0; - } - bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; } - void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; } - bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; } - void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; } - bool isConfirmed() const { return m_confirm.status == CWalletTx::CONFIRMED; } - void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; } + template<typename T> const T* state() const { return std::get_if<T>(&m_state); } + template<typename T> T* state() { return std::get_if<T>(&m_state); } + + bool isAbandoned() const { return state<TxStateInactive>() && state<TxStateInactive>()->abandoned; } + bool isConflicted() const { return state<TxStateConflicted>(); } + bool isUnconfirmed() const { return !isAbandoned() && !isConflicted() && !isConfirmed(); } + bool isConfirmed() const { return state<TxStateConfirmed>(); } const uint256& GetHash() const { return tx->GetHash(); } bool IsCoinBase() const { return tx->IsCoinBase(); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7f60dd6906..96b18ef51a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -99,7 +99,11 @@ static void UpdateWalletSetting(interfaces::Chain& chain, */ static void RefreshMempoolStatus(CWalletTx& tx, interfaces::Chain& chain) { - tx.fInMempool = chain.isInMempool(tx.GetHash()); + if (chain.isInMempool(tx.GetHash())) { + tx.m_state = TxStateInMempool(); + } else if (tx.state<TxStateInMempool>()) { + tx.m_state = TxStateInactive(); + } } bool AddWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet) @@ -885,7 +889,7 @@ bool CWallet::IsSpentKey(const uint256& hash, unsigned int n) const return false; } -CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmation& confirm, const UpdateWalletTxFn& update_wtx, bool fFlushOnClose, bool rescanning_old_block) +CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const UpdateWalletTxFn& update_wtx, bool fFlushOnClose, bool rescanning_old_block) { LOCK(cs_wallet); @@ -906,12 +910,11 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio } // Inserts only if not already there, returns tx inserted or tx found - auto ret = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(tx)); + auto ret = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(tx, state)); CWalletTx& wtx = (*ret.first).second; bool fInsertedNew = ret.second; bool fUpdated = update_wtx && update_wtx(wtx, fInsertedNew); if (fInsertedNew) { - wtx.m_confirm = confirm; wtx.nTimeReceived = chain().getAdjustedTime(); wtx.nOrderPos = IncOrderPosNext(&batch); wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); @@ -921,16 +924,12 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio if (!fInsertedNew) { - if (confirm.status != wtx.m_confirm.status) { - wtx.m_confirm.status = confirm.status; - wtx.m_confirm.nIndex = confirm.nIndex; - wtx.m_confirm.hashBlock = confirm.hashBlock; - wtx.m_confirm.block_height = confirm.block_height; + if (state.index() != wtx.m_state.index()) { + wtx.m_state = state; fUpdated = true; } else { - assert(wtx.m_confirm.nIndex == confirm.nIndex); - assert(wtx.m_confirm.hashBlock == confirm.hashBlock); - assert(wtx.m_confirm.block_height == confirm.block_height); + assert(TxStateSerializedIndex(wtx.m_state) == TxStateSerializedIndex(state)); + assert(TxStateSerializedBlockHash(wtx.m_state) == TxStateSerializedBlockHash(state)); } // If we have a witness-stripped version of this transaction, and we // see a new version with a witness, then we must be upgrading a pre-segwit @@ -964,10 +963,10 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio if (!strCmd.empty()) { boost::replace_all(strCmd, "%s", hash.GetHex()); - if (confirm.status == CWalletTx::Status::CONFIRMED) + if (auto* conf = wtx.state<TxStateConfirmed>()) { - boost::replace_all(strCmd, "%b", confirm.hashBlock.GetHex()); - boost::replace_all(strCmd, "%h", ToString(confirm.block_height)); + boost::replace_all(strCmd, "%b", conf->confirmed_block_hash.GetHex()); + boost::replace_all(strCmd, "%h", ToString(conf->confirmed_block_height)); } else { boost::replace_all(strCmd, "%b", "unconfirmed"); boost::replace_all(strCmd, "%h", "-1"); @@ -990,7 +989,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx) { - const auto& ins = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(nullptr)); + const auto& ins = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(nullptr, TxStateInactive{})); CWalletTx& wtx = ins.first->second; if (!fill_wtx(wtx, ins.second)) { return false; @@ -998,22 +997,21 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx // If wallet doesn't have a chain (e.g wallet-tool), don't bother to update txn. if (HaveChain()) { bool active; - int height; - if (chain().findBlock(wtx.m_confirm.hashBlock, FoundBlock().inActiveChain(active).height(height)) && active) { - // Update cached block height variable since it not stored in the - // serialized transaction. - wtx.m_confirm.block_height = height; - } else if (wtx.isConflicted() || wtx.isConfirmed()) { + auto lookup_block = [&](const uint256& hash, int& height, TxState& state) { // If tx block (or conflicting block) was reorged out of chain // while the wallet was shutdown, change tx status to UNCONFIRMED // and reset block height, hash, and index. ABANDONED tx don't have // associated blocks and don't need to be updated. The case where a // transaction was reorged out while online and then reconfirmed // while offline is covered by the rescan logic. - wtx.setUnconfirmed(); - wtx.m_confirm.hashBlock = uint256(); - wtx.m_confirm.block_height = 0; - wtx.m_confirm.nIndex = 0; + if (!chain().findBlock(hash, FoundBlock().inActiveChain(active).height(height)) || !active) { + state = TxStateInactive{}; + } + }; + if (auto* conf = wtx.state<TxStateConfirmed>()) { + lookup_block(conf->confirmed_block_hash, conf->confirmed_block_height, wtx.m_state); + } else if (auto* conf = wtx.state<TxStateConflicted>()) { + lookup_block(conf->conflicting_block_hash, conf->conflicting_block_height, wtx.m_state); } } if (/* insertion took place */ ins.second) { @@ -1024,27 +1022,27 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx auto it = mapWallet.find(txin.prevout.hash); if (it != mapWallet.end()) { CWalletTx& prevtx = it->second; - if (prevtx.isConflicted()) { - MarkConflicted(prevtx.m_confirm.hashBlock, prevtx.m_confirm.block_height, wtx.GetHash()); + if (auto* prev = prevtx.state<TxStateConflicted>()) { + MarkConflicted(prev->conflicting_block_hash, prev->conflicting_block_height, wtx.GetHash()); } } } return true; } -bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool fUpdate, bool rescanning_old_block) +bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const SyncTxState& state, bool fUpdate, bool rescanning_old_block) { const CTransaction& tx = *ptx; { AssertLockHeld(cs_wallet); - if (!confirm.hashBlock.IsNull()) { + if (auto* conf = std::get_if<TxStateConfirmed>(&state)) { for (const CTxIn& txin : tx.vin) { std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout); while (range.first != range.second) { if (range.first->second != tx.GetHash()) { - WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), confirm.hashBlock.ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); - MarkConflicted(confirm.hashBlock, confirm.block_height, range.first->second); + WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), conf->confirmed_block_hash.ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); + MarkConflicted(conf->confirmed_block_hash, conf->confirmed_block_height, range.first->second); } range.first++; } @@ -1070,7 +1068,8 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Co // Block disconnection override an abandoned tx as unconfirmed // which means user may have to call abandontransaction again - return AddToWallet(MakeTransactionRef(tx), confirm, /* update_wtx= */ nullptr, /* fFlushOnClose= */ false, rescanning_old_block); + TxState tx_state = std::visit([](auto&& s) -> TxState { return s; }, state); + return AddToWallet(MakeTransactionRef(tx), tx_state, /*update_wtx=*/nullptr, /*fFlushOnClose=*/false, rescanning_old_block); } } return false; @@ -1126,7 +1125,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) if (currentconfirm == 0 && !wtx.isAbandoned()) { // If the orig tx was not in block/mempool, none of its spends can be in mempool assert(!wtx.InMempool()); - wtx.setAbandoned(); + wtx.m_state = TxStateInactive{/*abandoned=*/true}; wtx.MarkDirty(); batch.WriteTx(wtx); NotifyTransactionChanged(wtx.GetHash(), CT_UPDATED); @@ -1178,10 +1177,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c if (conflictconfirms < currentconfirm) { // Block is 'more conflicted' than current confirm; update. // Mark transaction as conflicted with this block. - wtx.m_confirm.nIndex = 0; - wtx.m_confirm.hashBlock = hashBlock; - wtx.m_confirm.block_height = conflicting_height; - wtx.setConflicted(); + wtx.m_state = TxStateConflicted{hashBlock, conflicting_height}; wtx.MarkDirty(); batch.WriteTx(wtx); // Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too @@ -1199,9 +1195,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c } } -void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool update_tx, bool rescanning_old_block) +void CWallet::SyncTransaction(const CTransactionRef& ptx, const SyncTxState& state, bool update_tx, bool rescanning_old_block) { - if (!AddToWalletIfInvolvingMe(ptx, confirm, update_tx, rescanning_old_block)) + if (!AddToWalletIfInvolvingMe(ptx, state, update_tx, rescanning_old_block)) return; // Not one of ours // If a transaction changes 'conflicted' state, that changes the balance @@ -1212,7 +1208,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Confirmatio void CWallet::transactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) { LOCK(cs_wallet); - SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /*block_height=*/0, /*block_hash=*/{}, /*block_index=*/0}); + SyncTransaction(tx, TxStateInMempool{}); auto it = mapWallet.find(tx->GetHash()); if (it != mapWallet.end()) { @@ -1253,7 +1249,7 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRe // distinguishing between conflicted and unconfirmed transactions are // imperfect, and could be improved in general, see // https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking - SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /*block_height=*/0, /*block_hash=*/{}, /*block_index=*/0}); + SyncTransaction(tx, TxStateInactive{}); } } @@ -1265,7 +1261,7 @@ void CWallet::blockConnected(const CBlock& block, int height) m_last_block_processed_height = height; m_last_block_processed = block_hash; for (size_t index = 0; index < block.vtx.size(); index++) { - SyncTransaction(block.vtx[index], {CWalletTx::Status::CONFIRMED, height, block_hash, (int)index}); + SyncTransaction(block.vtx[index], TxStateConfirmed{block_hash, height, static_cast<int>(index)}); transactionRemovedFromMempool(block.vtx[index], MemPoolRemovalReason::BLOCK, 0 /* mempool_sequence */); } } @@ -1281,7 +1277,7 @@ void CWallet::blockDisconnected(const CBlock& block, int height) m_last_block_processed_height = height - 1; m_last_block_processed = block.hashPrevBlock; for (const CTransactionRef& ptx : block.vtx) { - SyncTransaction(ptx, {CWalletTx::Status::UNCONFIRMED, /*block_height=*/0, /*block_hash=*/{}, /*block_index=*/0}); + SyncTransaction(ptx, TxStateInactive{}); } } @@ -1645,7 +1641,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc break; } for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { - SyncTransaction(block.vtx[posInBlock], {CWalletTx::Status::CONFIRMED, block_height, block_hash, (int)posInBlock}, fUpdate, /* rescanning_old_block */ true); + SyncTransaction(block.vtx[posInBlock], TxStateConfirmed{block_hash, block_height, static_cast<int>(posInBlock)}, fUpdate, /*rescanning_old_block=*/true); } // scan succeeded, record block as most recent successfully scanned result.last_scanned_block = block_hash; @@ -1720,7 +1716,7 @@ void CWallet::ReacceptWalletTransactions() } } -bool CWallet::SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, std::string& err_string, bool relay) const +bool CWallet::SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const { // Can't relay if wallet is not broadcasting if (!GetBroadcastTransactions()) return false; @@ -1734,17 +1730,17 @@ bool CWallet::SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, std::string& err_ // Submit transaction to mempool for relay WalletLogPrintf("Submitting wtx %s to mempool for relay\n", wtx.GetHash().ToString()); - // We must set fInMempool here - while it will be re-set to true by the + // We must set TxStateInMempool here. Even though it will also be set later by the // entered-mempool callback, if we did not there would be a race where a // user could call sendmoney in a loop and hit spurious out of funds errors // because we think that this newly generated transaction's change is // unavailable as we're not yet aware that it is in the mempool. // - // Irrespective of the failure reason, un-marking fInMempool - // out-of-order is incorrect - it should be unmarked when + // If broadcast fails for any reason, trying to set wtx.m_state here would be incorrect. + // If transaction was previously in the mempool, it should be updated when // TransactionRemovedFromMempool fires. bool ret = chain().broadcastTransaction(wtx.tx, m_default_max_tx_fee, relay, err_string); - wtx.fInMempool |= ret; + if (ret) wtx.m_state = TxStateInMempool{}; return ret; } @@ -1831,7 +1827,8 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const return false; } const CWalletTx& wtx = mi->second; - coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], wtx.m_confirm.block_height, wtx.IsCoinBase()); + int prev_height = wtx.state<TxStateConfirmed>() ? wtx.state<TxStateConfirmed>()->confirmed_block_height : 0; + coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], prev_height, wtx.IsCoinBase()); } std::map<int, bilingual_str> input_errors; return SignTransaction(tx, coins, SIGHASH_DEFAULT, input_errors); @@ -1956,7 +1953,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve // Add tx to wallet, because if it has change it's also ours, // otherwise just for transaction history. - AddToWallet(tx, {}, [&](CWalletTx& wtx, bool new_tx) { + AddToWallet(tx, TxStateInactive{}, [&](CWalletTx& wtx, bool new_tx) { CHECK_NONFATAL(wtx.mapValue.empty()); CHECK_NONFATAL(wtx.vOrderForm.empty()); wtx.mapValue = std::move(mapValue); @@ -1974,7 +1971,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve } // Get the inserted-CWalletTx from mapWallet so that the - // fInMempool flag is cached properly + // wtx cached mempool state is updated correctly CWalletTx& wtx = mapWallet.at(tx->GetHash()); if (!fBroadcastTransactions) { @@ -2169,14 +2166,18 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des return true; } -int64_t CWallet::GetOldestKeyPoolTime() const +std::optional<int64_t> CWallet::GetOldestKeyPoolTime() const { LOCK(cs_wallet); - int64_t oldestKey = std::numeric_limits<int64_t>::max(); + if (m_spk_managers.empty()) { + return std::nullopt; + } + + std::optional<int64_t> oldest_key{std::numeric_limits<int64_t>::max()}; for (const auto& spk_man_pair : m_spk_managers) { - oldestKey = std::min(oldestKey, spk_man_pair.second->GetOldestKeyPoolTime()); + oldest_key = std::min(oldest_key, spk_man_pair.second->GetOldestKeyPoolTime()); } - return oldestKey; + return oldest_key; } void CWallet::MarkDestinationsDirty(const std::set<CTxDestination>& destinations) { @@ -2321,10 +2322,10 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t>& mapKeyBirth) const { mapKeyBirth.clear(); // map in which we'll infer heights of other keys - std::map<CKeyID, const CWalletTx::Confirmation*> mapKeyFirstBlock; - CWalletTx::Confirmation max_confirm; - max_confirm.block_height = GetLastBlockHeight() > 144 ? GetLastBlockHeight() - 144 : 0; // the tip can be reorganized; use a 144-block safety margin - CHECK_NONFATAL(chain().findAncestorByHeight(GetLastBlockHash(), max_confirm.block_height, FoundBlock().hash(max_confirm.hashBlock))); + std::map<CKeyID, const TxStateConfirmed*> mapKeyFirstBlock; + TxStateConfirmed max_confirm{uint256{}, /*height=*/-1, /*index=*/-1}; + max_confirm.confirmed_block_height = GetLastBlockHeight() > 144 ? GetLastBlockHeight() - 144 : 0; // the tip can be reorganized; use a 144-block safety margin + CHECK_NONFATAL(chain().findAncestorByHeight(GetLastBlockHash(), max_confirm.confirmed_block_height, FoundBlock().hash(max_confirm.confirmed_block_hash))); { LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan(); @@ -2352,15 +2353,15 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t>& mapKeyBirth) const { for (const auto& entry : mapWallet) { // iterate over all wallet transactions... const CWalletTx &wtx = entry.second; - if (wtx.m_confirm.status == CWalletTx::CONFIRMED) { + if (auto* conf = wtx.state<TxStateConfirmed>()) { // ... which are already in a block for (const CTxOut &txout : wtx.tx->vout) { // iterate over all their outputs for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *spk_man)) { // ... and all their affected keys auto rit = mapKeyFirstBlock.find(keyid); - if (rit != mapKeyFirstBlock.end() && wtx.m_confirm.block_height < rit->second->block_height) { - rit->second = &wtx.m_confirm; + if (rit != mapKeyFirstBlock.end() && conf->confirmed_block_height < rit->second->confirmed_block_height) { + rit->second = conf; } } } @@ -2371,7 +2372,7 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t>& mapKeyBirth) const { // Extract block timestamps for those keys for (const auto& entry : mapKeyFirstBlock) { int64_t block_time; - CHECK_NONFATAL(chain().findBlock(entry.second->hashBlock, FoundBlock().time(block_time))); + CHECK_NONFATAL(chain().findBlock(entry.second->confirmed_block_hash, FoundBlock().time(block_time))); mapKeyBirth[entry.first] = block_time - TIMESTAMP_WINDOW; // block times can be 2h off } } @@ -2401,11 +2402,18 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t>& mapKeyBirth) const { */ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx, bool rescanning_old_block) const { + std::optional<uint256> block_hash; + if (auto* conf = wtx.state<TxStateConfirmed>()) { + block_hash = conf->confirmed_block_hash; + } else if (auto* conf = wtx.state<TxStateConflicted>()) { + block_hash = conf->conflicting_block_hash; + } + unsigned int nTimeSmart = wtx.nTimeReceived; - if (!wtx.isUnconfirmed() && !wtx.isAbandoned()) { + if (block_hash) { int64_t blocktime; int64_t block_max_time; - if (chain().findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(blocktime).maxTime(block_max_time))) { + if (chain().findBlock(*block_hash, FoundBlock().time(blocktime).maxTime(block_max_time))) { if (rescanning_old_block) { nTimeSmart = block_max_time; } else { @@ -2437,7 +2445,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx, bool rescanning_old nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); } } else { - WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.m_confirm.hashBlock.ToString()); + WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), block_hash->ToString()); } } return nTimeSmart; @@ -2944,9 +2952,13 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn) int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const { AssertLockHeld(cs_wallet); - if (wtx.isUnconfirmed() || wtx.isAbandoned()) return 0; - - return (GetLastBlockHeight() - wtx.m_confirm.block_height + 1) * (wtx.isConflicted() ? -1 : 1); + if (auto* conf = wtx.state<TxStateConfirmed>()) { + return GetLastBlockHeight() - conf->confirmed_block_height + 1; + } else if (auto* conf = wtx.state<TxStateConflicted>()) { + return -1 * (GetLastBlockHeight() - conf->conflicting_block_height + 1); + } else { + return 0; + } } int CWallet::GetTxBlocksToMaturity(const CWalletTx& wtx) const @@ -3164,11 +3176,6 @@ void CWallet::SetupDescriptorScriptPubKeyMans() for (bool internal : {false, true}) { for (OutputType t : OUTPUT_TYPES) { - if (t == OutputType::BECH32M) { - // Skip taproot (bech32m) for now - // TODO: Setup taproot (bech32m) descriptors by default - continue; - } auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this)); if (IsCrypted()) { if (IsLocked()) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 3855ad821d..3390d632e7 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -275,7 +275,7 @@ private: * Should be called with rescanning_old_block set to true, if the transaction is * not discovered in real time, but during a rescan of old blocks. */ - bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Confirmation confirm, bool fUpdate, bool rescanning_old_block) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const SyncTxState& state, bool fUpdate, bool rescanning_old_block) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */ void MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx); @@ -285,7 +285,7 @@ private: void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void SyncTransaction(const CTransactionRef& tx, CWalletTx::Confirmation confirm, bool update_tx = true, bool rescanning_old_block = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void SyncTransaction(const CTransactionRef& tx, const SyncTxState& state, bool update_tx = true, bool rescanning_old_block = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** WalletFlags set on this wallet. */ std::atomic<uint64_t> m_wallet_flags{0}; @@ -508,7 +508,7 @@ public: //! @return true if wtx is changed and needs to be saved to disk, otherwise false using UpdateWalletTxFn = std::function<bool(CWalletTx& wtx, bool new_tx)>; - CWalletTx* AddToWallet(CTransactionRef tx, const CWalletTx::Confirmation& confirm, const UpdateWalletTxFn& update_wtx=nullptr, bool fFlushOnClose=true, bool rescanning_old_block = false); + CWalletTx* AddToWallet(CTransactionRef tx, const TxState& state, const UpdateWalletTxFn& update_wtx=nullptr, bool fFlushOnClose=true, bool rescanning_old_block = false); bool LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void transactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) override; void blockConnected(const CBlock& block, int height) override; @@ -576,7 +576,7 @@ public: void CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm); /** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */ - bool SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, std::string& err_string, bool relay) const; + bool SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const; bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const { @@ -632,7 +632,7 @@ public: size_t KeypoolCountExternalKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool TopUpKeyPool(unsigned int kpSize = 0); - int64_t GetOldestKeyPoolTime() const; + std::optional<int64_t> GetOldestKeyPoolTime() const; std::set<CTxDestination> GetLabelAddresses(const std::string& label) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index c920d4af51..f392649bd9 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -987,7 +987,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWal uint256 hash; ssKey >> hash; vTxHash.push_back(hash); - vWtx.emplace_back(nullptr /* tx */); + vWtx.emplace_back(/*tx=*/nullptr, TxStateInactive{}); ssValue >> vWtx.back(); } } diff --git a/src/warnings.h b/src/warnings.h index c38edb4570..7ab0a93e3f 100644 --- a/src/warnings.h +++ b/src/warnings.h @@ -20,4 +20,4 @@ void SetfLargeWorkInvalidChainFound(bool flag); */ bilingual_str GetWarnings(bool verbose); -#endif // BITCOIN_WARNINGS_H +#endif // BITCOIN_WARNINGS_H diff --git a/src/zmq/zmqrpc.h b/src/zmq/zmqrpc.h index 5a810a16fb..8538adf9d3 100644 --- a/src/zmq/zmqrpc.h +++ b/src/zmq/zmqrpc.h @@ -9,4 +9,4 @@ class CRPCTable; void RegisterZMQRPCCommands(CRPCTable& t); -#endif // BITCOIN_ZMQ_ZMRRPC_H +#endif // BITCOIN_ZMQ_ZMQRPC_H diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index 952401c94c..67cacaa9ce 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -122,10 +122,8 @@ class AssumeValidTest(BitcoinTestFramework): tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE]))) tx.calc_sha256() - block102 = create_block(self.tip, create_coinbase(height), self.block_time) + block102 = create_block(self.tip, create_coinbase(height), self.block_time, txlist=[tx]) self.block_time += 1 - block102.vtx.extend([tx]) - block102.hashMerkleRoot = block102.calc_merkle_root() block102.solve() self.blocks.append(block102) self.tip = block102.sha256 @@ -135,7 +133,6 @@ class AssumeValidTest(BitcoinTestFramework): # Bury the assumed valid block 2100 deep for _ in range(2100): block = create_block(self.tip, create_coinbase(height), self.block_time) - block.nVersion = 4 block.solve() self.blocks.append(block) self.tip = block.sha256 diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py index e9e8db7a16..05d274a9fe 100755 --- a/test/functional/feature_bip68_sequence.py +++ b/test/functional/feature_bip68_sequence.py @@ -388,9 +388,7 @@ class BIP68Test(BitcoinTestFramework): assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, tx3.serialize().hex()) # make a block that violates bip68; ensure that the tip updates - block = create_block(tmpl=self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)) - block.vtx.extend([tx1, tx2, tx3]) - block.hashMerkleRoot = block.calc_merkle_root() + block = create_block(tmpl=self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS), txlist=[tx1, tx2, tx3]) add_witness_commitment(block) block.solve() diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 883e15dda3..a3253763bd 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -1361,11 +1361,10 @@ class FullBlockTest(BitcoinTestFramework): else: coinbase.vout[0].nValue += spend.vout[0].nValue - 1 # all but one satoshi to fees coinbase.rehash() - block = create_block(base_block_hash, coinbase, block_time, version=version) tx = self.create_tx(spend, 0, 1, script) # spend 1 satoshi self.sign_tx(tx, spend) - self.add_transactions_to_block(block, [tx]) - block.hashMerkleRoot = block.calc_merkle_root() + tx.rehash() + block = create_block(base_block_hash, coinbase, block_time, version=version, txlist=[tx]) # Block is created. Find a valid nonce. block.solve() self.tip = block diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index 1702debe17..eb90b2c598 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -120,10 +120,7 @@ class BIP65Test(BitcoinTestFramework): tip = self.nodes[0].getbestblockhash() block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 - block = create_block(int(tip, 16), create_coinbase(CLTV_HEIGHT - 1), block_time) - block.nVersion = 3 - block.vtx.extend(invalid_cltv_txs) - block.hashMerkleRoot = block.calc_merkle_root() + block = create_block(int(tip, 16), create_coinbase(CLTV_HEIGHT - 1), block_time, version=3, txlist=invalid_cltv_txs) block.solve() self.test_cltv_info(is_active=False) # Not active as of current tip and next block does not need to obey rules @@ -134,8 +131,7 @@ class BIP65Test(BitcoinTestFramework): self.log.info("Test that blocks must now be at least version 4") tip = block.sha256 block_time += 1 - block = create_block(tip, create_coinbase(CLTV_HEIGHT), block_time) - block.nVersion = 3 + block = create_block(tip, create_coinbase(CLTV_HEIGHT), block_time, version=3) block.solve() with self.nodes[0].assert_debug_log(expected_msgs=[f'{block.hash}, bad-version(0x00000003)']): diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py index 5f2ba35c71..c200445e81 100755 --- a/test/functional/feature_csv_activation.py +++ b/test/functional/feature_csv_activation.py @@ -173,10 +173,7 @@ class BIP68_112_113Test(BitcoinTestFramework): return test_blocks def create_test_block(self, txs): - block = create_block(self.tip, create_coinbase(self.tipheight + 1), self.last_block_time + 600) - block.nVersion = 4 - block.vtx.extend(txs) - block.hashMerkleRoot = block.calc_merkle_root() + block = create_block(self.tip, create_coinbase(self.tipheight + 1), self.last_block_time + 600, txlist=txs) block.solve() return block diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index 8a71bf480e..b7cb32c842 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -85,9 +85,7 @@ class BIP66Test(BitcoinTestFramework): tip = self.nodes[0].getbestblockhash() block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 - block = create_block(int(tip, 16), create_coinbase(DERSIG_HEIGHT - 1), block_time) - block.vtx.append(spendtx) - block.hashMerkleRoot = block.calc_merkle_root() + block = create_block(int(tip, 16), create_coinbase(DERSIG_HEIGHT - 1), block_time, txlist=[spendtx]) block.solve() assert_equal(self.nodes[0].getblockcount(), DERSIG_HEIGHT - 2) @@ -100,8 +98,7 @@ class BIP66Test(BitcoinTestFramework): self.log.info("Test that blocks must now be at least version 3") tip = block.sha256 block_time += 1 - block = create_block(tip, create_coinbase(DERSIG_HEIGHT), block_time) - block.nVersion = 2 + block = create_block(tip, create_coinbase(DERSIG_HEIGHT), block_time, version=2) block.solve() with self.nodes[0].assert_debug_log(expected_msgs=[f'{block.hash}, bad-version(0x00000002)']): diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py index 4d59d5a07d..7a84098a83 100755 --- a/test/functional/feature_nulldummy.py +++ b/test/functional/feature_nulldummy.py @@ -22,7 +22,10 @@ from test_framework.blocktools import ( create_transaction, ) from test_framework.messages import CTransaction -from test_framework.script import CScript +from test_framework.script import ( + OP_0, + OP_TRUE, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -32,16 +35,11 @@ from test_framework.util import ( NULLDUMMY_ERROR = "non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero)" -def trueDummy(tx): - scriptSig = CScript(tx.vin[0].scriptSig) - newscript = [] - for i in scriptSig: - if len(newscript) == 0: - assert len(i) == 0 - newscript.append(b'\x51') - else: - newscript.append(i) - tx.vin[0].scriptSig = CScript(newscript) +def invalidate_nulldummy_tx(tx): + """Transform a NULLDUMMY compliant tx (i.e. scriptSig starts with OP_0) + to be non-NULLDUMMY compliant by replacing the dummy with OP_TRUE""" + assert_equal(tx.vin[0].scriptSig[0], OP_0) + tx.vin[0].scriptSig = bytes([OP_TRUE]) + tx.vin[0].scriptSig[1:] tx.rehash() @@ -94,7 +92,7 @@ class NULLDUMMYTest(BitcoinTestFramework): self.log.info("Test 2: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation") test2tx = create_transaction(self.nodes[0], txid2, self.ms_address, amount=47) - trueDummy(test2tx) + invalidate_nulldummy_tx(test2tx) assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test2tx.serialize_with_witness().hex(), 0) self.log.info(f"Test 3: Non-NULLDUMMY base transactions should be accepted in a block before activation [{COINBASE_MATURITY + 4}]") @@ -103,7 +101,7 @@ class NULLDUMMYTest(BitcoinTestFramework): self.log.info("Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation") test4tx = create_transaction(self.nodes[0], test2tx.hash, self.address, amount=46) test6txs = [CTransaction(test4tx)] - trueDummy(test4tx) + invalidate_nulldummy_tx(test4tx) assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test4tx.serialize_with_witness().hex(), 0) self.block_submit(self.nodes[0], [test4tx], accept=False) @@ -123,11 +121,7 @@ class NULLDUMMYTest(BitcoinTestFramework): tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) assert_equal(tmpl['previousblockhash'], self.lastblockhash) assert_equal(tmpl['height'], self.lastblockheight + 1) - block = create_block(tmpl=tmpl, ntime=self.lastblocktime + 1) - for tx in txs: - tx.rehash() - block.vtx.append(tx) - block.hashMerkleRoot = block.calc_merkle_root() + block = create_block(tmpl=tmpl, ntime=self.lastblocktime + 1, txlist=txs) if with_witness: add_witness_commitment(block) block.solve() diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index 9f00240a47..e9da6edaf6 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -98,6 +98,7 @@ from test_framework.address import ( program_to_witness ) from collections import OrderedDict, namedtuple +from enum import Enum from io import BytesIO import json import hashlib @@ -456,7 +457,7 @@ def spend(tx, idx, utxos, **kwargs): # Each spender is a tuple of: # - A scriptPubKey which is to be spent from (CScript) # - A comment describing the test (string) -# - Whether the spending (on itself) is expected to be standard (bool) +# - Whether the spending (on itself) is expected to be standard (Enum.Standard) # - A tx-signing lambda returning (scriptsig, witness_stack), taking as inputs: # - A transaction to sign (CTransaction) # - An input position (int) @@ -468,8 +469,14 @@ def spend(tx, idx, utxos, **kwargs): # - Whether this test demands being placed in a txin with no corresponding txout (for testing SIGHASH_SINGLE behavior) Spender = namedtuple("Spender", "script,comment,is_standard,sat_function,err_msg,sigops_weight,no_fail,need_vin_vout_mismatch") +# The full node versions that treat the tx standard. +# ALL means any version +# V23 means the major version 23.0 and any later version +# NONE means no version +Standard = Enum('Standard', 'ALL V23 NONE') -def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=False, spk_mutate_pre_p2sh=None, failure=None, standard=True, err_msg=None, sigops_weight=0, need_vin_vout_mismatch=False, **kwargs): + +def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=False, spk_mutate_pre_p2sh=None, failure=None, standard=Standard.ALL, err_msg=None, sigops_weight=0, need_vin_vout_mismatch=False, **kwargs): """Helper for constructing Spender objects using the context signing framework. * tap: a TaprootInfo object (see taproot_construct), for Taproot spends (cannot be combined with pkh, witv0, or script) @@ -479,13 +486,18 @@ def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh= * p2sh: whether the output is P2SH wrapper (this is supported even for Taproot, where it makes the output unencumbered) * spk_mutate_pre_psh: a callable to be applied to the script (before potentially P2SH-wrapping it) * failure: a dict of entries to override in the context when intentionally failing to spend (if None, no_fail will be set) - * standard: whether the (valid version of) spending is expected to be standard + * standard: whether the (valid version of) spending is expected to be standard (True is mapped to Standard.ALL, False is mapped to Standard.NONE) * err_msg: a string with an expected error message for failure (or None, if not cared about) * sigops_weight: the pre-taproot sigops weight consumed by a successful spend * need_vin_vout_mismatch: whether this test requires being tested in a transaction input that has no corresponding transaction output. """ + if standard == True: + standard = Standard.ALL + elif standard == False: + standard = Standard.NONE + conf = dict() # Compute scriptPubKey and set useful defaults based on the inputs. @@ -1170,12 +1182,12 @@ def spenders_taproot_inactive(): tap = taproot_construct(pub, scripts) # Test that keypath spending is valid & non-standard, regardless of validity. - add_spender(spenders, "inactive/keypath_valid", key=sec, tap=tap, standard=False) + add_spender(spenders, "inactive/keypath_valid", key=sec, tap=tap, standard=Standard.V23) add_spender(spenders, "inactive/keypath_invalidsig", key=sec, tap=tap, standard=False, sighash=bitflipper(default_sighash)) add_spender(spenders, "inactive/keypath_empty", key=sec, tap=tap, standard=False, witness=[]) # Same for scriptpath spending (and features like annex, leaf versions, or OP_SUCCESS don't change this) - add_spender(spenders, "inactive/scriptpath_valid", key=sec, tap=tap, leaf="pk", standard=False, inputs=[getter("sign")]) + add_spender(spenders, "inactive/scriptpath_valid", key=sec, tap=tap, leaf="pk", standard=Standard.V23, inputs=[getter("sign")]) add_spender(spenders, "inactive/scriptpath_invalidsig", key=sec, tap=tap, leaf="pk", standard=False, inputs=[getter("sign")], sighash=bitflipper(default_sighash)) add_spender(spenders, "inactive/scriptpath_invalidcb", key=sec, tap=tap, leaf="pk", standard=False, inputs=[getter("sign")], controlblock=bitflipper(default_controlblock)) add_spender(spenders, "inactive/scriptpath_valid_unkleaf", key=sec, tap=tap, leaf="future_leaf", standard=False, inputs=[getter("sign")]) @@ -1205,7 +1217,7 @@ def dump_json_test(tx, input_utxos, idx, success, failure): # The "final" field indicates that a spend should be always valid, even with more validation flags enabled # than the listed ones. Use standardness as a proxy for this (which gives a conservative underestimate). - if spender.is_standard: + if spender.is_standard == Standard.ALL: fields.append(("final", True)) def dump_witness(wit): @@ -1266,12 +1278,8 @@ class TaprootTest(BitcoinTestFramework): # transactions. extra_output_script = CScript([OP_CHECKSIG]*((MAX_BLOCK_SIGOPS_WEIGHT - sigops_weight) // WITNESS_SCALE_FACTOR)) - block = create_block(self.tip, create_coinbase(self.lastblockheight + 1, pubkey=cb_pubkey, extra_output_script=extra_output_script, fees=fees), self.lastblocktime + 1) - block.nVersion = 4 - for tx in txs: - tx.rehash() - block.vtx.append(tx) - block.hashMerkleRoot = block.calc_merkle_root() + coinbase_tx = create_coinbase(self.lastblockheight + 1, pubkey=cb_pubkey, extra_output_script=extra_output_script, fees=fees) + block = create_block(self.tip, coinbase_tx, self.lastblocktime + 1, txlist=txs) witness and add_witness_commitment(block) block.solve() block_response = node.submitblock(block.serialize().hex()) @@ -1474,8 +1482,13 @@ class TaprootTest(BitcoinTestFramework): for i in range(len(input_utxos)): tx.vin[i].scriptSig = input_data[i][i != fail_input][0] tx.wit.vtxinwit[i].scriptWitness.stack = input_data[i][i != fail_input][1] + taproot_spend_policy = Standard.V23 if node.version is None else Standard.ALL # Submit to mempool to check standardness - is_standard_tx = fail_input is None and all(utxo.spender.is_standard for utxo in input_utxos) and tx.nVersion >= 1 and tx.nVersion <= 2 + is_standard_tx = ( + fail_input is None # Must be valid to be standard + and (all(utxo.spender.is_standard == Standard.ALL or utxo.spender.is_standard == taproot_spend_policy for utxo in input_utxos)) # All inputs must be standard + and tx.nVersion >= 1 # The tx version must be standard + and tx.nVersion <= 2) tx.rehash() msg = ','.join(utxo.spender.comment + ("*" if n == fail_input else "") for n, utxo in enumerate(input_utxos)) if is_standard_tx: diff --git a/test/functional/feature_versionbits_warning.py b/test/functional/feature_versionbits_warning.py index fb4fa61a1c..e83dd7f446 100755 --- a/test/functional/feature_versionbits_warning.py +++ b/test/functional/feature_versionbits_warning.py @@ -48,8 +48,7 @@ class VersionBitsWarningTest(BitcoinTestFramework): tip = int(tip, 16) for _ in range(numblocks): - block = create_block(tip, create_coinbase(height + 1), block_time) - block.nVersion = version + block = create_block(tip, create_coinbase(height + 1), block_time, version=version) block.solve() peer.send_message(msg_block(block)) block_time += 1 diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py index 395fde18fd..1ee12c0040 100755 --- a/test/functional/interface_zmq.py +++ b/test/functional/interface_zmq.py @@ -18,7 +18,6 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import ( CTransaction, hash256, - tx_from_hex, ) from test_framework.util import ( assert_equal, @@ -402,12 +401,8 @@ class ZMQTest (BitcoinTestFramework): raw_tx = self.nodes[0].getrawtransaction(orig_txid) bump_info = self.nodes[0].bumpfee(orig_txid) # Mine the pre-bump tx - block = create_block(int(self.nodes[0].getbestblockhash(), 16), create_coinbase(self.nodes[0].getblockcount()+1)) - tx = tx_from_hex(raw_tx) - block.vtx.append(tx) - for txid in more_tx: - tx = tx_from_hex(self.nodes[0].getrawtransaction(txid)) - block.vtx.append(tx) + txs_to_add = [raw_tx] + [self.nodes[0].getrawtransaction(txid) for txid in more_tx] + block = create_block(int(self.nodes[0].getbestblockhash(), 16), create_coinbase(self.nodes[0].getblockcount()+1), txlist=txs_to_add) add_witness_commitment(block) block.solve() assert_equal(self.nodes[0].submitblock(block.serialize().hex()), None) diff --git a/test/functional/p2p_invalid_block.py b/test/functional/p2p_invalid_block.py index 4baf407f89..0dfa25174b 100755 --- a/test/functional/p2p_invalid_block.py +++ b/test/functional/p2p_invalid_block.py @@ -15,9 +15,14 @@ becomes valid. import copy import time -from test_framework.blocktools import create_block, create_coinbase, create_tx_with_script +from test_framework.blocktools import ( + create_block, + create_coinbase, + create_tx_with_script, +) from test_framework.messages import COIN from test_framework.p2p import P2PDataStore +from test_framework.script import OP_TRUE from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal @@ -66,15 +71,10 @@ class InvalidBlockRequestTest(BitcoinTestFramework): # For more information on merkle-root malleability see src/consensus/merkle.cpp. self.log.info("Test merkle root malleability.") - block2 = create_block(tip, create_coinbase(height), block_time) + tx1 = create_tx_with_script(block1.vtx[0], 0, script_sig=bytes([OP_TRUE]), amount=50 * COIN) + tx2 = create_tx_with_script(tx1, 0, script_sig=bytes([OP_TRUE]), amount=50 * COIN) + block2 = create_block(tip, create_coinbase(height), block_time, txlist=[tx1, tx2]) block_time += 1 - - # b'0x51' is OP_TRUE - tx1 = create_tx_with_script(block1.vtx[0], 0, script_sig=b'\x51', amount=50 * COIN) - tx2 = create_tx_with_script(tx1, 0, script_sig=b'\x51', amount=50 * COIN) - - block2.vtx.extend([tx1, tx2]) - block2.hashMerkleRoot = block2.calc_merkle_root() block2.solve() orig_hash = block2.sha256 block2_orig = copy.deepcopy(block2) @@ -99,12 +99,8 @@ class InvalidBlockRequestTest(BitcoinTestFramework): self.log.info("Test very broken block.") - block3 = create_block(tip, create_coinbase(height), block_time) + block3 = create_block(tip, create_coinbase(height, nValue=100), block_time) block_time += 1 - block3.vtx[0].vout[0].nValue = 100 * COIN # Too high! - block3.vtx[0].sha256 = None - block3.vtx[0].calc_sha256() - block3.hashMerkleRoot = block3.calc_merkle_root() block3.solve() peer.send_blocks_and_test([block3], node, success=False, reject_reason='bad-cb-amount') @@ -123,14 +119,10 @@ class InvalidBlockRequestTest(BitcoinTestFramework): # Complete testing of CVE-2018-17144, by checking for the inflation bug. # Create a block that spends the output of a tx in a previous block. - block4 = create_block(tip, create_coinbase(height), block_time) - tx3 = create_tx_with_script(tx2, 0, script_sig=b'\x51', amount=50 * COIN) - - # Duplicates input - tx3.vin.append(tx3.vin[0]) + tx3 = create_tx_with_script(tx2, 0, script_sig=bytes([OP_TRUE]), amount=50 * COIN) + tx3.vin.append(tx3.vin[0]) # Duplicates input tx3.rehash() - block4.vtx.append(tx3) - block4.hashMerkleRoot = block4.calc_merkle_root() + block4 = create_block(tip, create_coinbase(height), block_time, txlist=[tx3]) block4.solve() self.log.info("Test inflation by duplicating input") peer.send_blocks_and_test([block4], node, success=False, reject_reason='bad-txns-inputs-duplicate') @@ -140,7 +132,6 @@ class InvalidBlockRequestTest(BitcoinTestFramework): node.setmocktime(t) # Set block time +1 second past max future validity block = create_block(tip, create_coinbase(height), t + MAX_FUTURE_BLOCK_TIME + 1) - block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Need force_send because the block will get rejected without a getdata otherwise peer.send_blocks_and_test([block], node, force_send=True, success=False, reject_reason='time-too-new') diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index 252844ce1f..f377fbaaa6 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -232,7 +232,6 @@ class SegWitTest(BitcoinTestFramework): height = self.nodes[0].getblockcount() + 1 block_time = self.nodes[0].getblockheader(tip)["mediantime"] + 1 block = create_block(int(tip, 16), create_coinbase(height), block_time) - block.nVersion = 4 block.rehash() return block diff --git a/test/functional/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py index 3b02a1c61f..9c4e1dd1b1 100755 --- a/test/functional/p2p_unrequested_blocks.py +++ b/test/functional/p2p_unrequested_blocks.py @@ -225,10 +225,9 @@ class AcceptBlockTest(BitcoinTestFramework): block_289f.solve() block_290f = create_block(block_289f.sha256, create_coinbase(290), block_289f.nTime+1) block_290f.solve() - block_291 = create_block(block_290f.sha256, create_coinbase(291), block_290f.nTime+1) # block_291 spends a coinbase below maturity! - block_291.vtx.append(create_tx_with_script(block_290f.vtx[0], 0, script_sig=b"42", amount=1)) - block_291.hashMerkleRoot = block_291.calc_merkle_root() + tx_to_add = create_tx_with_script(block_290f.vtx[0], 0, script_sig=b"42", amount=1) + block_291 = create_block(block_290f.sha256, create_coinbase(291), block_290f.nTime+1, txlist=[tx_to_add]) block_291.solve() block_292 = create_block(block_291.sha256, create_coinbase(292), block_291.nTime+1) block_292.solve() diff --git a/test/functional/rpc_decodescript.py b/test/functional/rpc_decodescript.py index 58432c3dae..8c0f48129a 100755 --- a/test/functional/rpc_decodescript.py +++ b/test/functional/rpc_decodescript.py @@ -27,29 +27,29 @@ class DecodeScriptTest(BitcoinTestFramework): # below are test cases for all of the standard transaction types - # 1) P2PK scriptSig + self.log.info("- P2PK") # the scriptSig of a public key scriptPubKey simply pushes a signature onto the stack rpc_result = self.nodes[0].decodescript(push_signature) assert_equal(signature, rpc_result['asm']) - # 2) P2PKH scriptSig + self.log.info("- P2PKH") rpc_result = self.nodes[0].decodescript(push_signature + push_public_key) assert_equal(signature + ' ' + public_key, rpc_result['asm']) - # 3) multisig scriptSig + self.log.info("- multisig") # this also tests the leading portion of a P2SH multisig scriptSig # OP_0 <A sig> <B sig> rpc_result = self.nodes[0].decodescript('00' + push_signature + push_signature) assert_equal('0 ' + signature + ' ' + signature, rpc_result['asm']) - # 4) P2SH scriptSig + self.log.info("- P2SH") # an empty P2SH redeemScript is valid and makes for a very simple test case. # thus, such a spending scriptSig would just need to pass the outer redeemScript # hash test and leave true on the top of the stack. rpc_result = self.nodes[0].decodescript('5100') assert_equal('1 0', rpc_result['asm']) - # 5) null data scriptSig - no such thing because null data scripts can not be spent. + # null data scriptSig - no such thing because null data scripts can not be spent. # thus, no test case for that standard transaction type is here. def decodescript_script_pub_key(self): @@ -63,50 +63,58 @@ class DecodeScriptTest(BitcoinTestFramework): # below are test cases for all of the standard transaction types - # 1) P2PK scriptPubKey + self.log.info("- P2PK") # <pubkey> OP_CHECKSIG rpc_result = self.nodes[0].decodescript(push_public_key + 'ac') assert_equal(public_key + ' OP_CHECKSIG', rpc_result['asm']) + assert_equal('pubkey', rpc_result['type']) # P2PK is translated to P2WPKH assert_equal('0 ' + public_key_hash, rpc_result['segwit']['asm']) - # 2) P2PKH scriptPubKey + self.log.info("- P2PKH") # OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG rpc_result = self.nodes[0].decodescript('76a9' + push_public_key_hash + '88ac') + assert_equal('pubkeyhash', rpc_result['type']) assert_equal('OP_DUP OP_HASH160 ' + public_key_hash + ' OP_EQUALVERIFY OP_CHECKSIG', rpc_result['asm']) # P2PKH is translated to P2WPKH + assert_equal('witness_v0_keyhash', rpc_result['segwit']['type']) assert_equal('0 ' + public_key_hash, rpc_result['segwit']['asm']) - # 3) multisig scriptPubKey + self.log.info("- multisig") # <m> <A pubkey> <B pubkey> <C pubkey> <n> OP_CHECKMULTISIG # just imagine that the pub keys used below are different. # for our purposes here it does not matter that they are the same even though it is unrealistic. multisig_script = '52' + push_public_key + push_public_key + push_public_key + '53ae' rpc_result = self.nodes[0].decodescript(multisig_script) + assert_equal('multisig', rpc_result['type']) assert_equal('2 ' + public_key + ' ' + public_key + ' ' + public_key + ' 3 OP_CHECKMULTISIG', rpc_result['asm']) # multisig in P2WSH multisig_script_hash = sha256(bytes.fromhex(multisig_script)).hex() + assert_equal('witness_v0_scripthash', rpc_result['segwit']['type']) assert_equal('0 ' + multisig_script_hash, rpc_result['segwit']['asm']) - # 4) P2SH scriptPubKey + self.log.info ("- P2SH") # OP_HASH160 <Hash160(redeemScript)> OP_EQUAL. # push_public_key_hash here should actually be the hash of a redeem script. # but this works the same for purposes of this test. rpc_result = self.nodes[0].decodescript('a9' + push_public_key_hash + '87') + assert_equal('scripthash', rpc_result['type']) assert_equal('OP_HASH160 ' + public_key_hash + ' OP_EQUAL', rpc_result['asm']) # P2SH does not work in segwit secripts. decodescript should not return a result for it. assert 'segwit' not in rpc_result - # 5) null data scriptPubKey + self.log.info("- null data") # use a signature look-alike here to make sure that we do not decode random data as a signature. # this matters if/when signature sighash decoding comes along. # would want to make sure that no such decoding takes place in this case. signature_imposter = '48304502207fa7a6d1e0ee81132a269ad84e68d695483745cde8b541e3bf630749894e342a022100c1f7ab20e13e22fb95281a870f3dcf38d782e53023ee313d741ad0cfbc0c509001' # OP_RETURN <data> rpc_result = self.nodes[0].decodescript('6a' + signature_imposter) + assert_equal('nulldata', rpc_result['type']) assert_equal('OP_RETURN ' + signature_imposter[2:], rpc_result['asm']) - # 6) a CLTV redeem script. redeem scripts are in-effect scriptPubKey scripts, so adding a test here. + self.log.info("- CLTV redeem script") + # redeem scripts are in-effect scriptPubKey scripts, so adding a test here. # OP_NOP2 is also known as OP_CHECKLOCKTIMEVERIFY. # just imagine that the pub keys used below are different. # for our purposes here it does not matter that they are the same even though it is unrealistic. @@ -121,55 +129,69 @@ class DecodeScriptTest(BitcoinTestFramework): # lock until block 500,000 cltv_script = '63' + push_public_key + 'ad670320a107b17568' + push_public_key + 'ac' rpc_result = self.nodes[0].decodescript(cltv_script) + assert_equal('nonstandard', rpc_result['type']) assert_equal('OP_IF ' + public_key + ' OP_CHECKSIGVERIFY OP_ELSE 500000 OP_CHECKLOCKTIMEVERIFY OP_DROP OP_ENDIF ' + public_key + ' OP_CHECKSIG', rpc_result['asm']) # CLTV script in P2WSH cltv_script_hash = sha256(bytes.fromhex(cltv_script)).hex() assert_equal('0 ' + cltv_script_hash, rpc_result['segwit']['asm']) - # 7) P2PK scriptPubKey + self.log.info("- P2PK with uncompressed pubkey") # <pubkey> OP_CHECKSIG rpc_result = self.nodes[0].decodescript(push_uncompressed_public_key + 'ac') + assert_equal('pubkey', rpc_result['type']) assert_equal(uncompressed_public_key + ' OP_CHECKSIG', rpc_result['asm']) # uncompressed pubkeys are invalid for checksigs in segwit scripts. # decodescript should not return a P2WPKH equivalent. assert 'segwit' not in rpc_result - # 8) multisig scriptPubKey with an uncompressed pubkey + self.log.info("- multisig with uncompressed pubkey") # <m> <A pubkey> <B pubkey> <n> OP_CHECKMULTISIG # just imagine that the pub keys used below are different. # the purpose of this test is to check that a segwit script is not returned for bare multisig scripts # with an uncompressed pubkey in them. rpc_result = self.nodes[0].decodescript('52' + push_public_key + push_uncompressed_public_key +'52ae') + assert_equal('multisig', rpc_result['type']) assert_equal('2 ' + public_key + ' ' + uncompressed_public_key + ' 2 OP_CHECKMULTISIG', rpc_result['asm']) # uncompressed pubkeys are invalid for checksigs in segwit scripts. # decodescript should not return a P2WPKH equivalent. assert 'segwit' not in rpc_result - # 9) P2WPKH scriptpubkey + self.log.info("- P2WPKH") # 0 <PubKeyHash> rpc_result = self.nodes[0].decodescript('00' + push_public_key_hash) + assert_equal('witness_v0_keyhash', rpc_result['type']) assert_equal('0 ' + public_key_hash, rpc_result['asm']) # segwit scripts do not work nested into each other. # a nested segwit script should not be returned in the results. assert 'segwit' not in rpc_result - # 10) P2WSH scriptpubkey + self.log.info("- P2WSH") # 0 <ScriptHash> # even though this hash is of a P2PK script which is better used as bare P2WPKH, it should not matter # for the purpose of this test. rpc_result = self.nodes[0].decodescript('0020' + p2wsh_p2pk_script_hash) + assert_equal('witness_v0_scripthash', rpc_result['type']) assert_equal('0 ' + p2wsh_p2pk_script_hash, rpc_result['asm']) # segwit scripts do not work nested into each other. # a nested segwit script should not be returned in the results. assert 'segwit' not in rpc_result + self.log.info("- P2TR") + # 1 <x-only pubkey> + xonly_public_key = '01'*32 # first ever P2TR output on mainnet + rpc_result = self.nodes[0].decodescript('5120' + xonly_public_key) + assert_equal('witness_v1_taproot', rpc_result['type']) + assert_equal('1 ' + xonly_public_key, rpc_result['asm']) + assert 'segwit' not in rpc_result + def decoderawtransaction_asm_sighashtype(self): """Test decoding scripts via RPC command "decoderawtransaction". This test is in with the "decodescript" tests because they are testing the same "asm" script decodes. """ - # this test case uses a random plain vanilla mainnet transaction with a single P2PKH input and output + self.log.info("- various mainnet txs") + # this test case uses a mainnet transaction that has a P2SH input and both P2PKH and P2SH outputs. tx = '0100000001696a20784a2c70143f634e95227dbdfdf0ecd51647052e70854512235f5986ca010000008a47304402207174775824bec6c2700023309a168231ec80b82c6069282f5133e6f11cbb04460220570edc55c7c5da2ca687ebd0372d3546ebc3f810516a002350cac72dfe192dfb014104d3f898e6487787910a690410b7a917ef198905c27fb9d3b0a42da12aceae0544fc7088d239d9a48f2828a15a09e84043001f27cc80d162cb95404e1210161536ffffffff0100e1f505000000001976a914eb6c6e0cdb2d256a32d97b8df1fc75d1920d9bca88ac00000000' rpc_result = self.nodes[0].decoderawtransaction(tx) assert_equal('304402207174775824bec6c2700023309a168231ec80b82c6069282f5133e6f11cbb04460220570edc55c7c5da2ca687ebd0372d3546ebc3f810516a002350cac72dfe192dfb[ALL] 04d3f898e6487787910a690410b7a917ef198905c27fb9d3b0a42da12aceae0544fc7088d239d9a48f2828a15a09e84043001f27cc80d162cb95404e1210161536', rpc_result['vin'][0]['scriptSig']['asm']) @@ -185,11 +207,13 @@ class DecodeScriptTest(BitcoinTestFramework): assert_equal('OP_HASH160 2a5edea39971049a540474c6a99edf0aa4074c58 OP_EQUAL', rpc_result['vout'][1]['scriptPubKey']['asm']) txSave = tx_from_hex(tx) + self.log.info("- tx not passing DER signature checks") # make sure that a specifically crafted op_return value will not pass all the IsDERSignature checks and then get decoded as a sighash type tx = '01000000015ded05872fdbda629c7d3d02b194763ce3b9b1535ea884e3c8e765d42e316724020000006b48304502204c10d4064885c42638cbff3585915b322de33762598321145ba033fc796971e2022100bb153ad3baa8b757e30a2175bd32852d2e1cb9080f84d7e32fcdfd667934ef1b012103163c0ff73511ea1743fb5b98384a2ff09dd06949488028fd819f4d83f56264efffffffff0200000000000000000b6a0930060201000201000180380100000000001976a9141cabd296e753837c086da7a45a6c2fe0d49d7b7b88ac00000000' rpc_result = self.nodes[0].decoderawtransaction(tx) assert_equal('OP_RETURN 300602010002010001', rpc_result['vout'][0]['scriptPubKey']['asm']) + self.log.info("- tx passing DER signature checks") # verify that we have not altered scriptPubKey processing even of a specially crafted P2PKH pubkeyhash and P2SH redeem script hash that is made to pass the der signature checks tx = '01000000018d1f5635abd06e2c7e2ddf58dc85b3de111e4ad6e0ab51bb0dcf5e84126d927300000000fdfe0000483045022100ae3b4e589dfc9d48cb82d41008dc5fa6a86f94d5c54f9935531924602730ab8002202f88cf464414c4ed9fa11b773c5ee944f66e9b05cc1e51d97abc22ce098937ea01483045022100b44883be035600e9328a01b66c7d8439b74db64187e76b99a68f7893b701d5380220225bf286493e4c4adcf928c40f785422572eb232f84a0b83b0dea823c3a19c75014c695221020743d44be989540d27b1b4bbbcfd17721c337cb6bc9af20eb8a32520b393532f2102c0120a1dda9e51a938d39ddd9fe0ebc45ea97e1d27a7cbd671d5431416d3dd87210213820eb3d5f509d7438c9eeecb4157b2f595105e7cd564b3cdbb9ead3da41eed53aeffffffff02611e0000000000001976a914301102070101010101010102060101010101010188acee2a02000000000017a91430110207010101010101010206010101010101018700000000' rpc_result = self.nodes[0].decoderawtransaction(tx) @@ -207,7 +231,7 @@ class DecodeScriptTest(BitcoinTestFramework): push_signature_2 = '48' + signature_2 signature_2_sighash_decoded = der_signature + '[NONE|ANYONECANPAY]' - # 1) P2PK scriptSig + self.log.info("- P2PK scriptSig") txSave.vin[0].scriptSig = bytes.fromhex(push_signature) rpc_result = self.nodes[0].decoderawtransaction(txSave.serialize().hex()) assert_equal(signature_sighash_decoded, rpc_result['vin'][0]['scriptSig']['asm']) @@ -217,20 +241,23 @@ class DecodeScriptTest(BitcoinTestFramework): rpc_result = self.nodes[0].decoderawtransaction(txSave.serialize().hex()) assert_equal(signature_2_sighash_decoded, rpc_result['vin'][0]['scriptSig']['asm']) - # 2) multisig scriptSig + self.log.info("- multisig scriptSig") txSave.vin[0].scriptSig = bytes.fromhex('00' + push_signature + push_signature_2) rpc_result = self.nodes[0].decoderawtransaction(txSave.serialize().hex()) assert_equal('0 ' + signature_sighash_decoded + ' ' + signature_2_sighash_decoded, rpc_result['vin'][0]['scriptSig']['asm']) - # 3) test a scriptSig that contains more than push operations. + self.log.info("- scriptSig that contains more than push operations") # in fact, it contains an OP_RETURN with data specially crafted to cause improper decode if the code does not catch it. txSave.vin[0].scriptSig = bytes.fromhex('6a143011020701010101010101020601010101010101') rpc_result = self.nodes[0].decoderawtransaction(txSave.serialize().hex()) assert_equal('OP_RETURN 3011020701010101010101020601010101010101', rpc_result['vin'][0]['scriptSig']['asm']) def run_test(self): + self.log.info("Test decoding of standard input scripts [scriptSig]") self.decodescript_script_sig() + self.log.info("Test decoding of standard output scripts [scriptPubKey]") self.decodescript_script_pub_key() + self.log.info("Test 'asm' script decoding of transactions") self.decoderawtransaction_asm_sighashtype() if __name__ == '__main__': diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index 0d8117bd02..93970ff40c 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -571,12 +571,12 @@ class RawTransactionsTest(BitcoinTestFramework): if self.options.descriptors: self.nodes[1].walletpassphrase('test', 10) self.nodes[1].importdescriptors([{ - 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/0h/*h)'), + 'desc': descsum_create('tr(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/0h/*h)'), 'timestamp': 'now', 'active': True }, { - 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/1h/*h)'), + 'desc': descsum_create('tr(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/1h/*h)'), 'timestamp': 'now', 'active': True, 'internal': True @@ -778,11 +778,18 @@ class RawTransactionsTest(BitcoinTestFramework): for param, zero_value in product(["fee_rate", "feeRate"], [0, 0.000, 0.00000000, "0", "0.000", "0.00000000"]): assert_equal(self.nodes[3].fundrawtransaction(rawtx, {param: zero_value})["fee"], 0) - # With no arguments passed, expect fee of 141 satoshis. - assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000141, vspan=0.00000001) - # Expect fee to be 10,000x higher when an explicit fee rate 10,000x greater is specified. - result = node.fundrawtransaction(rawtx, {"fee_rate": 10000}) - assert_approx(result["fee"], vexp=0.0141, vspan=0.0001) + if self.options.descriptors: + # With no arguments passed, expect fee of 153 satoshis as descriptor wallets now have a taproot output. + assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000153, vspan=0.00000001) + # Expect fee to be 10,000x higher when an explicit fee rate 10,000x greater is specified. + result = node.fundrawtransaction(rawtx, {"fee_rate": 10000}) + assert_approx(result["fee"], vexp=0.0153, vspan=0.0001) + else: + # With no arguments passed, expect fee of 141 satoshis as legacy wallets only support up to segwit v0. + assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000141, vspan=0.00000001) + # Expect fee to be 10,000x higher when an explicit fee rate 10,000x greater is specified. + result = node.fundrawtransaction(rawtx, {"fee_rate": 10000}) + assert_approx(result["fee"], vexp=0.0141, vspan=0.0001) self.log.info("Test fundrawtxn with invalid estimate_mode settings") for k, v in {"number": 42, "object": {"foo": "bar"}}.items(): @@ -1073,7 +1080,7 @@ class RawTransactionsTest(BitcoinTestFramework): # Make sure the default wallet will not be loaded when restarted with a high minrelaytxfee self.nodes[0].unloadwallet(self.default_wallet_name, False) feerate = Decimal("0.1") - self.restart_node(0, [f"-minrelaytxfee={feerate}", "-discardfee=0"]) # Set high minrelayfee, set discardfee to 0 for easier calculation + self.restart_node(0, [f"-minrelaytxfee={feerate}", "-discardfee=0", "-changetype=bech32", "-addresstype=bech32"]) # Set high minrelayfee, set discardfee to 0 for easier calculation self.nodes[0].loadwallet(self.default_wallet_name, True) funds = self.nodes[0].get_wallet_rpc(self.default_wallet_name) diff --git a/test/functional/rpc_generateblock.py b/test/functional/rpc_generateblock.py index 4dc5c63ff3..7aede0e947 100755 --- a/test/functional/rpc_generateblock.py +++ b/test/functional/rpc_generateblock.py @@ -6,6 +6,7 @@ ''' from test_framework.test_framework import BitcoinTestFramework +from test_framework.wallet import MiniWallet from test_framework.util import ( assert_equal, assert_raises_rpc_error, @@ -16,14 +17,13 @@ class GenerateBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def run_test(self): node = self.nodes[0] + miniwallet = MiniWallet(node) + miniwallet.rescan_utxos() self.log.info('Generate an empty block to address') - address = node.getnewaddress() + address = miniwallet.get_address() hash = self.generateblock(node, output=address, transactions=[])['hash'] block = node.getblock(blockhash=hash, verbose=2) assert_equal(len(block['tx']), 1) @@ -51,37 +51,31 @@ class GenerateBlockTest(BitcoinTestFramework): assert_equal(len(block['tx']), 1) assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], combo_address) - # Generate 110 blocks to spend - self.generatetoaddress(node, 110, address) - # Generate some extra mempool transactions to verify they don't get mined for _ in range(10): - node.sendtoaddress(address, 0.001) + miniwallet.send_self_transfer(from_node=node) self.log.info('Generate block with txid') - txid = node.sendtoaddress(address, 1) + txid = miniwallet.send_self_transfer(from_node=node)['txid'] hash = self.generateblock(node, address, [txid])['hash'] block = node.getblock(hash, 1) assert_equal(len(block['tx']), 2) assert_equal(block['tx'][1], txid) self.log.info('Generate block with raw tx') - utxos = node.listunspent(addresses=[address]) - raw = node.createrawtransaction([{'txid':utxos[0]['txid'], 'vout':utxos[0]['vout']}],[{address:1}]) - signed_raw = node.signrawtransactionwithwallet(raw)['hex'] - hash = self.generateblock(node, address, [signed_raw])['hash'] + rawtx = miniwallet.create_self_transfer(from_node=node)['hex'] + hash = self.generateblock(node, address, [rawtx])['hash'] + block = node.getblock(hash, 1) assert_equal(len(block['tx']), 2) txid = block['tx'][1] - assert_equal(node.gettransaction(txid)['hex'], signed_raw) + assert_equal(node.getrawtransaction(txid=txid, verbose=False, blockhash=hash), rawtx) self.log.info('Fail to generate block with out of order txs') - raw1 = node.createrawtransaction([{'txid':txid, 'vout':0}],[{address:0.9999}]) - signed_raw1 = node.signrawtransactionwithwallet(raw1)['hex'] - txid1 = node.sendrawtransaction(signed_raw1) - raw2 = node.createrawtransaction([{'txid':txid1, 'vout':0}],[{address:0.999}]) - signed_raw2 = node.signrawtransactionwithwallet(raw2)['hex'] - assert_raises_rpc_error(-25, 'TestBlockValidity failed: bad-txns-inputs-missingorspent', self.generateblock, node, address, [signed_raw2, txid1]) + txid1 = miniwallet.send_self_transfer(from_node=node)['txid'] + utxo1 = miniwallet.get_utxo(txid=txid1) + rawtx2 = miniwallet.create_self_transfer(from_node=node, utxo_to_spend=utxo1)['hex'] + assert_raises_rpc_error(-25, 'TestBlockValidity failed: bad-txns-inputs-missingorspent', self.generateblock, node, address, [rawtx2, txid1]) self.log.info('Fail to generate block with txid not in mempool') missing_txid = '0000000000000000000000000000000000000000000000000000000000000000' diff --git a/test/functional/rpc_invalid_address_message.py b/test/functional/rpc_invalid_address_message.py index 8f9b2c3312..2567564f15 100755 --- a/test/functional/rpc_invalid_address_message.py +++ b/test/functional/rpc_invalid_address_message.py @@ -12,79 +12,96 @@ from test_framework.util import ( ) BECH32_VALID = 'bcrt1qtmp74ayg7p24uslctssvjm06q5phz4yrxucgnv' +BECH32_VALID_CAPITALS = 'BCRT1QPLMTZKC2XHARPPZDLNPAQL78RSHJ68U33RAH7R' +BECH32_VALID_MULTISIG = 'bcrt1qdg3myrgvzw7ml9q0ejxhlkyxm7vl9r56yzkfgvzclrf4hkpx9yfqhpsuks' + BECH32_INVALID_BECH32 = 'bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqdmchcc' BECH32_INVALID_BECH32M = 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7k35mrzd' BECH32_INVALID_VERSION = 'bcrt130xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqynjegk' BECH32_INVALID_SIZE = 'bcrt1s0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav25430mtr' BECH32_INVALID_V0_SIZE = 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kqqq5k3my' BECH32_INVALID_PREFIX = 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx' +BECH32_TOO_LONG = 'bcrt1q049edschfnwystcqnsvyfpj23mpsg3jcedq9xv049edschfnwystcqnsvyfpj23mpsg3jcedq9xv049edschfnwystcqnsvyfpj23m' +BECH32_ONE_ERROR = 'bcrt1q049edschfnwystcqnsvyfpj23mpsg3jcedq9xv' +BECH32_ONE_ERROR_CAPITALS = 'BCRT1QPLMTZKC2XHARPPZDLNPAQL78RSHJ68U32RAH7R' +BECH32_TWO_ERRORS = 'bcrt1qax9suht3qv95sw33xavx8crpxduefdrsvgsklu' # should be bcrt1qax9suht3qv95sw33wavx8crpxduefdrsvgsklx +BECH32_NO_SEPARATOR = 'bcrtq049ldschfnwystcqnsvyfpj23mpsg3jcedq9xv' +BECH32_INVALID_CHAR = 'bcrt1q04oldschfnwystcqnsvyfpj23mpsg3jcedq9xv' +BECH32_MULTISIG_TWO_ERRORS = 'bcrt1qdg3myrgvzw7ml8q0ejxhlkyxn7vl9r56yzkfgvzclrf4hkpx9yfqhpsuks' +BECH32_WRONG_VERSION = 'bcrt1ptmp74ayg7p24uslctssvjm06q5phz4yrxucgnv' BASE58_VALID = 'mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn' BASE58_INVALID_PREFIX = '17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem' +BASE58_INVALID_CHECKSUM = 'mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJJfn' +BASE58_INVALID_LENGTH = '2VKf7XKMrp4bVNVmuRbyCewkP8FhGLP2E54LHDPakr9Sq5mtU2' INVALID_ADDRESS = 'asfah14i8fajz0123f' +INVALID_ADDRESS_2 = '1q049ldschfnwystcqnsvyfpj23mpsg3jcedq9xv' class InvalidAddressErrorMessageTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - def test_validateaddress(self): - node = self.nodes[0] - - # Bech32 - info = node.validateaddress(BECH32_INVALID_SIZE) - assert not info['isvalid'] - assert_equal(info['error'], 'Invalid Bech32 address data size') - - info = node.validateaddress(BECH32_INVALID_PREFIX) - assert not info['isvalid'] - assert_equal(info['error'], 'Invalid prefix for Bech32 address') - - info = node.validateaddress(BECH32_INVALID_BECH32) - assert not info['isvalid'] - assert_equal(info['error'], 'Version 1+ witness address must use Bech32m checksum') - - info = node.validateaddress(BECH32_INVALID_BECH32M) - assert not info['isvalid'] - assert_equal(info['error'], 'Version 0 witness address must use Bech32 checksum') - - info = node.validateaddress(BECH32_INVALID_V0_SIZE) - assert not info['isvalid'] - assert_equal(info['error'], 'Invalid Bech32 v0 address data size') - - info = node.validateaddress(BECH32_VALID) + def check_valid(self, addr): + info = self.nodes[0].validateaddress(addr) assert info['isvalid'] assert 'error' not in info + assert 'error_locations' not in info - info = node.validateaddress(BECH32_INVALID_VERSION) - assert not info['isvalid'] - assert_equal(info['error'], 'Invalid Bech32 address witness version') - - # Base58 - info = node.validateaddress(BASE58_INVALID_PREFIX) - assert not info['isvalid'] - assert_equal(info['error'], 'Invalid prefix for Base58-encoded address') + def check_invalid(self, addr, error_str, error_locations=None): + res = self.nodes[0].validateaddress(addr) + assert not res['isvalid'] + assert_equal(res['error'], error_str) + if error_locations: + assert_equal(res['error_locations'], error_locations) + else: + assert_equal(res['error_locations'], []) - info = node.validateaddress(BASE58_VALID) - assert info['isvalid'] - assert 'error' not in info + def test_validateaddress(self): + # Invalid Bech32 + self.check_invalid(BECH32_INVALID_SIZE, 'Invalid Bech32 address data size') + self.check_invalid(BECH32_INVALID_PREFIX, 'Invalid HRP or Base58 character in address') + self.check_invalid(BECH32_INVALID_BECH32, 'Version 1+ witness address must use Bech32m checksum') + self.check_invalid(BECH32_INVALID_BECH32M, 'Version 0 witness address must use Bech32 checksum') + self.check_invalid(BECH32_INVALID_VERSION, 'Invalid Bech32 address witness version') + self.check_invalid(BECH32_INVALID_V0_SIZE, 'Invalid Bech32 v0 address data size') + self.check_invalid(BECH32_TOO_LONG, 'Bech32 string too long', list(range(90, 108))) + self.check_invalid(BECH32_ONE_ERROR, 'Invalid checksum', [9]) + self.check_invalid(BECH32_TWO_ERRORS, 'Invalid checksum', [22, 43]) + self.check_invalid(BECH32_ONE_ERROR_CAPITALS, 'Invalid checksum', [38]) + self.check_invalid(BECH32_NO_SEPARATOR, 'Missing separator') + self.check_invalid(BECH32_INVALID_CHAR, 'Invalid Base 32 character', [8]) + self.check_invalid(BECH32_MULTISIG_TWO_ERRORS, 'Invalid checksum', [19, 30]) + self.check_invalid(BECH32_WRONG_VERSION, 'Invalid checksum', [5]) + + # Valid Bech32 + self.check_valid(BECH32_VALID) + self.check_valid(BECH32_VALID_CAPITALS) + self.check_valid(BECH32_VALID_MULTISIG) + + # Invalid Base58 + self.check_invalid(BASE58_INVALID_PREFIX, 'Invalid prefix for Base58-encoded address') + self.check_invalid(BASE58_INVALID_CHECKSUM, 'Invalid checksum or length of Base58 address') + self.check_invalid(BASE58_INVALID_LENGTH, 'Invalid checksum or length of Base58 address') + + # Valid Base58 + self.check_valid(BASE58_VALID) # Invalid address format - info = node.validateaddress(INVALID_ADDRESS) - assert not info['isvalid'] - assert_equal(info['error'], 'Invalid address format') + self.check_invalid(INVALID_ADDRESS, 'Invalid HRP or Base58 character in address') + self.check_invalid(INVALID_ADDRESS_2, 'Invalid HRP or Base58 character in address') def test_getaddressinfo(self): node = self.nodes[0] assert_raises_rpc_error(-5, "Invalid Bech32 address data size", node.getaddressinfo, BECH32_INVALID_SIZE) - assert_raises_rpc_error(-5, "Invalid prefix for Bech32 address", node.getaddressinfo, BECH32_INVALID_PREFIX) + assert_raises_rpc_error(-5, "Invalid HRP or Base58 character in address", node.getaddressinfo, BECH32_INVALID_PREFIX) assert_raises_rpc_error(-5, "Invalid prefix for Base58-encoded address", node.getaddressinfo, BASE58_INVALID_PREFIX) - assert_raises_rpc_error(-5, "Invalid address format", node.getaddressinfo, INVALID_ADDRESS) + assert_raises_rpc_error(-5, "Invalid HRP or Base58 character in address", node.getaddressinfo, INVALID_ADDRESS) def run_test(self): self.test_validateaddress() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 9b00eab346..4ac4f27d8a 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -31,7 +31,7 @@ class PSBTTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 3 self.extra_args = [ - ["-walletrbf=1"], + ["-walletrbf=1", "-addresstype=bech32", "-changetype=bech32"], #TODO: Remove address type restrictions once taproot has psbt extensions ["-walletrbf=0", "-changetype=legacy"], [] ] diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index 616ac29b15..afe4dba7b4 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -70,8 +70,8 @@ class ToolWalletTest(BitcoinTestFramework): def get_expected_info_output(self, name="", transactions=0, keypool=2, address=0): wallet_name = self.default_wallet_name if name == "" else name - output_types = 3 # p2pkh, p2sh, segwit if self.options.descriptors: + output_types = 4 # p2pkh, p2sh, segwit, bech32m return textwrap.dedent('''\ Wallet info =========== @@ -85,6 +85,7 @@ class ToolWalletTest(BitcoinTestFramework): Address Book: %d ''' % (wallet_name, keypool * output_types, transactions, address)) else: + output_types = 3 # p2pkh, p2sh, segwit. Legacy wallets do not support bech32m. return textwrap.dedent('''\ Wallet info =========== @@ -298,8 +299,8 @@ class ToolWalletTest(BitcoinTestFramework): assert_equal(1000, out['keypoolsize_hd_internal']) assert_equal(True, 'hdseedid' in out) else: - assert_equal(3000, out['keypoolsize']) - assert_equal(3000, out['keypoolsize_hd_internal']) + assert_equal(4000, out['keypoolsize']) + assert_equal(4000, out['keypoolsize_hd_internal']) self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after) assert_equal(timestamp_before, timestamp_after) diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py index e4fe150333..eb6e497951 100755 --- a/test/functional/wallet_address_types.py +++ b/test/functional/wallet_address_types.py @@ -121,6 +121,12 @@ class AddressTypeTest(BitcoinTestFramework): assert_equal(info['witness_version'], 0) assert_equal(len(info['witness_program']), 40) assert 'pubkey' in info + elif not multisig and typ == "bech32m": + # P2TR single sig + assert info["isscript"] + assert info["iswitness"] + assert_equal(info["witness_version"], 1) + assert_equal(len(info["witness_program"]), 64) elif typ == 'legacy': # P2SH-multisig assert info['isscript'] @@ -339,19 +345,31 @@ class AddressTypeTest(BitcoinTestFramework): self.log.info("Nodes with addresstype=legacy never use a P2WPKH change output (unless changetype is set otherwise):") self.test_change_output_type(0, [to_address_bech32_1], 'legacy') - self.log.info("Nodes with addresstype=p2sh-segwit only use a P2WPKH change output if any destination address is bech32:") - self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit') - self.test_change_output_type(1, [to_address_bech32_1], 'bech32') - self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32') - self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32') + if self.options.descriptors: + self.log.info("Nodes with addresstype=p2sh-segwit only use a bech32m change output if any destination address is bech32:") + self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit') + self.test_change_output_type(1, [to_address_bech32_1], 'bech32m') + self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32m') + self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32m') + else: + self.log.info("Nodes with addresstype=p2sh-segwit only use a P2WPKH change output if any destination address is bech32:") + self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit') + self.test_change_output_type(1, [to_address_bech32_1], 'bech32') + self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32') + self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32') self.log.info("Nodes with change_type=bech32 always use a P2WPKH change output:") self.test_change_output_type(2, [to_address_bech32_1], 'bech32') self.test_change_output_type(2, [to_address_p2sh], 'bech32') - self.log.info("Nodes with addresstype=bech32 always use a P2WPKH change output (unless changetype is set otherwise):") - self.test_change_output_type(3, [to_address_bech32_1], 'bech32') - self.test_change_output_type(3, [to_address_p2sh], 'bech32') + if self.options.descriptors: + self.log.info("Nodes with addresstype=bech32 always use either a bech32 or bech32m change output (unless changetype is set otherwise):") + self.test_change_output_type(3, [to_address_bech32_1], 'bech32m') + self.test_change_output_type(3, [to_address_p2sh], 'bech32') + else: + self.log.info("Nodes with addresstype=bech32 always use a P2WPKH change output (unless changetype is set otherwise):") + self.test_change_output_type(3, [to_address_bech32_1], 'bech32') + self.test_change_output_type(3, [to_address_p2sh], 'bech32') self.log.info('getrawchangeaddress defaults to addresstype if -changetype is not set and argument is absent') self.test_address(3, self.nodes[3].getrawchangeaddress(), multisig=False, typ='bech32') @@ -370,10 +388,9 @@ class AddressTypeTest(BitcoinTestFramework): self.test_address(4, self.nodes[4].getrawchangeaddress('bech32'), multisig=False, typ='bech32') if self.options.descriptors: - self.log.info("Descriptor wallets do not have bech32m addresses by default yet") - # TODO: Remove this when they do - assert_raises_rpc_error(-12, "Error: No bech32m addresses available", self.nodes[0].getnewaddress, "", "bech32m") - assert_raises_rpc_error(-12, "Error: No bech32m addresses available", self.nodes[0].getrawchangeaddress, "bech32m") + self.log.info("Descriptor wallets have bech32m addresses") + self.test_address(4, self.nodes[4].getnewaddress("", "bech32m"), multisig=False, typ="bech32m") + self.test_address(4, self.nodes[4].getrawchangeaddress("bech32m"), multisig=False, typ="bech32m") else: self.log.info("Legacy wallets cannot make bech32m addresses") assert_raises_rpc_error(-8, "Legacy wallets cannot provide bech32m addresses", self.nodes[0].getnewaddress, "", "bech32m") diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 291be055b7..f6843d597d 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -24,7 +24,6 @@ from test_framework.blocktools import ( ) from test_framework.messages import ( BIP125_SEQUENCE_NUMBER, - tx_from_hex, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -588,13 +587,10 @@ def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")): def submit_block_with_tx(node, tx): - ctx = tx_from_hex(tx) tip = node.getbestblockhash() height = node.getblockcount() + 1 block_time = node.getblockheader(tip)["mediantime"] + 1 - block = create_block(int(tip, 16), create_coinbase(height), block_time) - block.vtx.append(ctx) - block.hashMerkleRoot = block.calc_merkle_root() + block = create_block(int(tip, 16), create_coinbase(height), block_time, txlist=[tx]) add_witness_commitment(block) block.solve() node.submitblock(block.serialize().hex()) diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py index a751138d89..4416a9655f 100755 --- a/test/functional/wallet_createwallet.py +++ b/test/functional/wallet_createwallet.py @@ -146,7 +146,7 @@ class CreateWalletTest(BitcoinTestFramework): w6.keypoolrefill(1) # There should only be 1 key for legacy, 3 for descriptors walletinfo = w6.getwalletinfo() - keys = 3 if self.options.descriptors else 1 + keys = 4 if self.options.descriptors else 1 assert_equal(walletinfo['keypoolsize'], keys) assert_equal(walletinfo['keypoolsize_hd_internal'], keys) # Allow empty passphrase, but there should be a warning diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py index df2fdb2943..e47d021210 100755 --- a/test/functional/wallet_descriptor.py +++ b/test/functional/wallet_descriptor.py @@ -37,12 +37,12 @@ class WalletDescriptorTest(BitcoinTestFramework): self.log.info("Making a descriptor wallet") self.nodes[0].createwallet(wallet_name="desc1", descriptors=True) - # A descriptor wallet should have 100 addresses * 3 types = 300 keys + # A descriptor wallet should have 100 addresses * 4 types = 400 keys self.log.info("Checking wallet info") wallet_info = self.nodes[0].getwalletinfo() assert_equal(wallet_info['format'], 'sqlite') - assert_equal(wallet_info['keypoolsize'], 300) - assert_equal(wallet_info['keypoolsize_hd_internal'], 300) + assert_equal(wallet_info['keypoolsize'], 400) + assert_equal(wallet_info['keypoolsize_hd_internal'], 400) assert 'keypoololdest' not in wallet_info # Check that getnewaddress works diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index c228bb122a..9052bc7f7f 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -108,12 +108,24 @@ class WalletGroupTest(BitcoinTestFramework): assert_equal(input_addrs[0], input_addrs[1]) # Node 2 enforces avoidpartialspends so needs no checking here + if self.options.descriptors: + # Descriptor wallets will use Taproot change by default which has different fees + tx4_ungrouped_fee = 3060 + tx4_grouped_fee = 4400 + tx5_6_ungrouped_fee = 5760 + tx5_6_grouped_fee = 8480 + else: + tx4_ungrouped_fee = 2820 + tx4_grouped_fee = 4160 + tx5_6_ungrouped_fee = 5520 + tx5_6_grouped_fee = 8240 + self.log.info("Test wallet option maxapsfee") addr_aps = self.nodes[3].getnewaddress() self.nodes[0].sendtoaddress(addr_aps, 1.0) self.nodes[0].sendtoaddress(addr_aps, 1.0) self.generate(self.nodes[0], 1) - with self.nodes[3].assert_debug_log(['Fee non-grouped = 2820, grouped = 4160, using grouped']): + with self.nodes[3].assert_debug_log([f'Fee non-grouped = {tx4_ungrouped_fee}, grouped = {tx4_grouped_fee}, using grouped']): txid4 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 0.1) tx4 = self.nodes[3].getrawtransaction(txid4, True) # tx4 should have 2 inputs and 2 outputs although one output would @@ -124,7 +136,7 @@ class WalletGroupTest(BitcoinTestFramework): addr_aps2 = self.nodes[3].getnewaddress() [self.nodes[0].sendtoaddress(addr_aps2, 1.0) for _ in range(5)] self.generate(self.nodes[0], 1) - with self.nodes[3].assert_debug_log(['Fee non-grouped = 5520, grouped = 8240, using non-grouped']): + with self.nodes[3].assert_debug_log([f'Fee non-grouped = {tx5_6_ungrouped_fee}, grouped = {tx5_6_grouped_fee}, using non-grouped']): txid5 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 2.95) tx5 = self.nodes[3].getrawtransaction(txid5, True) # tx5 should have 3 inputs (1.0, 1.0, 1.0) and 2 outputs @@ -137,7 +149,7 @@ class WalletGroupTest(BitcoinTestFramework): addr_aps3 = self.nodes[4].getnewaddress() [self.nodes[0].sendtoaddress(addr_aps3, 1.0) for _ in range(5)] self.generate(self.nodes[0], 1) - with self.nodes[4].assert_debug_log(['Fee non-grouped = 5520, grouped = 8240, using grouped']): + with self.nodes[4].assert_debug_log([f'Fee non-grouped = {tx5_6_ungrouped_fee}, grouped = {tx5_6_grouped_fee}, using grouped']): txid6 = self.nodes[4].sendtoaddress(self.nodes[0].getnewaddress(), 2.95) tx6 = self.nodes[4].getrawtransaction(txid6, True) # tx6 should have 5 inputs and 2 outputs diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index ac878ea0aa..d78afb4212 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -136,7 +136,7 @@ class WalletHDTest(BitcoinTestFramework): keypath = self.nodes[1].getaddressinfo(out['scriptPubKey']['address'])['hdkeypath'] if self.options.descriptors: - assert_equal(keypath[0:14], "m/84'/1'/0'/1/") + assert_equal(keypath[0:14], "m/86'/1'/0'/1/") else: assert_equal(keypath[0:7], "m/0'/1'") diff --git a/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py index bf7a342be6..54c47511a9 100755 --- a/test/functional/wallet_keypool.py +++ b/test/functional/wallet_keypool.py @@ -87,8 +87,8 @@ class KeyPoolTest(BitcoinTestFramework): nodes[0].walletlock() wi = nodes[0].getwalletinfo() if self.options.descriptors: - assert_equal(wi['keypoolsize_hd_internal'], 18) - assert_equal(wi['keypoolsize'], 18) + assert_equal(wi['keypoolsize_hd_internal'], 24) + assert_equal(wi['keypoolsize'], 24) else: assert_equal(wi['keypoolsize_hd_internal'], 6) assert_equal(wi['keypoolsize'], 6) @@ -132,8 +132,8 @@ class KeyPoolTest(BitcoinTestFramework): nodes[0].keypoolrefill(100) wi = nodes[0].getwalletinfo() if self.options.descriptors: - assert_equal(wi['keypoolsize_hd_internal'], 300) - assert_equal(wi['keypoolsize'], 300) + assert_equal(wi['keypoolsize_hd_internal'], 400) + assert_equal(wi['keypoolsize'], 400) else: assert_equal(wi['keypoolsize_hd_internal'], 100) assert_equal(wi['keypoolsize'], 100) diff --git a/test/functional/wallet_listdescriptors.py b/test/functional/wallet_listdescriptors.py index 436bbdcfcc..202ef92887 100755 --- a/test/functional/wallet_listdescriptors.py +++ b/test/functional/wallet_listdescriptors.py @@ -43,9 +43,9 @@ class ListDescriptorsTest(BitcoinTestFramework): node.createwallet(wallet_name='w3', descriptors=True) result = node.get_wallet_rpc('w3').listdescriptors() assert_equal("w3", result['wallet_name']) - assert_equal(6, len(result['descriptors'])) - assert_equal(6, len([d for d in result['descriptors'] if d['active']])) - assert_equal(3, len([d for d in result['descriptors'] if d['internal']])) + assert_equal(8, len(result['descriptors'])) + assert_equal(8, len([d for d in result['descriptors'] if d['active']])) + assert_equal(4, len([d for d in result['descriptors'] if d['internal']])) for item in result['descriptors']: assert item['desc'] != '' assert item['next'] == 0 diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py index b22c171374..17eab25457 100755 --- a/test/functional/wallet_taproot.py +++ b/test/functional/wallet_taproot.py @@ -10,7 +10,12 @@ from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal from test_framework.descriptors import descsum_create -from test_framework.script import (CScript, OP_CHECKSIG, taproot_construct) +from test_framework.script import ( + CScript, + OP_1, + OP_CHECKSIG, + taproot_construct, +) from test_framework.segwit_addr import encode_segwit_address # xprvs/xpubs, and m/* derived x-only pubkeys (created using independent implementation) @@ -165,7 +170,7 @@ def pk(hex_key): def compute_taproot_address(pubkey, scripts): """Compute the address for a taproot output with given inner key and scripts.""" tap = taproot_construct(pubkey, scripts) - assert tap.scriptPubKey[0] == 0x51 + assert tap.scriptPubKey[0] == OP_1 assert tap.scriptPubKey[1] == 0x20 return encode_segwit_address("bcrt", 1, tap.scriptPubKey[2:]) @@ -237,20 +242,15 @@ class WalletTaprootTest(BitcoinTestFramework): assert_equal(len(rederive), 1) assert_equal(rederive[0], addr_g) - # tr descriptors cannot be imported when Taproot is not active + # tr descriptors can be imported regardless of Taproot status result = self.privs_tr_enabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) assert(result[0]["success"]) result = self.pubs_tr_enabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) assert(result[0]["success"]) - if desc.startswith("tr"): - result = self.privs_tr_disabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) - assert(not result[0]["success"]) - assert_equal(result[0]["error"]["code"], -4) - assert_equal(result[0]["error"]["message"], "Cannot import tr() descriptor when Taproot is not active") - result = self.pubs_tr_disabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) - assert(not result[0]["success"]) - assert_equal(result[0]["error"]["code"], -4) - assert_equal(result[0]["error"]["message"], "Cannot import tr() descriptor when Taproot is not active") + result = self.privs_tr_disabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) + assert result[0]["success"] + result = self.pubs_tr_disabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) + assert result[0]["success"] def do_test_sendtoaddress(self, comment, pattern, privmap, treefn, keys_pay, keys_change): self.log.info("Testing %s through sendtoaddress" % comment) diff --git a/test/lint/lint-include-guards.sh b/test/lint/lint-include-guards.sh index ac2177bbe3..23f53f027e 100755 --- a/test/lint/lint-include-guards.sh +++ b/test/lint/lint-include-guards.sh @@ -17,7 +17,7 @@ for HEADER_FILE in $(git ls-files -- "*.h" | grep -vE "^${REGEXP_EXCLUDE_FILES_W do HEADER_ID_BASE=$(cut -f2- -d/ <<< "${HEADER_FILE}" | sed "s/\.h$//g" | tr / _ | tr - _ | tr "[:lower:]" "[:upper:]") HEADER_ID="${HEADER_ID_PREFIX}${HEADER_ID_BASE}${HEADER_ID_SUFFIX}" - if [[ $(grep -cE "^#(ifndef|define) ${HEADER_ID}" "${HEADER_FILE}") != 2 ]]; then + if [[ $(grep --count --extended-regexp "^#(ifndef|define|endif //) ${HEADER_ID}" "${HEADER_FILE}") != 3 ]]; then echo "${HEADER_FILE} seems to be missing the expected include guard:" echo " #ifndef ${HEADER_ID}" echo " #define ${HEADER_ID}" diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh index b58600e6cb..661bc35175 100755 --- a/test/lint/lint-locale-dependence.sh +++ b/test/lint/lint-locale-dependence.sh @@ -37,15 +37,12 @@ export LC_ALL=C # See https://doc.qt.io/qt-5/qcoreapplication.html#locale-settings and # https://stackoverflow.com/a/34878283 for more details. -# TODO: Reduce KNOWN_VIOLATIONS by replacing uses of locale dependent stoul/strtol with locale -# independent ToIntegral<T>(...) or the ParseInt*() functions. # TODO: Reduce KNOWN_VIOLATIONS by replacing uses of locale dependent snprintf with strprintf. KNOWN_VIOLATIONS=( "src/dbwrapper.cpp:.*vsnprintf" "src/test/dbwrapper_tests.cpp:.*snprintf" "src/test/fuzz/locale.cpp" "src/test/fuzz/string.cpp" - "src/torcontrol.cpp:.*strtol" ) REGEXP_IGNORE_EXTERNAL_DEPENDENCIES="^src/(crypto/ctaes/|leveldb/|secp256k1/|minisketch/|tinyformat.h|univalue/)" diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index 83ad98226a..6032fe496f 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -91,7 +91,6 @@ implicit-signed-integer-truncation:leveldb/ implicit-signed-integer-truncation:node/miner.cpp implicit-signed-integer-truncation:net.cpp implicit-signed-integer-truncation:net_processing.cpp -implicit-signed-integer-truncation:netaddress.cpp implicit-signed-integer-truncation:streams.h implicit-signed-integer-truncation:test/arith_uint256_tests.cpp implicit-signed-integer-truncation:test/skiplist_tests.cpp |