diff options
112 files changed, 1681 insertions, 1791 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index ccf7077546..fcf1200d35 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -35,22 +35,30 @@ base_template: &BASE_TEMPLATE main_template: &MAIN_TEMPLATE timeout_in: 120m # https://cirrus-ci.org/faq/#instance-timed-out + ccache_cache: + folder: "/tmp/ccache_dir" + ci_script: + - ./ci/test_run_all.sh + +global_task_template: &GLOBAL_TASK_TEMPLATE + << : *BASE_TEMPLATE container: # https://cirrus-ci.org/faq/#are-there-any-limits # Each project has 16 CPU in total, assign 2 to each container, so that 8 tasks run in parallel cpu: 2 greedy: true memory: 8G # Set to 8GB to avoid OOM. https://cirrus-ci.org/guide/linux/#linux-containers - ccache_cache: - folder: "/tmp/ccache_dir" depends_built_cache: folder: "depends/built" fingerprint_script: echo $CIRRUS_TASK_NAME $(git rev-list -1 HEAD ./depends) - ci_script: - - ./ci/test_run_all.sh + << : *MAIN_TEMPLATE -global_task_template: &GLOBAL_TASK_TEMPLATE +macos_native_task_template: &MACOS_NATIVE_TASK_TEMPLATE << : *BASE_TEMPLATE + check_clang_script: + - clang --version + brew_install_script: + - brew install boost libevent qt@5 miniupnpc libnatpmp ccache zeromq qrencode libtool automake gnu-getopt << : *MAIN_TEMPLATE compute_credits_template: &CREDITS_TEMPLATE @@ -86,17 +94,17 @@ task: FILE_ENV: "./ci/test/00_setup_env_native_tidy.sh" task: - name: "Win64 native [msvc]" + name: "Win64 native [vs2022]" << : *FILTER_TEMPLATE windows_container: cpu: 4 memory: 8G - image: cirrusci/windowsservercore:visualstudio2019 + image: cirrusci/windowsservercore:visualstudio2022 timeout_in: 120m env: - PATH: 'C:\jom;C:\Python39;C:\Python39\Scripts;C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin;%PATH%' + PATH: 'C:\jom;C:\Python39;C:\Python39\Scripts;C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin;%PATH%' PYTHONUTF8: 1 - CI_VCPKG_TAG: '2022.04.12' + CI_VCPKG_TAG: '2022.06.16.1' VCPKG_DOWNLOADS: 'C:\Users\ContainerAdministrator\AppData\Local\vcpkg\downloads' VCPKG_DEFAULT_BINARY_CACHE: 'C:\Users\ContainerAdministrator\AppData\Local\vcpkg\archives' CCACHE_DIR: 'C:\Users\ContainerAdministrator\AppData\Local\ccache' @@ -105,7 +113,7 @@ task: QT_LOCAL_PATH: 'C:\qt-everywhere-opensource-src-5.15.3.zip' QT_SOURCE_DIR: 'C:\qt-everywhere-src-5.15.3' QTBASEDIR: 'C:\Qt_static' - x64_NATIVE_TOOLS: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat"' + x64_NATIVE_TOOLS: '"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat"' QT_CONFIGURE_COMMAND: '..\configure -release -silent -opensource -confirm-license -opengl desktop -static -static-runtime -mp -qt-zlib -qt-pcre -qt-libpng -nomake examples -nomake tests -nomake tools -no-angle -no-dbus -no-gif -no-gtk -no-ico -no-icu -no-libjpeg -no-libudev -no-sql-sqlite -no-sql-odbc -no-sqlite -no-vulkan -skip qt3d -skip qtactiveqt -skip qtandroidextras -skip qtcharts -skip qtconnectivity -skip qtdatavis3d -skip qtdeclarative -skip doc -skip qtdoc -skip qtgamepad -skip qtgraphicaleffects -skip qtimageformats -skip qtlocation -skip qtlottie -skip qtmacextras -skip qtmultimedia -skip qtnetworkauth -skip qtpurchasing -skip qtquick3d -skip qtquickcontrols -skip qtquickcontrols2 -skip qtquicktimeline -skip qtremoteobjects -skip qtscript -skip qtscxml -skip qtsensors -skip qtserialbus -skip qtserialport -skip qtspeech -skip qtsvg -skip qtvirtualkeyboard -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebglplugin -skip qtwebsockets -skip qtwebview -skip qtx11extras -skip qtxmlpatterns -no-openssl -no-feature-bearermanagement -no-feature-printdialog -no-feature-printer -no-feature-printpreviewdialog -no-feature-printpreviewwidget -no-feature-sql -no-feature-sqlmodel -no-feature-textbrowser -no-feature-textmarkdownwriter -no-feature-textodfwriter -no-feature-xml' IgnoreWarnIntDirInTempDetected: 'true' merge_script: @@ -171,7 +179,7 @@ task: build_script: - '%x64_NATIVE_TOOLS%' - cd %CIRRUS_WORKING_DIR% - - ccache --zero-stats + - ccache --zero-stats --max-size=%CCACHE_SIZE% - python build_msvc\msvc-autogen.py - msbuild build_msvc\bitcoin.sln -property:CLToolExe=%WRAPPED_CL% -property:Configuration=Release -maxCpuCount -verbosity:minimal -noLogo - ccache --show-stats @@ -306,18 +314,16 @@ task: FILE_ENV: "./ci/test/00_setup_env_mac.sh" task: - name: 'macOS 12 native [gui, system sqlite only] [no depends]' - brew_install_script: - - brew install boost libevent qt@5 miniupnpc libnatpmp ccache zeromq qrencode libtool automake gnu-getopt - << : *GLOBAL_TASK_TEMPLATE + name: 'macOS 12 native x86_64 [gui, system sqlite] [no depends]' macos_instance: # Use latest image, but hardcode version to avoid silent upgrades (and breaks) image: monterey-xcode-13.3 # https://cirrus-ci.org/guide/macOS + << : *MACOS_NATIVE_TASK_TEMPLATE env: << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV CI_USE_APT_INSTALL: "no" PACKAGE_MANAGER_INSTALL: "echo" # Nothing to do - FILE_ENV: "./ci/test/00_setup_env_mac_host.sh" + FILE_ENV: "./ci/test/00_setup_env_mac_native_x86_64.sh" task: name: 'ARM64 Android APK [focal]' diff --git a/build_msvc/README.md b/build_msvc/README.md index 7feee6b766..d59942cb17 100644 --- a/build_msvc/README.md +++ b/build_msvc/README.md @@ -3,7 +3,7 @@ Building Bitcoin Core with Visual Studio Introduction --------------------- -Solution and project files to build Bitcoin Core with `msbuild` or Visual Studio can be found in the `build_msvc` directory. The build has been tested with Visual Studio 2019 (building with earlier versions of Visual Studio should not be expected to work). +Solution and project files to build Bitcoin Core with `msbuild` or Visual Studio can be found in the `build_msvc` directory. The build has been tested with Visual Studio 2022 and Visual Studio 2019 (building with earlier versions of Visual Studio should not be expected to work). To build Bitcoin Core from the command-line, it is sufficient to only install the Visual Studio Build Tools component. @@ -30,7 +30,7 @@ To build Bitcoin Core with the GUI, a static build of Qt is required. 1. Download a single ZIP archive of Qt source code from https://download.qt.io/official_releases/qt/ (e.g., [`qt-everywhere-opensource-src-5.15.3.zip`](https://download.qt.io/official_releases/qt/5.15/5.15.3/single/qt-everywhere-opensource-src-5.15.3.zip)), and expand it into a dedicated folder. The following instructions assume that this folder is `C:\dev\qt-source`. -2. Open "x64 Native Tools Command Prompt for VS 2019", and input the following commands: +2. Open "x64 Native Tools Command Prompt for VS 2022", and input the following commands: ```cmd cd C:\dev\qt-source mkdir build @@ -47,21 +47,21 @@ To build Bitcoin Core without Qt, unload or disable the `bitcoin-qt`, `libbitcoi Building --------------------- -1. Use Python to generate `*.vcxproj` from Makefile: +1. Use Python to generate `*.vcxproj` for the Visual Studio 2022 toolchain from Makefile: -``` -PS >py -3 msvc-autogen.py +```cmd +python build_msvc\msvc-autogen.py ``` 2. An optional step is to adjust the settings in the `build_msvc` directory and the `common.init.vcxproj` file. This project file contains settings that are common to all projects such as the runtime library version and target Windows SDK version. The Qt directories can also be set. To specify a non-default path to a static Qt package directory, use the `QTBASEDIR` environment variable. -3. To build from the command-line with the Visual Studio 2019 toolchain use: +3. To build from the command-line with the Visual Studio toolchain use: ```cmd -msbuild -property:Configuration=Release -maxCpuCount -verbosity:minimal bitcoin.sln +msbuild build_msvc\bitcoin.sln -property:Configuration=Release -maxCpuCount -verbosity:minimal ``` -Alternatively, open the `build_msvc/bitcoin.sln` file in Visual Studio 2019. +Alternatively, open the `build_msvc/bitcoin.sln` file in Visual Studio. Security --------------------- diff --git a/build_msvc/libsecp256k1_config.h b/build_msvc/libsecp256k1_config.h index 57f2f144ff..2b1a980e27 100644 --- a/build_msvc/libsecp256k1_config.h +++ b/build_msvc/libsecp256k1_config.h @@ -8,23 +8,6 @@ #define BITCOIN_LIBSECP256K1_CONFIG_H #undef USE_ASM_X86_64 -#undef USE_ENDOMORPHISM -#undef USE_FIELD_10X26 -#undef USE_FIELD_5X52 -#undef USE_FIELD_INV_BUILTIN -#undef USE_FIELD_INV_NUM -#undef USE_NUM_GMP -#undef USE_NUM_NONE -#undef USE_SCALAR_4X64 -#undef USE_SCALAR_8X32 -#undef USE_SCALAR_INV_BUILTIN -#undef USE_SCALAR_INV_NUM - -#define USE_NUM_NONE 1 -#define USE_FIELD_INV_BUILTIN 1 -#define USE_SCALAR_INV_BUILTIN 1 -#define USE_FIELD_10X26 1 -#define USE_SCALAR_8X32 1 #define ECMULT_GEN_PREC_BITS 4 #define ECMULT_WINDOW_SIZE 15 diff --git a/build_msvc/msvc-autogen.py b/build_msvc/msvc-autogen.py index 819fe1b7ae..c4ea598aa0 100755 --- a/build_msvc/msvc-autogen.py +++ b/build_msvc/msvc-autogen.py @@ -9,7 +9,7 @@ import argparse from shutil import copyfile SOURCE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src')) -DEFAULT_PLATFORM_TOOLSET = R'v142' +DEFAULT_PLATFORM_TOOLSET = R'v143' libs = [ 'libbitcoin_cli', @@ -93,7 +93,7 @@ def set_properties(vcxproj_filename, placeholder, content): def main(): parser = argparse.ArgumentParser(description='Bitcoin-core msbuild configuration initialiser.') parser.add_argument('-toolset', nargs='?', default=DEFAULT_PLATFORM_TOOLSET, - help='Optionally sets the msbuild platform toolset, e.g. v142 for Visual Studio 2019.' + help='Optionally sets the msbuild platform toolset, e.g. v142 for Visual Studio 2019, or v143 for Visual Studio 2022.' ' default is %s.'%DEFAULT_PLATFORM_TOOLSET) args = parser.parse_args() set_properties(os.path.join(SOURCE_DIR, '../build_msvc/common.init.vcxproj'), '@TOOLSET@', args.toolset) diff --git a/ci/test/00_setup_env_mac_host.sh b/ci/test/00_setup_env_mac_native_x86_64.sh index d176296e76..d176296e76 100755 --- a/ci/test/00_setup_env_mac_host.sh +++ b/ci/test/00_setup_env_mac_native_x86_64.sh diff --git a/configure.ac b/configure.ac index a4a23474c8..0e1968f5c4 100644 --- a/configure.ac +++ b/configure.ac @@ -31,6 +31,7 @@ BITCOIN_MP_NODE_NAME=bitcoin-node BITCOIN_MP_GUI_NAME=bitcoin-gui dnl Unless the user specified ARFLAGS, force it to be cr +dnl This is also the default as-of libtool 2.4.7 AC_ARG_VAR([ARFLAGS], [Flags for the archiver, defaults to <cr> if not set]) if test "${ARFLAGS+set}" != "set"; then ARFLAGS="cr" @@ -710,6 +711,9 @@ case $host in fi CORE_CPPFLAGS="$CORE_CPPFLAGS -D_MT -DWIN32 -D_WINDOWS -D_WIN32_WINNT=0x0601 -D_WIN32_IE=0x0501 -DWIN32_LEAN_AND_MEAN" + dnl Prevent the definition of min/max macros. + dnl We always want to use the standard library. + CORE_CPPFLAGS="$CORE_CPPFLAGS -DNOMINMAX" dnl libtool insists upon adding -nostdlib and a list of objects/libs to link against. dnl That breaks our ability to build dll's with static libgcc/libstdc++/libssp. Override @@ -1261,8 +1265,8 @@ AC_LINK_IFELSE( AC_MSG_CHECKING([for ::_wsystem]) AC_LINK_IFELSE( [ AC_LANG_PROGRAM( - [[ ]], - [[ int nErr = ::_wsystem(""); ]] + [[ #include <stdlib.h> ]], + [[ int nErr = ::_wsystem(NULL); ]] )], [ AC_MSG_RESULT([yes]); have_any_system=yes], [ AC_MSG_RESULT([no]) ] @@ -1299,6 +1303,7 @@ if test "$enable_fuzz" = "yes"; then bitcoin_enable_qt_test=no bitcoin_enable_qt_dbus=no use_bench=no + use_tests=no use_external_signer=no use_upnp=no use_natpmp=no @@ -1456,6 +1461,11 @@ if test "$use_boost" = "yes"; then dnl we don't use multi_index serialization BOOST_CPPFLAGS="$BOOST_CPPFLAGS -DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION" + dnl Prevent use of std::unary_function, which was removed in C++17, + dnl and will generate warnings with newer compilers. + dnl See: https://github.com/boostorg/container_hash/issues/22. + BOOST_CPPFLAGS="$BOOST_CPPFLAGS -DBOOST_NO_CXX98_FUNCTION_BASE" + if test "$enable_debug" = "yes" || test "$enable_fuzz" = "yes"; then BOOST_CPPFLAGS="$BOOST_CPPFLAGS -DBOOST_MULTI_INDEX_ENABLE_SAFE_MODE" fi @@ -1555,7 +1565,7 @@ fi dnl libevent check use_libevent=no -if test "$build_bitcoin_cli$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench" != "nonononono"; then +if test "$build_bitcoin_cli$build_bitcoind$bitcoin_enable_qt$enable_fuzz_binary$use_tests$use_bench" != "nononononono"; then PKG_CHECK_MODULES([EVENT], [libevent >= 2.1.8], [use_libevent=yes], [AC_MSG_ERROR([libevent version 2.1.8 or greater not found.])]) if test "$TARGET_OS" != "windows"; then PKG_CHECK_MODULES([EVENT_PTHREADS], [libevent_pthreads >= 2.1.8], [], [AC_MSG_ERROR([libevent_pthreads version 2.1.8 or greater not found.])]) @@ -1842,8 +1852,8 @@ else AC_MSG_RESULT([no]) fi -if test "$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoin_libs$build_bitcoind$bitcoin_enable_qt$use_bench$use_tests" = "nononononononono"; then - AC_MSG_ERROR([No targets! Please specify at least one of: --with-utils --with-libs --with-daemon --with-gui --enable-bench or --enable-tests]) +if test "$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoin_libs$build_bitcoind$bitcoin_enable_qt$enable_fuzz_binary$use_bench$use_tests" = "nonononononononono"; then + AC_MSG_ERROR([No targets! Please specify at least one of: --with-utils --with-libs --with-daemon --with-gui --enable-fuzz(-binary) --enable-bench or --enable-tests]) fi AM_CONDITIONAL([TARGET_DARWIN], [test "$TARGET_OS" = "darwin"]) diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index 44c769f463..ad3129184c 100755 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -249,10 +249,6 @@ case "$HOST" in *powerpc64*) HOST_LDFLAGS="${HOST_LDFLAGS} -Wl,--no-tls-get-addr-optimize" ;; esac -case "$HOST" in - powerpc64-linux-*) HOST_LDFLAGS="${HOST_LDFLAGS} -Wl,-z,noexecstack" ;; -esac - # Make $HOST-specific native binaries from depends available in $PATH export PATH="${BASEPREFIX}/${HOST}/native/bin:${PATH}" mkdir -p "$DISTSRC" diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm index 34a9c608db..36d8dddab6 100644 --- a/contrib/guix/manifest.scm +++ b/contrib/guix/manifest.scm @@ -200,10 +200,14 @@ chain for " target " development.")) (package-with-extra-patches base-nsis (search-our-patches "nsis-gcc-10-memmove.patch"))) +(define (fix-ppc64-nx-default lief) + (package-with-extra-patches lief + (search-our-patches "lief-fix-ppc64-nx-default.patch"))) + (define-public lief (package (name "python-lief") - (version "0.12.0") + (version "0.12.1") (source (origin (method git-fetch) @@ -213,8 +217,15 @@ chain for " target " development.")) (file-name (git-file-name name version)) (sha256 (base32 - "026jchj56q25v6gc0754dj9cj5hz5zaza8ij93y5ga94w20kzm9q")))) + "1xzbh3bxy4rw1yamnx68da1v5s56ay4g081cyamv67256g0qy2i1")))) (build-system python-build-system) + (arguments + `(#:phases + (modify-phases %standard-phases + (add-after 'unpack 'parallel-jobs + ;; build with multiple cores + (lambda _ + (substitute* "setup.py" (("self.parallel if self.parallel else 1") (number->string (parallel-job-count))))))))) (native-inputs `(("cmake" ,cmake))) (home-page "https://github.com/lief-project/LIEF") @@ -581,7 +592,7 @@ inspecting signatures in Mach-O binaries.") xz ;; Build tools gnu-make - libtool + libtool-2.4.7 autoconf-2.71 automake pkg-config @@ -595,7 +606,7 @@ inspecting signatures in Mach-O binaries.") ;; Git git ;; Tests - lief) + (fix-ppc64-nx-default lief)) (let ((target (getenv "HOST"))) (cond ((string-suffix? "-mingw32" target) ;; Windows diff --git a/contrib/guix/patches/lief-fix-ppc64-nx-default.patch b/contrib/guix/patches/lief-fix-ppc64-nx-default.patch new file mode 100644 index 0000000000..101bc1ddc0 --- /dev/null +++ b/contrib/guix/patches/lief-fix-ppc64-nx-default.patch @@ -0,0 +1,29 @@ +Correct default for Binary::has_nx on ppc64 + +From the Linux kernel source: + + * This is the default if a program doesn't have a PT_GNU_STACK + * program header entry. The PPC64 ELF ABI has a non executable stack + * stack by default, so in the absence of a PT_GNU_STACK program header + * we turn execute permission off. + +This patch can be dropped the next time we update LIEF. + +diff --git a/src/ELF/Binary.cpp b/src/ELF/Binary.cpp +index a90be1ab..fd2d9764 100644 +--- a/src/ELF/Binary.cpp ++++ b/src/ELF/Binary.cpp +@@ -1084,7 +1084,12 @@ bool Binary::has_nx() const { + return segment->type() == SEGMENT_TYPES::PT_GNU_STACK; + }); + if (it_stack == std::end(segments_)) { +- return false; ++ if (header().machine_type() == ARCH::EM_PPC64) { ++ // The PPC64 ELF ABI has a non-executable stack by default. ++ return true; ++ } else { ++ return false; ++ } + } + + return !(*it_stack)->has(ELF_SEGMENT_FLAGS::PF_X); diff --git a/contrib/macdeploy/README.md b/contrib/macdeploy/README.md index 64e36f22b9..599a0bfa6c 100644 --- a/contrib/macdeploy/README.md +++ b/contrib/macdeploy/README.md @@ -20,7 +20,7 @@ can be extracted from [Xcode_12.2.xip](https://download.developer.apple.com/Developer_Tools/Xcode_12.2/Xcode_12.2.xip). Alternatively, after logging in to your account go to 'Downloads', then 'More' -and search for [`Xcode_12.2`](https://developer.apple.com/download/all/?q=Xcode%2012.2). +and search for [`Xcode 12.2`](https://developer.apple.com/download/all/?q=Xcode%2012.2). An Apple ID and cookies enabled for the hostname are needed to download this. diff --git a/depends/Makefile b/depends/Makefile index 5a1c472a15..11fdd6dd53 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -144,8 +144,8 @@ include packages/packages.mk # 2. Before including packages/*.mk (excluding packages/packages.mk), since # they rely on the build_id variables # -build_id:=$(shell env CC='$(build_CC)' CXX='$(build_CXX)' AR='$(build_AR)' RANLIB='$(build_RANLIB)' STRIP='$(build_STRIP)' SHA256SUM='$(build_SHA256SUM)' DEBUG='$(DEBUG)' LTO='$(LTO)' ./gen_id '$(BUILD_ID_SALT)' 'GUIX_ENVIRONMENT=$(realpath $(GUIX_ENVIRONMENT))') -$(host_arch)_$(host_os)_id:=$(shell env CC='$(host_CC)' CXX='$(host_CXX)' AR='$(host_AR)' RANLIB='$(host_RANLIB)' STRIP='$(host_STRIP)' SHA256SUM='$(build_SHA256SUM)' DEBUG='$(DEBUG)' LTO='$(LTO)' ./gen_id '$(HOST_ID_SALT)' 'GUIX_ENVIRONMENT=$(realpath $(GUIX_ENVIRONMENT))') +build_id:=$(shell env CC='$(build_CC)' C_STANDARD='$(C_STANDARD)' CXX='$(build_CXX)' CXX_STANDARD='$(CXX_STANDARD)' AR='$(build_AR)' RANLIB='$(build_RANLIB)' STRIP='$(build_STRIP)' SHA256SUM='$(build_SHA256SUM)' DEBUG='$(DEBUG)' LTO='$(LTO)' ./gen_id '$(BUILD_ID_SALT)' 'GUIX_ENVIRONMENT=$(realpath $(GUIX_ENVIRONMENT))') +$(host_arch)_$(host_os)_id:=$(shell env CC='$(host_CC)' C_STANDARD='$(C_STANDARD)' CXX='$(host_CXX)' CXX_STANDARD='$(CXX_STANDARD)' AR='$(host_AR)' RANLIB='$(host_RANLIB)' STRIP='$(host_STRIP)' SHA256SUM='$(build_SHA256SUM)' DEBUG='$(DEBUG)' LTO='$(LTO)' ./gen_id '$(HOST_ID_SALT)' 'GUIX_ENVIRONMENT=$(realpath $(GUIX_ENVIRONMENT))') qrencode_packages_$(NO_QR) = $(qrencode_$(host_os)_packages) diff --git a/depends/gen_id b/depends/gen_id index a0cd586461..7caf8d764d 100755 --- a/depends/gen_id +++ b/depends/gen_id @@ -1,7 +1,8 @@ #!/usr/bin/env bash -# Usage: env [ CC=... ] [ CXX=... ] [ AR=... ] [ RANLIB=... ] [ STRIP=... ] \ -# [ DEBUG=... ] [ LTO=... ] ./build-id [ID_SALT]... +# Usage: env [ CC=... ] [ C_STANDARD=...] [ CXX=... ] [CXX_STANDARD=...] \ +# [ AR=... ] [ RANLIB=... ] [ STRIP=... ] [ DEBUG=... ] \ +# [ LTO=... ] ./build-id [ID_SALT]... # # Prints to stdout a SHA256 hash representing the current toolset, used by # depends/Makefile as a build id for caching purposes (detecting when the @@ -39,12 +40,14 @@ bash -c "${CC} -v" bash -c "${CC} -v -E -xc -o /dev/null - < /dev/null" bash -c "${CC} -v -E -xobjective-c -o /dev/null - < /dev/null" + echo "C_STANDARD=${C_STANDARD}" echo "END CC" echo "BEGIN CXX" bash -c "${CXX} -v" bash -c "${CXX} -v -E -xc++ -o /dev/null - < /dev/null" bash -c "${CXX} -v -E -xobjective-c++ -o /dev/null - < /dev/null" + echo "CXX_STANDARD=${CXX_STANDARD}" echo "END CXX" echo "BEGIN AR" diff --git a/depends/packages/libxkbcommon.mk b/depends/packages/libxkbcommon.mk index 8c6c56545f..bcdcf671f7 100644 --- a/depends/packages/libxkbcommon.mk +++ b/depends/packages/libxkbcommon.mk @@ -5,9 +5,14 @@ $(package)_file_name=$(package)-$($(package)_version).tar.xz $(package)_sha256_hash=60ddcff932b7fd352752d51a5c4f04f3d0403230a584df9a2e0d5ed87c486c8b $(package)_dependencies=libxcb +# This package explicitly enables -Werror=array-bounds, which causes build failures +# with GCC 12.1+. Work around that by turning errors back into warnings. +# This workaround would be dropped if the package was updated, as that would require +# a different build system (Meson) define $(package)_set_vars $(package)_config_opts = --enable-option-checking --disable-dependency-tracking $(package)_config_opts += --disable-static --disable-docs +$(package)_cflags += -Wno-error=array-bounds endef define $(package)_preprocess_cmds diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 2bc3a81430..7e268fe9a5 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -171,7 +171,7 @@ $(package)_config_opts_mingw32 += -no-freetype $(package)_config_opts_mingw32 += -xplatform win32-g++ $(package)_config_opts_mingw32 += "QMAKE_CFLAGS = '$($(package)_cflags) $($(package)_cppflags)'" $(package)_config_opts_mingw32 += "QMAKE_CXX = '$($(package)_cxx)'" -$(package)_config_opts_mingw32 += "QMAKE_CXXFLAGS = '$($(package)_cflags) $($(package)_cppflags)'" +$(package)_config_opts_mingw32 += "QMAKE_CXXFLAGS = '$($(package)_cxxflags) $($(package)_cppflags)'" $(package)_config_opts_mingw32 += "QMAKE_LFLAGS = '$($(package)_ldflags)'" $(package)_config_opts_mingw32 += -device-option CROSS_COMPILE="$(host)-" $(package)_config_opts_mingw32 += -pch diff --git a/depends/packages/sqlite.mk b/depends/packages/sqlite.mk index 126781ceeb..820d724214 100644 --- a/depends/packages/sqlite.mk +++ b/depends/packages/sqlite.mk @@ -1,8 +1,8 @@ package=sqlite -$(package)_version=3320100 -$(package)_download_path=https://sqlite.org/2020/ +$(package)_version=3380500 +$(package)_download_path=https://sqlite.org/2022/ $(package)_file_name=sqlite-autoconf-$($(package)_version).tar.gz -$(package)_sha256_hash=486748abfb16abd8af664e3a5f03b228e5f124682b0c942e157644bf6fff7d10 +$(package)_sha256_hash=5af07de982ba658fd91a03170c945f99c971f6955bc79df3266544373e39869c define $(package)_set_vars $(package)_config_opts=--disable-shared --disable-readline --disable-dynamic-extensions --enable-option-checking diff --git a/doc/README.md b/doc/README.md index 33f71f4807..31c95afab0 100644 --- a/doc/README.md +++ b/doc/README.md @@ -64,6 +64,7 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th - [BIPS](bips.md) - [Dnsseed Policy](dnsseed-policy.md) - [Benchmarking](benchmarking.md) +- [Internal Design Docs](design/) ### Resources * Discuss on the [BitcoinTalk](https://bitcointalk.org/) forums, in the [Development & Technical Discussion board](https://bitcointalk.org/index.php?board=6.0). @@ -71,7 +72,6 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th ### Miscellaneous - [Assets Attribution](assets-attribution.md) -- [Assumeutxo design](assumeutxo.md) - [bitcoin.conf Configuration File](bitcoin-conf.md) - [CJDNS Support](cjdns.md) - [Files](files.md) diff --git a/doc/build-unix.md b/doc/build-unix.md index f02729ee32..bcfa25dc76 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -277,22 +277,17 @@ A list of additional configure flags can be displayed with: Setup and Build Example: Arch Linux ----------------------------------- -This example lists the steps necessary to setup and build a command line only, non-wallet distribution of the latest changes on Arch Linux: +This example lists the steps necessary to setup and build a command line only distribution of the latest changes on Arch Linux: - pacman -S git base-devel boost libevent python + pacman --sync --needed autoconf automake boost gcc git libevent libtool make pkgconf python sqlite git clone https://github.com/bitcoin/bitcoin.git cd bitcoin/ ./autogen.sh - ./configure --disable-wallet --without-gui --without-miniupnpc + ./configure make check + ./src/bitcoind -Note: -Enabling wallet support requires either compiling against a Berkeley DB newer than 4.8 (package `db`) using `--with-incompatible-bdb`, -or building and depending on a local version of Berkeley DB 4.8. The readily available Arch Linux packages are currently built using -`--with-incompatible-bdb` according to the [PKGBUILD](https://github.com/archlinux/svntogit-community/blob/packages/bitcoin/trunk/PKGBUILD). -As mentioned above, when maintaining portability of the wallet between the standard Bitcoin Core distributions and independently built -node software is desired, Berkeley DB 4.8 must be used. - +If you intend to work with legacy Berkeley DB wallets, see [Berkeley DB](#berkeley-db) section. ARM Cross-compilation ------------------- diff --git a/doc/assumeutxo.md b/doc/design/assumeutxo.md index 2726cf779b..2726cf779b 100644 --- a/doc/assumeutxo.md +++ b/doc/design/assumeutxo.md diff --git a/doc/design/libraries.md b/doc/design/libraries.md new file mode 100644 index 0000000000..75f8d60ba0 --- /dev/null +++ b/doc/design/libraries.md @@ -0,0 +1,104 @@ +# Libraries + +| Name | Description | +|--------------------------|-------------| +| *libbitcoin_cli* | RPC client functionality used by *bitcoin-cli* executable | +| *libbitcoin_common* | Home for common functionality shared by different executables and libraries. Similar to *libbitcoin_util*, but higher-level (see [Dependencies](#dependencies)). | +| *libbitcoin_consensus* | Stable, backwards-compatible consensus functionality used by *libbitcoin_node* and *libbitcoin_wallet* and also exposed as a [shared library](../shared-libraries.md). | +| *libbitcoinconsensus* | Shared library build of static *libbitcoin_consensus* library | +| *libbitcoin_kernel* | Consensus engine and support library used for validation by *libbitcoin_node* and also exposed as a [shared library](../shared-libraries.md). | +| *libbitcoinqt* | GUI functionality used by *bitcoin-qt* and *bitcoin-gui* executables | +| *libbitcoin_ipc* | IPC functionality used by *bitcoin-node*, *bitcoin-wallet*, *bitcoin-gui* executables to communicate when [`--enable-multiprocess`](multiprocess.md) is used. | +| *libbitcoin_node* | P2P and RPC server functionality used by *bitcoind* and *bitcoin-qt* executables. | +| *libbitcoin_util* | Home for common functionality shared by different executables and libraries. Similar to *libbitcoin_common*, but lower-level (see [Dependencies](#dependencies)). | +| *libbitcoin_wallet* | Wallet functionality used by *bitcoind* and *bitcoin-wallet* executables. | +| *libbitcoin_wallet_tool* | Lower-level wallet functionality used by *bitcoin-wallet* executable. | +| *libbitcoin_zmq* | [ZeroMQ](../zmq.md) functionality used by *bitcoind* and *bitcoin-qt* executables. | + +## Conventions + +- Most libraries are internal libraries and have APIs which are completely unstable! There are few or no restrictions on backwards compatibility or rules about external dependencies. Exceptions are *libbitcoin_consensus* and *libbitcoin_kernel* which have external interfaces documented at [../shared-libraries.md](../shared-libraries.md). + +- Generally each library should have a corresponding source directory and namespace. Source code organization is a work in progress, so it is true that some namespaces are applied inconsistently, and if you look at [`libbitcoin_*_SOURCES`](../../src/Makefile.am) lists you can see that many libraries pull in files from outside their source directory. But when working with libraries, it is good to follow a consistent pattern like: + + - *libbitcoin_node* code lives in `src/node/` in the `node::` namespace + - *libbitcoin_wallet* code lives in `src/wallet/` in the `wallet::` namespace + - *libbitcoin_ipc* code lives in `src/ipc/` in the `ipc::` namespace + - *libbitcoin_util* code lives in `src/util/` in the `util::` namespace + - *libbitcoin_consensus* code lives in `src/consensus/` in the `Consensus::` namespace + +## Dependencies + +- Libraries should minimize what other libraries they depend on, and only reference symbols following the arrows shown in the dependency graph below: + +<table><tr><td> + +```mermaid + +%%{ init : { "flowchart" : { "curve" : "linear" }}}%% + +graph TD; + +bitcoin-cli[bitcoin-cli]-->libbitcoin_cli; + +bitcoind[bitcoind]-->libbitcoin_node; +bitcoind[bitcoind]-->libbitcoin_wallet; + +bitcoin-qt[bitcoin-qt]-->libbitcoin_node; +bitcoin-qt[bitcoin-qt]-->libbitcoinqt; +bitcoin-qt[bitcoin-qt]-->libbitcoin_wallet; + +bitcoin-wallet[bitcoin-wallet]-->libbitcoin_wallet; +bitcoin-wallet[bitcoin-wallet]-->libbitcoin_wallet_tool; + +libbitcoin_cli-->libbitcoin_common; +libbitcoin_cli-->libbitcoin_util; + +libbitcoin_common-->libbitcoin_util; +libbitcoin_common-->libbitcoin_consensus; + +libbitcoin_kernel-->libbitcoin_consensus; +libbitcoin_kernel-->libbitcoin_util; + +libbitcoin_node-->libbitcoin_common; +libbitcoin_node-->libbitcoin_consensus; +libbitcoin_node-->libbitcoin_kernel; +libbitcoin_node-->libbitcoin_util; + +libbitcoinqt-->libbitcoin_common; +libbitcoinqt-->libbitcoin_util; + +libbitcoin_wallet-->libbitcoin_common; +libbitcoin_wallet-->libbitcoin_util; + +libbitcoin_wallet_tool-->libbitcoin_util; +libbitcoin_wallet_tool-->libbitcoin_wallet; + +classDef bold stroke-width:2px, font-weight:bold, font-size: smaller; +class bitcoin-qt,bitcoind,bitcoin-cli,bitcoin-wallet bold +``` +</td></tr><tr><td> + +**Dependency graph**. Arrows show linker symbol dependencies. *Consensus* lib depends on nothing. *Util* lib is depended on by everything. *Kernel* lib depends only on consensus and util. + +</td></tr></table> + +- The graph shows what _linker symbols_ (functions and variables) from each library other libraries can call and reference directly, but it is not a call graph. For example, there is no arrow connecting *libbitcoin_wallet* and *libbitcoin_node* libraries, because these libraries are intended to be modular and not depend on each other's internal implementation details. But wallet code still is still able to call node code indirectly through the `interfaces::Chain` abstract class in [`interfaces/chain.h`](../../src/interfaces/chain.h) and node code calls wallet code through the `interfaces::ChainClient` and `interfaces::Chain::Notifications` abstract classes in the same file. In general, defining abstract classes in [`src/interfaces/`](../../src/interfaces/) can be a convenient way of avoiding unwanted direct dependencies or circular dependencies between libraries. + +- *libbitcoin_consensus* should be a standalone dependency that any library can depend on, and it should not depend on any other libraries itself. + +- *libbitcoin_util* should also be a standalone dependency that any library can depend on, and it should not depend on other internal libraries. + +- *libbitcoin_common* should serve a similar function as *libbitcoin_util* and be a place for miscellaneous code used by various daemon, GUI, and CLI applications and libraries to live. It should not depend on anything other than *libbitcoin_util* and *libbitcoin_consensus*. The boundary between _util_ and _common_ is a little fuzzy but historically _util_ has been used for more generic, lower-level things like parsing hex, and _common_ has been used for bitcoin-specific, higher-level things like parsing base58. The difference between util and common is mostly important because *libbitcoin_kernel* is not supposed to depend on *libbitcoin_common*, only *libbitcoin_util*. In general, if it is ever unclear whether it is better to add code to *util* or *common*, it is probably better to add it to *common* unless it is very generically useful or useful particularly to include in the kernel. + + +- *libbitcoin_kernel* should only depend on *libbitcoin_util* and *libbitcoin_consensus*. + +- The only thing that should depend on *libbitcoin_kernel* internally should be *libbitcoin_node*. GUI and wallet libraries *libbitcoinqt* and *libbitcoin_wallet* in particular should not depend on *libbitcoin_kernel* and the unneeded functionality it would pull in, like block validation. To the extent that GUI and wallet code need scripting and signing functionality, they should be get able it from *libbitcoin_consensus*, *libbitcoin_common*, and *libbitcoin_util*, instead of *libbitcoin_kernel*. + +- GUI, node, and wallet code internal implementations should all be independent of each other, and the *libbitcoinqt*, *libbitcoin_node*, *libbitcoin_wallet* libraries should never reference each other's symbols. They should only call each other through [`src/interfaces/`](`../../src/interfaces/`) abstract interfaces. + +## Work in progress + +- Validation code is moving from *libbitcoin_node* to *libbitcoin_kernel* as part of [The libbitcoinkernel Project #24303](https://github.com/bitcoin/bitcoin/issues/24303) +- Source code organization is discussed in general in [Library source code organization #15732](https://github.com/bitcoin/bitcoin/issues/15732) diff --git a/doc/multiprocess.md b/doc/design/multiprocess.md index e3f389a6d3..e3f389a6d3 100644 --- a/doc/multiprocess.md +++ b/doc/design/multiprocess.md diff --git a/src/Makefile.am b/src/Makefile.am index 16c29a16b7..488ff0e273 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1059,9 +1059,7 @@ include Makefile.leveldb.include include Makefile.test_util.include include Makefile.test_fuzz.include -if ENABLE_TESTS include Makefile.test.include -endif if ENABLE_BENCH include Makefile.bench.include diff --git a/src/Makefile.leveldb.include b/src/Makefile.leveldb.include index 066f8940c5..bf14fe206b 100644 --- a/src/Makefile.leveldb.include +++ b/src/Makefile.leveldb.include @@ -13,7 +13,6 @@ LIBMEMENV = $(LIBMEMENV_INT) LEVELDB_CPPFLAGS = LEVELDB_CPPFLAGS += -I$(srcdir)/leveldb/include -LEVELDB_CPPFLAGS += -I$(srcdir)/leveldb/helpers/memenv LEVELDB_CPPFLAGS_INT = LEVELDB_CPPFLAGS_INT += -I$(srcdir)/leveldb diff --git a/src/Makefile.test.include b/src/Makefile.test.include index ebd9e860cf..098feacb3d 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -6,7 +6,7 @@ if ENABLE_FUZZ_BINARY noinst_PROGRAMS += test/fuzz/fuzz endif -if !ENABLE_FUZZ +if ENABLE_TESTS bin_PROGRAMS += test/test_bitcoin endif @@ -371,7 +371,7 @@ endif endif $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C secp256k1 check -if !ENABLE_FUZZ +if ENABLE_TESTS UNIVALUE_TESTS = univalue/test/object univalue/test/unitester univalue/test/no_nul noinst_PROGRAMS += $(UNIVALUE_TESTS) TESTS += $(UNIVALUE_TESTS) diff --git a/src/bench/wallet_loading.cpp b/src/bench/wallet_loading.cpp index 38d3460001..f611383788 100644 --- a/src/bench/wallet_loading.cpp +++ b/src/bench/wallet_loading.cpp @@ -17,20 +17,19 @@ #include <optional> using wallet::CWallet; +using wallet::DatabaseFormat; using wallet::DatabaseOptions; -using wallet::DatabaseStatus; using wallet::ISMINE_SPENDABLE; using wallet::MakeWalletDatabase; +using wallet::TxStateInactive; using wallet::WALLET_FLAG_DESCRIPTORS; using wallet::WalletContext; +using wallet::WalletDatabase; -static const std::shared_ptr<CWallet> BenchLoadWallet(WalletContext& context, DatabaseOptions& options) +static const std::shared_ptr<CWallet> BenchLoadWallet(std::unique_ptr<WalletDatabase> database, WalletContext& context, DatabaseOptions& options) { - DatabaseStatus status; bilingual_str error; std::vector<bilingual_str> warnings; - auto database = MakeWalletDatabase("", options, status, error); - assert(database); auto wallet = CWallet::Create(context, "", std::move(database), options.create_flags, error, warnings); NotifyWalletLoaded(context, wallet); if (context.chain) { @@ -46,9 +45,47 @@ static void BenchUnloadWallet(std::shared_ptr<CWallet>&& wallet) UnloadWallet(std::move(wallet)); } +static void AddTx(CWallet& wallet) +{ + bilingual_str error; + CTxDestination dest; + wallet.GetNewDestination(OutputType::BECH32, "", dest, error); + + CMutableTransaction mtx; + mtx.vout.push_back({COIN, GetScriptForDestination(dest)}); + mtx.vin.push_back(CTxIn()); + + wallet.AddToWallet(MakeTransactionRef(mtx), TxStateInactive{}); +} + +static std::unique_ptr<WalletDatabase> DuplicateMockDatabase(WalletDatabase& database, DatabaseOptions& options) +{ + auto new_database = CreateMockWalletDatabase(options); + + // Get a cursor to the original database + auto batch = database.MakeBatch(); + batch->StartCursor(); + + // Get a batch for the new database + auto new_batch = new_database->MakeBatch(); + + // Read all records from the original database and write them to the new one + while (true) { + CDataStream key(SER_DISK, CLIENT_VERSION); + CDataStream value(SER_DISK, CLIENT_VERSION); + bool complete; + batch->ReadAtCursor(key, value, complete); + if (complete) break; + new_batch->Write(key, value); + } + + return new_database; +} + static void WalletLoading(benchmark::Bench& bench, bool legacy_wallet) { const auto test_setup = MakeNoLogFileContext<TestingSetup>(); + test_setup->m_args.ForceSetArg("-unsafesqlitesync", "1"); WalletContext context; context.args = &test_setup->m_args; @@ -57,27 +94,40 @@ static void WalletLoading(benchmark::Bench& bench, bool legacy_wallet) // Setup the wallet // Loading the wallet will also create it DatabaseOptions options; - if (!legacy_wallet) options.create_flags = WALLET_FLAG_DESCRIPTORS; - auto wallet = BenchLoadWallet(context, options); + if (legacy_wallet) { + options.require_format = DatabaseFormat::BERKELEY; + } else { + options.create_flags = WALLET_FLAG_DESCRIPTORS; + options.require_format = DatabaseFormat::SQLITE; + } + auto database = CreateMockWalletDatabase(options); + auto wallet = BenchLoadWallet(std::move(database), context, options); // Generate a bunch of transactions and addresses to put into the wallet - for (int i = 0; i < 5000; ++i) { - generatetoaddress(test_setup->m_node, getnewaddress(*wallet)); + for (int i = 0; i < 1000; ++i) { + AddTx(*wallet); } + database = DuplicateMockDatabase(wallet->GetDatabase(), options); + // reload the wallet for the actual benchmark BenchUnloadWallet(std::move(wallet)); - bench.minEpochIterations(10).run([&] { - wallet = BenchLoadWallet(context, options); + bench.epochs(5).run([&] { + wallet = BenchLoadWallet(std::move(database), context, options); // Cleanup + database = DuplicateMockDatabase(wallet->GetDatabase(), options); BenchUnloadWallet(std::move(wallet)); }); } +#ifdef USE_BDB static void WalletLoadingLegacy(benchmark::Bench& bench) { WalletLoading(bench, /*legacy_wallet=*/true); } -static void WalletLoadingDescriptors(benchmark::Bench& bench) { WalletLoading(bench, /*legacy_wallet=*/false); } - BENCHMARK(WalletLoadingLegacy); +#endif + +#ifdef USE_SQLITE +static void WalletLoadingDescriptors(benchmark::Bench& bench) { WalletLoading(bench, /*legacy_wallet=*/false); } BENCHMARK(WalletLoadingDescriptors); +#endif diff --git a/src/chain.h b/src/chain.h index 24b5026aba..ecc2ae732f 100644 --- a/src/chain.h +++ b/src/chain.h @@ -138,7 +138,7 @@ enum BlockStatus : uint32_t { * If set, this indicates that the block index entry is assumed-valid. * Certain diagnostics will be skipped in e.g. CheckBlockIndex(). * It almost certainly means that the block's full validation is pending - * on a background chainstate. See `doc/assumeutxo.md`. + * on a background chainstate. See `doc/design/assumeutxo.md`. */ BLOCK_ASSUMED_VALID = 256, }; diff --git a/src/coins.h b/src/coins.h index de297dd427..67fecc9785 100644 --- a/src/coins.h +++ b/src/coins.h @@ -142,7 +142,6 @@ public: virtual bool GetKey(COutPoint &key) const = 0; virtual bool GetValue(Coin &coin) const = 0; - virtual unsigned int GetValueSize() const = 0; virtual bool Valid() const = 0; virtual void Next() = 0; diff --git a/src/compat.h b/src/compat.h index f41c501c84..0a44b98b4e 100644 --- a/src/compat.h +++ b/src/compat.h @@ -11,9 +11,6 @@ #endif #ifdef WIN32 -#ifndef NOMINMAX -#define NOMINMAX -#endif #ifdef FD_SETSIZE #undef FD_SETSIZE // prevent redefinition compiler warning #endif diff --git a/src/consensus/consensus.h b/src/consensus/consensus.h index 788fa4e55b..b2a31e3ba4 100644 --- a/src/consensus/consensus.h +++ b/src/consensus/consensus.h @@ -26,7 +26,5 @@ static const size_t MIN_SERIALIZABLE_TRANSACTION_WEIGHT = WITNESS_SCALE_FACTOR * /** Flags for nSequence and nLockTime locks */ /** Interpret sequence numbers as relative lock-time constraints. */ static constexpr unsigned int LOCKTIME_VERIFY_SEQUENCE = (1 << 0); -/** Use GetMedianTimePast() instead of nTime for end point timestamp. */ -static constexpr unsigned int LOCKTIME_MEDIAN_TIME_PAST = (1 << 1); #endif // BITCOIN_CONSENSUS_CONSENSUS_H diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp index a2f1f32780..d4a8e4f35a 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -10,7 +10,7 @@ #include <leveldb/cache.h> #include <leveldb/env.h> #include <leveldb/filter_policy.h> -#include <memenv.h> +#include <leveldb/helpers/memenv/memenv.h> #include <stdint.h> #include <algorithm> diff --git a/src/dbwrapper.h b/src/dbwrapper.h index 1109cb5888..cef8426d61 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -166,11 +166,6 @@ public: } return true; } - - unsigned int GetValueSize() { - return piter->value().size(); - } - }; class CDBWrapper @@ -318,22 +313,6 @@ public: pdb->GetApproximateSizes(&range, 1, &size); return size; } - - /** - * Compact a certain range of keys in the database. - */ - template<typename K> - void CompactRange(const K& key_begin, const K& key_end) const - { - CDataStream ssKey1(SER_DISK, CLIENT_VERSION), ssKey2(SER_DISK, CLIENT_VERSION); - ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); - ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); - ssKey1 << key_begin; - ssKey2 << key_end; - leveldb::Slice slKey1((const char*)ssKey1.data(), ssKey1.size()); - leveldb::Slice slKey2((const char*)ssKey2.data(), ssKey2.size()); - pdb->CompactRange(&slKey1, &slKey2); - } }; #endif // BITCOIN_DBWRAPPER_H diff --git a/src/fs.cpp b/src/fs.cpp index b61115bf01..74b167e313 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -12,9 +12,6 @@ #include <sys/utsname.h> #include <unistd.h> #else -#ifndef NOMINMAX -#define NOMINMAX -#endif #include <codecvt> #include <limits> #include <windows.h> diff --git a/src/i2p.cpp b/src/i2p.cpp index caff8c1e69..8611984555 100644 --- a/src/i2p.cpp +++ b/src/i2p.cpp @@ -410,7 +410,7 @@ void Session::Disconnect() Log("Destroying session %s", m_session_id); } } - m_control_sock->Reset(); + m_control_sock = std::make_unique<Sock>(INVALID_SOCKET); m_session_id.clear(); } } // namespace sam diff --git a/src/net.cpp b/src/net.cpp index d42f130af7..7f4e571c8d 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -250,7 +250,7 @@ std::optional<CAddress> GetLocalAddrForPeer(CNode *pnode) if (pnode->IsInboundConn()) { // For inbound connections, assume both the address and the port // as seen from the peer. - addrLocal = CAddress{pnode->GetAddrLocal(), addrLocal.nServices}; + addrLocal = CAddress{pnode->GetAddrLocal(), addrLocal.nServices, addrLocal.nTime}; } else { // For outbound connections, assume just the address as seen from // the peer and leave the port in `addrLocal` as returned by @@ -422,13 +422,13 @@ bool CConnman::CheckIncomingNonce(uint64_t nonce) } /** Get the bind address for a socket as CAddress */ -static CAddress GetBindAddress(SOCKET sock) +static CAddress GetBindAddress(const Sock& sock) { CAddress addr_bind; struct sockaddr_storage sockaddr_bind; socklen_t sockaddr_bind_len = sizeof(sockaddr_bind); - if (sock != INVALID_SOCKET) { - if (!getsockname(sock, (struct sockaddr*)&sockaddr_bind, &sockaddr_bind_len)) { + if (sock.Get() != INVALID_SOCKET) { + if (!sock.GetSockName((struct sockaddr*)&sockaddr_bind, &sockaddr_bind_len)) { addr_bind.SetSockAddr((const struct sockaddr*)&sockaddr_bind); } else { LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "getsockname failed\n"); @@ -540,7 +540,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); if (!addr_bind.IsValid()) { - addr_bind = GetBindAddress(sock->Get()); + addr_bind = GetBindAddress(*sock); } CNode* pnode = new CNode(id, nLocalServices, @@ -1154,7 +1154,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { addr = CAddress{MaybeFlipIPv6toCJDNS(addr), NODE_NONE}; } - const CAddress addr_bind{MaybeFlipIPv6toCJDNS(GetBindAddress(sock->Get())), NODE_NONE}; + const CAddress addr_bind{MaybeFlipIPv6toCJDNS(GetBindAddress(*sock)), NODE_NONE}; NetPermissionFlags permissionFlags = NetPermissionFlags::None; hListenSocket.AddSocketPermissionFlags(permissionFlags); @@ -2323,8 +2323,7 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError, #endif } - if (::bind(sock->Get(), (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR) - { + if (sock->Bind(reinterpret_cast<struct sockaddr*>(&sockaddr), len) == SOCKET_ERROR) { int nErr = WSAGetLastError(); if (nErr == WSAEADDRINUSE) strError = strprintf(_("Unable to bind to %s on this computer. %s is probably already running."), addrBind.ToString(), PACKAGE_NAME); @@ -2336,7 +2335,7 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError, LogPrintf("Bound to %s\n", addrBind.ToString()); // Listen for incoming connections - if (listen(sock->Get(), SOMAXCONN) == SOCKET_ERROR) + if (sock->Listen(SOMAXCONN) == SOCKET_ERROR) { strError = strprintf(_("Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 14cc9c169d..db711be130 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -493,7 +493,7 @@ public: void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); void RelayTransaction(const uint256& txid, const uint256& wtxid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); void SetBestHeight(int height) override { m_best_height = height; }; - void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void UnitTestMisbehaving(NodeId peer_id, int howmuch) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex) { Misbehaving(*Assert(GetPeerRef(peer_id)), howmuch, ""); }; void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex); @@ -518,6 +518,12 @@ private: PeerRef RemovePeer(NodeId id) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** + * Increment peer's misbehavior score. If the new value >= DISCOURAGEMENT_THRESHOLD, mark the node + * to be discouraged, meaning the peer might be disconnected and added to the discouragement filter. + */ + void Misbehaving(Peer& peer, int howmuch, const std::string& message); + + /** * Potentially mark a node discouraged based on the contents of a BlockValidationState object * * @param[in] via_compact_block this bool is passed in because net_processing should @@ -550,13 +556,12 @@ private: void ProcessOrphanTx(std::set<uint256>& orphan_work_set) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** Process a single headers message from a peer. */ - void ProcessHeadersMessage(CNode& pfrom, const Peer& peer, + void ProcessHeadersMessage(CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers, bool via_compact_block) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); - void SendBlockTransactions(CNode& pfrom, const CBlock& block, const BlockTransactionsRequest& req) - EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void SendBlockTransactions(CNode& pfrom, Peer& peer, const CBlock& block, const BlockTransactionsRequest& req); /** Register with TxRequestTracker that an INV has been received from a * peer. The announcement parameters are decided in PeerManager and then @@ -1444,33 +1449,31 @@ void PeerManagerImpl::AddToCompactExtraTransactions(const CTransactionRef& tx) vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % max_extra_txn; } -void PeerManagerImpl::Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) +void PeerManagerImpl::Misbehaving(Peer& peer, int howmuch, const std::string& message) { assert(howmuch > 0); - PeerRef peer = GetPeerRef(pnode); - if (peer == nullptr) return; - - LOCK(peer->m_misbehavior_mutex); - const int score_before{peer->m_misbehavior_score}; - peer->m_misbehavior_score += howmuch; - const int score_now{peer->m_misbehavior_score}; + LOCK(peer.m_misbehavior_mutex); + const int score_before{peer.m_misbehavior_score}; + peer.m_misbehavior_score += howmuch; + const int score_now{peer.m_misbehavior_score}; const std::string message_prefixed = message.empty() ? "" : (": " + message); std::string warning; if (score_now >= DISCOURAGEMENT_THRESHOLD && score_before < DISCOURAGEMENT_THRESHOLD) { warning = " DISCOURAGE THRESHOLD EXCEEDED"; - peer->m_should_discourage = true; + peer.m_should_discourage = true; } LogPrint(BCLog::NET, "Misbehaving: peer=%d (%d -> %d)%s%s\n", - pnode, score_before, score_now, warning, message_prefixed); + peer.m_id, score_before, score_now, warning, message_prefixed); } bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state, bool via_compact_block, const std::string& message) { + PeerRef peer{GetPeerRef(nodeid)}; switch (state.GetResult()) { case BlockValidationResult::BLOCK_RESULT_UNSET: break; @@ -1478,7 +1481,7 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati case BlockValidationResult::BLOCK_CONSENSUS: case BlockValidationResult::BLOCK_MUTATED: if (!via_compact_block) { - Misbehaving(nodeid, 100, message); + if (peer) Misbehaving(*peer, 100, message); return true; } break; @@ -1493,7 +1496,7 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati // Discourage outbound (but not inbound) peers if on an invalid chain. // Exempt HB compact block peers. Manual connections are always protected from discouragement. if (!via_compact_block && !node_state->m_is_inbound) { - Misbehaving(nodeid, 100, message); + if (peer) Misbehaving(*peer, 100, message); return true; } break; @@ -1501,12 +1504,12 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati case BlockValidationResult::BLOCK_INVALID_HEADER: case BlockValidationResult::BLOCK_CHECKPOINT: case BlockValidationResult::BLOCK_INVALID_PREV: - Misbehaving(nodeid, 100, message); + if (peer) Misbehaving(*peer, 100, message); return true; // Conflicting (but not necessarily invalid) data or different policy: case BlockValidationResult::BLOCK_MISSING_PREV: // TODO: Handle this much more gracefully (10 DoS points is super arbitrary) - Misbehaving(nodeid, 10, message); + if (peer) Misbehaving(*peer, 10, message); return true; case BlockValidationResult::BLOCK_RECENT_CONSENSUS_CHANGE: case BlockValidationResult::BLOCK_TIME_FUTURE: @@ -1520,12 +1523,13 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati bool PeerManagerImpl::MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message) { + PeerRef peer{GetPeerRef(nodeid)}; switch (state.GetResult()) { case TxValidationResult::TX_RESULT_UNSET: break; // The node is providing invalid data: case TxValidationResult::TX_CONSENSUS: - Misbehaving(nodeid, 100, message); + if (peer) Misbehaving(*peer, 100, message); return true; // Conflicting (but not necessarily invalid) data or different policy: case TxValidationResult::TX_RECENT_CONSENSUS_CHANGE: @@ -2175,12 +2179,12 @@ uint32_t PeerManagerImpl::GetFetchFlags(const CNode& pfrom) const EXCLUSIVE_LOCK return nFetchFlags; } -void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, const CBlock& block, const BlockTransactionsRequest& req) +void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, Peer& peer, const CBlock& block, const BlockTransactionsRequest& req) { BlockTransactions resp(req); for (size_t i = 0; i < req.indexes.size(); i++) { if (req.indexes[i] >= block.vtx.size()) { - Misbehaving(pfrom.GetId(), 100, "getblocktxn with out-of-bounds tx indices"); + Misbehaving(peer, 100, "getblocktxn with out-of-bounds tx indices"); return; } resp.txn[i] = block.vtx[req.indexes[i]]; @@ -2190,7 +2194,7 @@ void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, const CBlock& block, c m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCKTXN, resp)); } -void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, +void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers, bool via_compact_block) { @@ -2208,7 +2212,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, LOCK(cs_main); CNodeState *nodestate = State(pfrom.GetId()); - // If this looks like it could be a block announcement (nCount < + // If this looks like it could be a block announcement (nCount <= // MAX_BLOCKS_TO_ANNOUNCE), use special logic for handling headers that // don't connect: // - Send a getheaders message in response to try to connect the chain. @@ -2216,7 +2220,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // don't connect before giving DoS points // - Once a headers message is received that is valid and does connect, // nUnconnectingHeaders gets reset back to 0. - if (!m_chainman.m_blockman.LookupBlockIndex(headers[0].hashPrevBlock) && nCount < MAX_BLOCKS_TO_ANNOUNCE) { + if (!m_chainman.m_blockman.LookupBlockIndex(headers[0].hashPrevBlock) && nCount <= MAX_BLOCKS_TO_ANNOUNCE) { nodestate->nUnconnectingHeaders++; m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), uint256())); LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n", @@ -2230,7 +2234,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, UpdateBlockAvailability(pfrom.GetId(), headers.back().GetHash()); if (nodestate->nUnconnectingHeaders % MAX_UNCONNECTING_HEADERS == 0) { - Misbehaving(pfrom.GetId(), 20, strprintf("%d non-connecting headers", nodestate->nUnconnectingHeaders)); + Misbehaving(peer, 20, strprintf("%d non-connecting headers", nodestate->nUnconnectingHeaders)); } return; } @@ -2238,7 +2242,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, uint256 hashLastBlock; for (const CBlockHeader& header : headers) { if (!hashLastBlock.IsNull() && header.hashPrevBlock != hashLastBlock) { - Misbehaving(pfrom.GetId(), 20, "non-continuous headers sequence"); + Misbehaving(peer, 20, "non-continuous headers sequence"); return; } hashLastBlock = header.GetHash(); @@ -3001,7 +3005,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (vAddr.size() > MAX_ADDR_TO_SEND) { - Misbehaving(pfrom.GetId(), 20, strprintf("%s message size = %u", msg_type, vAddr.size())); + Misbehaving(*peer, 20, strprintf("%s message size = %u", msg_type, vAddr.size())); return; } @@ -3015,7 +3019,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (peer->m_addr_token_bucket < MAX_ADDR_PROCESSING_TOKEN_BUCKET) { // Don't increment bucket if it's already full const auto time_diff = std::max(current_time - peer->m_addr_token_timestamp, 0us); - const double increment = CountSecondsDouble(time_diff) * MAX_ADDR_RATE_PER_SECOND; + const double increment = Ticks<SecondsDouble>(time_diff) * MAX_ADDR_RATE_PER_SECOND; peer->m_addr_token_bucket = std::min<double>(peer->m_addr_token_bucket + increment, MAX_ADDR_PROCESSING_TOKEN_BUCKET); } peer->m_addr_token_timestamp = current_time; @@ -3082,7 +3086,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, vRecv >> vInv; if (vInv.size() > MAX_INV_SZ) { - Misbehaving(pfrom.GetId(), 20, strprintf("inv message size = %u", vInv.size())); + Misbehaving(*peer, 20, strprintf("inv message size = %u", vInv.size())); return; } @@ -3150,7 +3154,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, vRecv >> vInv; if (vInv.size() > MAX_INV_SZ) { - Misbehaving(pfrom.GetId(), 20, strprintf("getdata message size = %u", vInv.size())); + Misbehaving(*peer, 20, strprintf("getdata message size = %u", vInv.size())); return; } @@ -3248,7 +3252,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // Unlock m_most_recent_block_mutex to avoid cs_main lock inversion } if (recent_block) { - SendBlockTransactions(pfrom, *recent_block, req); + SendBlockTransactions(pfrom, *peer, *recent_block, req); return; } @@ -3266,7 +3270,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, bool ret = ReadBlockFromDisk(block, pindex, m_chainparams.GetConsensus()); assert(ret); - SendBlockTransactions(pfrom, block, req); + SendBlockTransactions(pfrom, *peer, block, req); return; } } @@ -3685,7 +3689,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, ReadStatus status = partialBlock.InitData(cmpctblock, vExtraTxnForCompact); if (status == READ_STATUS_INVALID) { RemoveBlockRequest(pindex->GetBlockHash()); // Reset in-flight state in case Misbehaving does not result in a disconnect - Misbehaving(pfrom.GetId(), 100, "invalid compact block"); + Misbehaving(*peer, 100, "invalid compact block"); return; } else if (status == READ_STATUS_FAILED) { // Duplicate txindexes, the block is now in-flight, so just request it @@ -3812,7 +3816,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, ReadStatus status = partialBlock.FillBlock(*pblock, resp.txn); if (status == READ_STATUS_INVALID) { RemoveBlockRequest(resp.blockhash); // Reset in-flight state in case Misbehaving does not result in a disconnect - Misbehaving(pfrom.GetId(), 100, "invalid compact block/non-matching block transactions"); + Misbehaving(*peer, 100, "invalid compact block/non-matching block transactions"); return; } else if (status == READ_STATUS_FAILED) { // Might have collided, fall back to getdata now :( @@ -3873,7 +3877,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // Bypass the normal CBlock deserialization, as we don't want to risk deserializing 2000 full blocks. unsigned int nCount = ReadCompactSize(vRecv); if (nCount > MAX_HEADERS_RESULTS) { - Misbehaving(pfrom.GetId(), 20, strprintf("headers message size = %u", nCount)); + Misbehaving(*peer, 20, strprintf("headers message size = %u", nCount)); return; } headers.resize(nCount); @@ -4067,7 +4071,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (!filter.IsWithinSizeConstraints()) { // There is no excuse for sending a too-large filter - Misbehaving(pfrom.GetId(), 100, "too-large bloom filter"); + Misbehaving(*peer, 100, "too-large bloom filter"); } else if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { { LOCK(tx_relay->m_bloom_filter_mutex); @@ -4075,6 +4079,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, tx_relay->m_relay_txs = true; } pfrom.m_bloom_filter_loaded = true; + pfrom.m_relays_txs = true; } return; } @@ -4102,7 +4107,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } } if (bad) { - Misbehaving(pfrom.GetId(), 100, "bad filteradd message"); + Misbehaving(*peer, 100, "bad filteradd message"); } return; } @@ -4772,7 +4777,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // Try sending block announcements via headers // { - // If we have less than MAX_BLOCKS_TO_ANNOUNCE in our + // If we have no more than MAX_BLOCKS_TO_ANNOUNCE in our // list of block hashes we're relaying, and our peer wants // headers announcements, then find the first header // not yet known to our peer but would connect, and send. diff --git a/src/net_processing.h b/src/net_processing.h index d5c73e6c79..5fbae98c27 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -71,12 +71,8 @@ public: /** Set the best height */ virtual void SetBestHeight(int height) = 0; - /** - * Increment peer's misbehavior score. If the new value >= DISCOURAGEMENT_THRESHOLD, mark the node - * to be discouraged, meaning the peer might be disconnected and added to the discouragement filter. - * Public for unit testing. - */ - virtual void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) = 0; + /* Public for unit testing. */ + virtual void UnitTestMisbehaving(NodeId peer_id, int howmuch) = 0; /** * Evict extra outbound peers. If we think our tip may be stale, connect to an extra outbound. diff --git a/src/policy/policy.h b/src/policy/policy.h index e255708eff..cd98a601a3 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -99,8 +99,7 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS{MANDATORY_SCRIPT_VERI static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS{STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS}; /** Used as the flags parameter to sequence and nLocktime checks in non-consensus code. */ -static constexpr unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS{LOCKTIME_VERIFY_SEQUENCE | - LOCKTIME_MEDIAN_TIME_PAST}; +static constexpr unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS{LOCKTIME_VERIFY_SEQUENCE}; CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee); diff --git a/src/psbt.cpp b/src/psbt.cpp index c1c8a385cc..36fec74bc9 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -113,6 +113,24 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const for (const auto& key_pair : hd_keypaths) { sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair); } + if (!m_tap_key_sig.empty()) { + sigdata.taproot_key_path_sig = m_tap_key_sig; + } + for (const auto& [pubkey_leaf, sig] : m_tap_script_sigs) { + sigdata.taproot_script_sigs.emplace(pubkey_leaf, sig); + } + if (!m_tap_internal_key.IsNull()) { + sigdata.tr_spenddata.internal_key = m_tap_internal_key; + } + if (!m_tap_merkle_root.IsNull()) { + sigdata.tr_spenddata.merkle_root = m_tap_merkle_root; + } + for (const auto& [leaf_script, control_block] : m_tap_scripts) { + sigdata.tr_spenddata.scripts.emplace(leaf_script, control_block); + } + for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) { + sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin); + } } void PSBTInput::FromSignatureData(const SignatureData& sigdata) @@ -142,13 +160,30 @@ void PSBTInput::FromSignatureData(const SignatureData& sigdata) for (const auto& entry : sigdata.misc_pubkeys) { hd_keypaths.emplace(entry.second); } + if (!sigdata.taproot_key_path_sig.empty()) { + m_tap_key_sig = sigdata.taproot_key_path_sig; + } + for (const auto& [pubkey_leaf, sig] : sigdata.taproot_script_sigs) { + m_tap_script_sigs.emplace(pubkey_leaf, sig); + } + if (!sigdata.tr_spenddata.internal_key.IsNull()) { + m_tap_internal_key = sigdata.tr_spenddata.internal_key; + } + if (!sigdata.tr_spenddata.merkle_root.IsNull()) { + m_tap_merkle_root = sigdata.tr_spenddata.merkle_root; + } + for (const auto& [leaf_script, control_block] : sigdata.tr_spenddata.scripts) { + m_tap_scripts.emplace(leaf_script, control_block); + } + for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) { + m_tap_bip32_paths.emplace(pubkey, leaf_origin); + } } void PSBTInput::Merge(const PSBTInput& input) { if (!non_witness_utxo && input.non_witness_utxo) non_witness_utxo = input.non_witness_utxo; if (witness_utxo.IsNull() && !input.witness_utxo.IsNull()) { - // TODO: For segwit v1, we will want to clear out the non-witness utxo when setting a witness one. For v0 and non-segwit, this is not safe witness_utxo = input.witness_utxo; } @@ -159,11 +194,17 @@ void PSBTInput::Merge(const PSBTInput& input) hash256_preimages.insert(input.hash256_preimages.begin(), input.hash256_preimages.end()); hd_keypaths.insert(input.hd_keypaths.begin(), input.hd_keypaths.end()); unknown.insert(input.unknown.begin(), input.unknown.end()); + m_tap_script_sigs.insert(input.m_tap_script_sigs.begin(), input.m_tap_script_sigs.end()); + m_tap_scripts.insert(input.m_tap_scripts.begin(), input.m_tap_scripts.end()); + m_tap_bip32_paths.insert(input.m_tap_bip32_paths.begin(), input.m_tap_bip32_paths.end()); if (redeem_script.empty() && !input.redeem_script.empty()) redeem_script = input.redeem_script; if (witness_script.empty() && !input.witness_script.empty()) witness_script = input.witness_script; if (final_script_sig.empty() && !input.final_script_sig.empty()) final_script_sig = input.final_script_sig; if (final_script_witness.IsNull() && !input.final_script_witness.IsNull()) final_script_witness = input.final_script_witness; + if (m_tap_key_sig.empty() && !input.m_tap_key_sig.empty()) m_tap_key_sig = input.m_tap_key_sig; + if (m_tap_internal_key.IsNull() && !input.m_tap_internal_key.IsNull()) m_tap_internal_key = input.m_tap_internal_key; + if (m_tap_merkle_root.IsNull() && !input.m_tap_merkle_root.IsNull()) m_tap_merkle_root = input.m_tap_merkle_root; } void PSBTOutput::FillSignatureData(SignatureData& sigdata) const @@ -177,6 +218,15 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const for (const auto& key_pair : hd_keypaths) { sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair); } + if (m_tap_tree.has_value() && m_tap_internal_key.IsFullyValid()) { + TaprootSpendData spenddata = m_tap_tree->GetSpendData(); + + sigdata.tr_spenddata.internal_key = m_tap_internal_key; + sigdata.tr_spenddata.Merge(spenddata); + } + for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) { + sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin); + } } void PSBTOutput::FromSignatureData(const SignatureData& sigdata) @@ -190,6 +240,15 @@ void PSBTOutput::FromSignatureData(const SignatureData& sigdata) for (const auto& entry : sigdata.misc_pubkeys) { hd_keypaths.emplace(entry.second); } + if (!sigdata.tr_spenddata.internal_key.IsNull()) { + m_tap_internal_key = sigdata.tr_spenddata.internal_key; + } + if (sigdata.tr_builder.has_value()) { + m_tap_tree = sigdata.tr_builder; + } + for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) { + m_tap_bip32_paths.emplace(pubkey, leaf_origin); + } } bool PSBTOutput::IsNull() const @@ -201,9 +260,12 @@ void PSBTOutput::Merge(const PSBTOutput& output) { hd_keypaths.insert(output.hd_keypaths.begin(), output.hd_keypaths.end()); unknown.insert(output.unknown.begin(), output.unknown.end()); + m_tap_bip32_paths.insert(output.m_tap_bip32_paths.begin(), output.m_tap_bip32_paths.end()); if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script; if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script; + if (m_tap_internal_key.IsNull() && !output.m_tap_internal_key.IsNull()) m_tap_internal_key = output.m_tap_internal_key; + if (m_tap_tree.has_value() && !output.m_tap_tree.has_value()) m_tap_tree = output.m_tap_tree; } bool PSBTInputSigned(const PSBTInput& input) { @@ -313,10 +375,11 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& input.FromSignatureData(sigdata); // If we have a witness signature, put a witness UTXO. - // TODO: For segwit v1, we should remove the non_witness_utxo if (sigdata.witness) { input.witness_utxo = utxo; - // input.non_witness_utxo = nullptr; + // We can remove the non_witness_utxo if and only if there are no non-segwit or segwit v0 + // inputs in this transaction. Since this requires inspecting the entire transaction, this + // is something for the caller to deal with (i.e. FillPSBT). } // Fill in the missing info diff --git a/src/psbt.h b/src/psbt.h index 4a6d41076f..a143a99988 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -40,12 +40,21 @@ static constexpr uint8_t PSBT_IN_RIPEMD160 = 0x0A; static constexpr uint8_t PSBT_IN_SHA256 = 0x0B; static constexpr uint8_t PSBT_IN_HASH160 = 0x0C; static constexpr uint8_t PSBT_IN_HASH256 = 0x0D; +static constexpr uint8_t PSBT_IN_TAP_KEY_SIG = 0x13; +static constexpr uint8_t PSBT_IN_TAP_SCRIPT_SIG = 0x14; +static constexpr uint8_t PSBT_IN_TAP_LEAF_SCRIPT = 0x15; +static constexpr uint8_t PSBT_IN_TAP_BIP32_DERIVATION = 0x16; +static constexpr uint8_t PSBT_IN_TAP_INTERNAL_KEY = 0x17; +static constexpr uint8_t PSBT_IN_TAP_MERKLE_ROOT = 0x18; static constexpr uint8_t PSBT_IN_PROPRIETARY = 0xFC; // Output types static constexpr uint8_t PSBT_OUT_REDEEMSCRIPT = 0x00; static constexpr uint8_t PSBT_OUT_WITNESSSCRIPT = 0x01; static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02; +static constexpr uint8_t PSBT_OUT_TAP_INTERNAL_KEY = 0x05; +static constexpr uint8_t PSBT_OUT_TAP_TREE = 0x06; +static constexpr uint8_t PSBT_OUT_TAP_BIP32_DERIVATION = 0x07; static constexpr uint8_t PSBT_OUT_PROPRIETARY = 0xFC; // The separator is 0x00. Reading this in means that the unserializer can interpret it @@ -97,22 +106,30 @@ void UnserializeFromVector(Stream& s, X&... args) } } -// Deserialize an individual HD keypath to a stream +// Deserialize bytes of given length from the stream as a KeyOriginInfo template<typename Stream> -void DeserializeHDKeypath(Stream& s, KeyOriginInfo& hd_keypath) +KeyOriginInfo DeserializeKeyOrigin(Stream& s, uint64_t length) { // Read in key path - uint64_t value_len = ReadCompactSize(s); - if (value_len % 4 || value_len == 0) { + if (length % 4 || length == 0) { throw std::ios_base::failure("Invalid length for HD key path"); } + KeyOriginInfo hd_keypath; s >> hd_keypath.fingerprint; - for (unsigned int i = 4; i < value_len; i += sizeof(uint32_t)) { + for (unsigned int i = 4; i < length; i += sizeof(uint32_t)) { uint32_t index; s >> index; hd_keypath.path.push_back(index); } + return hd_keypath; +} + +// Deserialize a length prefixed KeyOriginInfo from a stream +template<typename Stream> +void DeserializeHDKeypath(Stream& s, KeyOriginInfo& hd_keypath) +{ + hd_keypath = DeserializeKeyOrigin(s, ReadCompactSize(s)); } // Deserialize HD keypaths into a map @@ -139,17 +156,24 @@ void DeserializeHDKeypaths(Stream& s, const std::vector<unsigned char>& key, std hd_keypaths.emplace(pubkey, std::move(keypath)); } -// Serialize an individual HD keypath to a stream +// Serialize a KeyOriginInfo to a stream template<typename Stream> -void SerializeHDKeypath(Stream& s, KeyOriginInfo hd_keypath) +void SerializeKeyOrigin(Stream& s, KeyOriginInfo hd_keypath) { - WriteCompactSize(s, (hd_keypath.path.size() + 1) * sizeof(uint32_t)); s << hd_keypath.fingerprint; for (const auto& path : hd_keypath.path) { s << path; } } +// Serialize a length prefixed KeyOriginInfo to a stream +template<typename Stream> +void SerializeHDKeypath(Stream& s, KeyOriginInfo hd_keypath) +{ + WriteCompactSize(s, (hd_keypath.path.size() + 1) * sizeof(uint32_t)); + SerializeKeyOrigin(s, hd_keypath); +} + // Serialize HD keypaths to a stream from a map template<typename Stream> void SerializeHDKeypaths(Stream& s, const std::map<CPubKey, KeyOriginInfo>& hd_keypaths, CompactSizeWriter type) @@ -178,6 +202,15 @@ struct PSBTInput std::map<uint256, std::vector<unsigned char>> sha256_preimages; std::map<uint160, std::vector<unsigned char>> hash160_preimages; std::map<uint256, std::vector<unsigned char>> hash256_preimages; + + // Taproot fields + std::vector<unsigned char> m_tap_key_sig; + std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> m_tap_script_sigs; + std::map<std::pair<CScript, int>, std::set<std::vector<unsigned char>, ShortestVectorFirstComparator>> m_tap_scripts; + std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths; + XOnlyPubKey m_tap_internal_key; + uint256 m_tap_merkle_root; + std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; std::set<PSBTProprietary> m_proprietary; std::optional<int> sighash_type; @@ -252,6 +285,53 @@ struct PSBTInput SerializeToVector(s, CompactSizeWriter(PSBT_IN_HASH256), Span{hash}); s << preimage; } + + // Write taproot key sig + if (!m_tap_key_sig.empty()) { + SerializeToVector(s, PSBT_IN_TAP_KEY_SIG); + s << m_tap_key_sig; + } + + // Write taproot script sigs + for (const auto& [pubkey_leaf, sig] : m_tap_script_sigs) { + const auto& [xonly, leaf_hash] = pubkey_leaf; + SerializeToVector(s, PSBT_IN_TAP_SCRIPT_SIG, xonly, leaf_hash); + s << sig; + } + + // Write taproot leaf scripts + for (const auto& [leaf, control_blocks] : m_tap_scripts) { + const auto& [script, leaf_ver] = leaf; + for (const auto& control_block : control_blocks) { + SerializeToVector(s, PSBT_IN_TAP_LEAF_SCRIPT, Span{control_block}); + std::vector<unsigned char> value_v(script.begin(), script.end()); + value_v.push_back((uint8_t)leaf_ver); + s << value_v; + } + } + + // Write taproot bip32 keypaths + for (const auto& [xonly, leaf_origin] : m_tap_bip32_paths) { + const auto& [leaf_hashes, origin] = leaf_origin; + SerializeToVector(s, PSBT_IN_TAP_BIP32_DERIVATION, xonly); + std::vector<unsigned char> value; + CVectorWriter s_value(s.GetType(), s.GetVersion(), value, 0); + s_value << leaf_hashes; + SerializeKeyOrigin(s_value, origin); + s << value; + } + + // Write taproot internal key + if (!m_tap_internal_key.IsNull()) { + SerializeToVector(s, PSBT_IN_TAP_INTERNAL_KEY); + s << ToByteVector(m_tap_internal_key); + } + + // Write taproot merkle root + if (!m_tap_merkle_root.IsNull()) { + SerializeToVector(s, PSBT_IN_TAP_MERKLE_ROOT); + SerializeToVector(s, m_tap_merkle_root); + } } // Write script sig @@ -488,6 +568,103 @@ struct PSBTInput hash256_preimages.emplace(hash, std::move(preimage)); break; } + case PSBT_IN_TAP_KEY_SIG: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot key signature already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Input Taproot key signature key is more than one byte type"); + } + s >> m_tap_key_sig; + if (m_tap_key_sig.size() < 64) { + throw std::ios_base::failure("Input Taproot key path signature is shorter than 64 bytes"); + } else if (m_tap_key_sig.size() > 65) { + throw std::ios_base::failure("Input Taproot key path signature is longer than 65 bytes"); + } + break; + } + case PSBT_IN_TAP_SCRIPT_SIG: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot script signature already provided"); + } else if (key.size() != 65) { + throw std::ios_base::failure("Input Taproot script signature key is not 65 bytes"); + } + SpanReader s_key(s.GetType(), s.GetVersion(), Span{key}.subspan(1)); + XOnlyPubKey xonly; + uint256 hash; + s_key >> xonly; + s_key >> hash; + std::vector<unsigned char> sig; + s >> sig; + if (sig.size() < 64) { + throw std::ios_base::failure("Input Taproot script path signature is shorter than 64 bytes"); + } else if (sig.size() > 65) { + throw std::ios_base::failure("Input Taproot script path signature is longer than 65 bytes"); + } + m_tap_script_sigs.emplace(std::make_pair(xonly, hash), sig); + break; + } + case PSBT_IN_TAP_LEAF_SCRIPT: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot leaf script already provided"); + } else if (key.size() < 34) { + throw std::ios_base::failure("Taproot leaf script key is not at least 34 bytes"); + } else if ((key.size() - 2) % 32 != 0) { + throw std::ios_base::failure("Input Taproot leaf script key's control block size is not valid"); + } + std::vector<unsigned char> script_v; + s >> script_v; + if (script_v.empty()) { + throw std::ios_base::failure("Input Taproot leaf script must be at least 1 byte"); + } + uint8_t leaf_ver = script_v.back(); + script_v.pop_back(); + const auto leaf_script = std::make_pair(CScript(script_v.begin(), script_v.end()), (int)leaf_ver); + m_tap_scripts[leaf_script].insert(std::vector<unsigned char>(key.begin() + 1, key.end())); + break; + } + case PSBT_IN_TAP_BIP32_DERIVATION: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot BIP32 keypath already provided"); + } else if (key.size() != 33) { + throw std::ios_base::failure("Input Taproot BIP32 keypath key is not at 33 bytes"); + } + SpanReader s_key(s.GetType(), s.GetVersion(), Span{key}.subspan(1)); + XOnlyPubKey xonly; + s_key >> xonly; + std::set<uint256> leaf_hashes; + uint64_t value_len = ReadCompactSize(s); + size_t before_hashes = s.size(); + s >> leaf_hashes; + size_t after_hashes = s.size(); + size_t hashes_len = before_hashes - after_hashes; + size_t origin_len = value_len - hashes_len; + m_tap_bip32_paths.emplace(xonly, std::make_pair(leaf_hashes, DeserializeKeyOrigin(s, origin_len))); + break; + } + case PSBT_IN_TAP_INTERNAL_KEY: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot internal key already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Input Taproot internal key key is more than one byte type"); + } + UnserializeFromVector(s, m_tap_internal_key); + break; + } + case PSBT_IN_TAP_MERKLE_ROOT: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot merkle root already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Input Taproot merkle root key is more than one byte type"); + } + UnserializeFromVector(s, m_tap_merkle_root); + break; + } case PSBT_IN_PROPRIETARY: { PSBTProprietary this_prop; @@ -532,6 +709,9 @@ struct PSBTOutput CScript redeem_script; CScript witness_script; std::map<CPubKey, KeyOriginInfo> hd_keypaths; + XOnlyPubKey m_tap_internal_key; + std::optional<TaprootBuilder> m_tap_tree; + std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths; std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; std::set<PSBTProprietary> m_proprietary; @@ -564,6 +744,40 @@ struct PSBTOutput s << entry.value; } + // Write taproot internal key + if (!m_tap_internal_key.IsNull()) { + SerializeToVector(s, PSBT_OUT_TAP_INTERNAL_KEY); + s << ToByteVector(m_tap_internal_key); + } + + // Write taproot tree + if (m_tap_tree.has_value()) { + SerializeToVector(s, PSBT_OUT_TAP_TREE); + std::vector<unsigned char> value; + CVectorWriter s_value(s.GetType(), s.GetVersion(), value, 0); + const auto& tuples = m_tap_tree->GetTreeTuples(); + for (const auto& tuple : tuples) { + uint8_t depth = std::get<0>(tuple); + uint8_t leaf_ver = std::get<1>(tuple); + CScript script = std::get<2>(tuple); + s_value << depth; + s_value << leaf_ver; + s_value << script; + } + s << value; + } + + // Write taproot bip32 keypaths + for (const auto& [xonly, leaf] : m_tap_bip32_paths) { + const auto& [leaf_hashes, origin] = leaf; + SerializeToVector(s, PSBT_OUT_TAP_BIP32_DERIVATION, xonly); + std::vector<unsigned char> value; + CVectorWriter s_value(s.GetType(), s.GetVersion(), value, 0); + s_value << leaf_hashes; + SerializeKeyOrigin(s_value, origin); + s << value; + } + // Write unknown things for (auto& entry : unknown) { s << entry.first; @@ -624,6 +838,59 @@ struct PSBTOutput DeserializeHDKeypaths(s, key, hd_keypaths); break; } + case PSBT_OUT_TAP_INTERNAL_KEY: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, output Taproot internal key already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Output Taproot internal key key is more than one byte type"); + } + UnserializeFromVector(s, m_tap_internal_key); + break; + } + case PSBT_OUT_TAP_TREE: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, output Taproot tree already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Output Taproot tree key is more than one byte type"); + } + m_tap_tree.emplace(); + std::vector<unsigned char> tree_v; + s >> tree_v; + SpanReader s_tree(s.GetType(), s.GetVersion(), tree_v); + while (!s_tree.empty()) { + uint8_t depth; + uint8_t leaf_ver; + CScript script; + s_tree >> depth; + s_tree >> leaf_ver; + s_tree >> script; + m_tap_tree->Add((int)depth, script, (int)leaf_ver, true /* track */); + } + if (!m_tap_tree->IsComplete()) { + throw std::ios_base::failure("Output Taproot tree is malformed"); + } + break; + } + case PSBT_OUT_TAP_BIP32_DERIVATION: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, output Taproot BIP32 keypath already provided"); + } else if (key.size() != 33) { + throw std::ios_base::failure("Output Taproot BIP32 keypath key is not at 33 bytes"); + } + XOnlyPubKey xonly(uint256({key.begin() + 1, key.begin() + 33})); + std::set<uint256> leaf_hashes; + uint64_t value_len = ReadCompactSize(s); + size_t before_hashes = s.size(); + s >> leaf_hashes; + size_t after_hashes = s.size(); + size_t hashes_len = before_hashes - after_hashes; + size_t origin_len = value_len - hashes_len; + m_tap_bip32_paths.emplace(xonly, std::make_pair(leaf_hashes, DeserializeKeyOrigin(s, origin_len))); + break; + } case PSBT_OUT_PROPRIETARY: { PSBTProprietary this_prop; @@ -652,6 +919,11 @@ struct PSBTOutput } } + // Finalize m_tap_tree so that all of the computed things are computed + if (m_tap_tree.has_value() && m_tap_tree->IsComplete() && m_tap_internal_key.IsFullyValid()) { + m_tap_tree->Finalize(m_tap_internal_key); + } + if (!found_sep) { throw std::ios_base::failure("Separator is missing at the end of an output map"); } diff --git a/src/pubkey.h b/src/pubkey.h index dfe06f834c..463efe1b00 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -286,6 +286,9 @@ public: bool operator==(const XOnlyPubKey& other) const { return m_keydata == other.m_keydata; } bool operator!=(const XOnlyPubKey& other) const { return m_keydata != other.m_keydata; } bool operator<(const XOnlyPubKey& other) const { return m_keydata < other.m_keydata; } + + //! Implement serialization without length prefixes since it is a fixed length + SERIALIZE_METHODS(XOnlyPubKey, obj) { READWRITE(obj.m_keydata); } }; struct CExtPubKey { diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index f11ddad30f..27d3a1b9e2 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -96,7 +96,11 @@ static void RegisterMetaTypes() qRegisterMetaType<QMessageBox::Icon>("QMessageBox::Icon"); qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo"); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) qRegisterMetaTypeStreamOperators<BitcoinUnit>("BitcoinUnit"); +#else + qRegisterMetaType<BitcoinUnit>("BitcoinUnit"); +#endif } static QString GetLangTerritory() @@ -135,21 +139,30 @@ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTrans // - First load the translator for the base language, without territory // - Then load the more specific locale translator +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + const QString translation_path{QLibraryInfo::location(QLibraryInfo::TranslationsPath)}; +#else + const QString translation_path{QLibraryInfo::path(QLibraryInfo::TranslationsPath)}; +#endif // Load e.g. qt_de.qm - if (qtTranslatorBase.load("qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + if (qtTranslatorBase.load("qt_" + lang, translation_path)) { QApplication::installTranslator(&qtTranslatorBase); + } // Load e.g. qt_de_DE.qm - if (qtTranslator.load("qt_" + lang_territory, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + if (qtTranslator.load("qt_" + lang_territory, translation_path)) { QApplication::installTranslator(&qtTranslator); + } // Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in bitcoin.qrc) - if (translatorBase.load(lang, ":/translations/")) + if (translatorBase.load(lang, ":/translations/")) { QApplication::installTranslator(&translatorBase); + } // Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in bitcoin.qrc) - if (translator.load(lang_territory, ":/translations/")) + if (translator.load(lang_territory, ":/translations/")) { QApplication::installTranslator(&translator); + } } static bool InitSettings() @@ -517,9 +530,11 @@ int GuiMain(int argc, char* argv[]) Q_INIT_RESOURCE(bitcoin); Q_INIT_RESOURCE(bitcoin_locale); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) // Generate high-dpi pixmaps QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif #if defined(QT_QPA_PLATFORM_ANDROID) QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 6fea8e1cba..d65fc58865 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -999,6 +999,7 @@ void BitcoinGUI::openOptionsDialogWithTab(OptionsDialog::Tab tab) auto dlg = new OptionsDialog(this, enableWallet); connect(dlg, &OptionsDialog::quitOnReset, this, &BitcoinGUI::quitRequested); dlg->setCurrentTab(tab); + dlg->setClientModel(clientModel); dlg->setModel(clientModel->getOptionsModel()); GUIUtil::ShowModalDialogAsynchronously(dlg); } diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui index 934363af1f..ffebc316b1 100644 --- a/src/qt/forms/sendcoinsentry.ui +++ b/src/qt/forms/sendcoinsentry.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>SendCoinsEntry</class> - <widget class="QStackedWidget" name="SendCoinsEntry"> + <widget class="QWidget" name="SendCoinsEntry"> <property name="geometry"> <rect> <x>0</x> @@ -16,1244 +16,204 @@ <property name="autoFillBackground"> <bool>false</bool> </property> - <widget class="QFrame" name="SendCoins"> - <property name="frameShape"> - <enum>QFrame::NoFrame</enum> + <layout class="QGridLayout" name="gridLayout"> + <property name="topMargin"> + <number>8</number> </property> - <layout class="QGridLayout" name="gridLayout"> - <property name="topMargin"> - <number>8</number> - </property> - <property name="bottomMargin"> - <number>4</number> - </property> - <property name="horizontalSpacing"> - <number>12</number> - </property> - <property name="verticalSpacing"> - <number>8</number> - </property> - <item row="0" column="0"> - <widget class="QLabel" name="payToLabel"> - <property name="text"> - <string>Pay &To:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>payTo</cstring> - </property> - </widget> - </item> - <item row="0" column="1"> - <layout class="QHBoxLayout" name="payToLayout"> - <property name="spacing"> - <number>0</number> - </property> - <item> - <widget class="QValidatedLineEdit" name="payTo"> - <property name="toolTip"> - <string>The Bitcoin address to send the payment to</string> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="addressBookButton"> - <property name="toolTip"> - <string>Choose previously used address</string> - </property> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="../bitcoin.qrc"> - <normaloff>:/icons/address-book</normaloff>:/icons/address-book</iconset> - </property> - <property name="iconSize"> - <size> - <width>22</width> - <height>22</height> - </size> - </property> - <property name="shortcut"> - <string>Alt+A</string> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="pasteButton"> - <property name="toolTip"> - <string>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> - <property name="shortcut"> - <string>Alt+P</string> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="deleteButton"> - <property name="toolTip"> - <string>Remove this entry</string> - </property> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="../bitcoin.qrc"> - <normaloff>:/icons/remove</normaloff>:/icons/remove</iconset> - </property> - <property name="iconSize"> - <size> - <width>22</width> - <height>22</height> - </size> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="labellLabel"> - <property name="text"> - <string>&Label:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>addAsLabel</cstring> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="addAsLabel"> - <property name="toolTip"> - <string>Enter a label for this address to add it to the list of used addresses</string> - </property> - <property name="placeholderText"> - <string>Enter a label for this address to add it to the list of used addresses</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="amountLabel"> - <property name="text"> - <string>A&mount:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>payAmount</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <layout class="QHBoxLayout" name="horizontalLayoutAmount" stretch="0,1,0"> - <item> - <widget class="BitcoinAmountField" name="payAmount"> - <property name="toolTip"> - <string>The amount to send in the selected unit</string> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="checkboxSubtractFeeFromAmount"> - <property name="toolTip"> - <string>The fee will be deducted from the amount being sent. The recipient will receive less bitcoins than you enter in the amount field. If multiple recipients are selected, the fee is split equally.</string> - </property> - <property name="text"> - <string>S&ubtract fee from amount</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="useAvailableBalanceButton"> - <property name="text"> - <string>Use available balance</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="messageLabel"> - <property name="text"> - <string>Message:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QLabel" name="messageTextLabel"> - <property name="toolTip"> - <string>A message that was attached to the bitcoin: URI which will be stored with the transaction for your reference. Note: This message will not be sent over the Bitcoin network.</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - </widget> - </item> - <item row="4" column="0" colspan="2"> - <widget class="Line" name="line"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QFrame" name="SendCoins_UnauthenticatedPaymentRequest"> - <property name="palette"> - <palette> - <active> - <colorrole role="WindowText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Button"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Light"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="Midlight"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>191</blue> - </color> - </brush> - </colorrole> - <colorrole role="Dark"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>127</red> - <green>127</green> - <blue>63</blue> - </color> - </brush> - </colorrole> - <colorrole role="Mid"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>170</red> - <green>170</green> - <blue>84</blue> - </color> - </brush> - </colorrole> - <colorrole role="Text"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="BrightText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="ButtonText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Base"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="Window"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Shadow"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="AlternateBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>191</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>220</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - </active> - <inactive> - <colorrole role="WindowText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Button"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Light"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="Midlight"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>191</blue> - </color> - </brush> - </colorrole> - <colorrole role="Dark"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>127</red> - <green>127</green> - <blue>63</blue> - </color> - </brush> - </colorrole> - <colorrole role="Mid"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>170</red> - <green>170</green> - <blue>84</blue> - </color> - </brush> - </colorrole> - <colorrole role="Text"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="BrightText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="ButtonText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Base"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="Window"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Shadow"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="AlternateBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>191</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>220</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - </inactive> - <disabled> - <colorrole role="WindowText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>127</red> - <green>127</green> - <blue>63</blue> - </color> - </brush> - </colorrole> - <colorrole role="Button"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Light"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="Midlight"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>191</blue> - </color> - </brush> - </colorrole> - <colorrole role="Dark"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>127</red> - <green>127</green> - <blue>63</blue> - </color> - </brush> - </colorrole> - <colorrole role="Mid"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>170</red> - <green>170</green> - <blue>84</blue> - </color> - </brush> - </colorrole> - <colorrole role="Text"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>127</red> - <green>127</green> - <blue>63</blue> - </color> - </brush> - </colorrole> - <colorrole role="BrightText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="ButtonText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>127</red> - <green>127</green> - <blue>63</blue> - </color> - </brush> - </colorrole> - <colorrole role="Base"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Window"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Shadow"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="AlternateBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>220</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - </disabled> - </palette> + <property name="bottomMargin"> + <number>4</number> </property> - <property name="toolTip"> - <string>This is an unauthenticated payment request.</string> + <property name="horizontalSpacing"> + <number>12</number> </property> - <property name="autoFillBackground"> - <bool>true</bool> + <property name="verticalSpacing"> + <number>8</number> </property> - <property name="frameShape"> - <enum>QFrame::NoFrame</enum> - </property> - <layout class="QGridLayout" name="gridLayout_is"> - <property name="spacing"> - <number>12</number> - </property> - <item row="0" column="0"> - <widget class="QLabel" name="payToLabel_is"> - <property name="text"> - <string>Pay To:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="0" column="1"> - <layout class="QHBoxLayout" name="payToLayout_is"> - <property name="spacing"> - <number>0</number> - </property> - <item> - <widget class="QLabel" name="payTo_is"/> - </item> - <item> - <widget class="QToolButton" name="deleteButton_is"> - <property name="toolTip"> - <string>Remove this entry</string> - </property> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="../bitcoin.qrc"> - <normaloff>:/icons/remove</normaloff>:/icons/remove</iconset> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="memoLabel_is"> - <property name="text"> - <string>Memo:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLabel" name="memoTextLabel_is"> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="amountLabel_is"> - <property name="text"> - <string>A&mount:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>payAmount_is</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="BitcoinAmountField" name="payAmount_is"> - <property name="acceptDrops"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QFrame" name="SendCoins_AuthenticatedPaymentRequest"> - <property name="palette"> - <palette> - <active> - <colorrole role="WindowText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Button"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Light"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>230</red> - <green>255</green> - <blue>224</blue> - </color> - </brush> - </colorrole> - <colorrole role="Midlight"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>185</red> - <green>243</green> - <blue>171</blue> - </color> - </brush> - </colorrole> - <colorrole role="Dark"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>70</red> - <green>116</green> - <blue>59</blue> - </color> - </brush> - </colorrole> - <colorrole role="Mid"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>93</red> - <green>155</green> - <blue>79</blue> - </color> - </brush> - </colorrole> - <colorrole role="Text"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="BrightText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>155</red> - <green>255</green> - <blue>147</blue> - </color> - </brush> - </colorrole> - <colorrole role="ButtonText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Base"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>119</red> - <green>255</green> - <blue>233</blue> - </color> - </brush> - </colorrole> - <colorrole role="Window"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Shadow"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="AlternateBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>197</red> - <green>243</green> - <blue>187</blue> - </color> - </brush> - </colorrole> - <colorrole role="NoRole"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>125</red> - <green>194</green> - <blue>122</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>220</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - </active> - <inactive> - <colorrole role="WindowText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Button"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Light"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>230</red> - <green>255</green> - <blue>224</blue> - </color> - </brush> - </colorrole> - <colorrole role="Midlight"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>185</red> - <green>243</green> - <blue>171</blue> - </color> - </brush> - </colorrole> - <colorrole role="Dark"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>70</red> - <green>116</green> - <blue>59</blue> - </color> - </brush> - </colorrole> - <colorrole role="Mid"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>93</red> - <green>155</green> - <blue>79</blue> - </color> - </brush> - </colorrole> - <colorrole role="Text"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="BrightText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>155</red> - <green>255</green> - <blue>147</blue> - </color> - </brush> - </colorrole> - <colorrole role="ButtonText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Base"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>119</red> - <green>255</green> - <blue>233</blue> - </color> - </brush> - </colorrole> - <colorrole role="Window"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Shadow"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="AlternateBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>197</red> - <green>243</green> - <blue>187</blue> - </color> - </brush> - </colorrole> - <colorrole role="NoRole"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>125</red> - <green>194</green> - <blue>122</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>220</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - </inactive> - <disabled> - <colorrole role="WindowText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>70</red> - <green>116</green> - <blue>59</blue> - </color> - </brush> - </colorrole> - <colorrole role="Button"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Light"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>230</red> - <green>255</green> - <blue>224</blue> - </color> - </brush> - </colorrole> - <colorrole role="Midlight"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>185</red> - <green>243</green> - <blue>171</blue> - </color> - </brush> - </colorrole> - <colorrole role="Dark"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>70</red> - <green>116</green> - <blue>59</blue> - </color> - </brush> - </colorrole> - <colorrole role="Mid"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>93</red> - <green>155</green> - <blue>79</blue> - </color> - </brush> - </colorrole> - <colorrole role="Text"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>70</red> - <green>116</green> - <blue>59</blue> - </color> - </brush> - </colorrole> - <colorrole role="BrightText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>155</red> - <green>255</green> - <blue>147</blue> - </color> - </brush> - </colorrole> - <colorrole role="ButtonText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>70</red> - <green>116</green> - <blue>59</blue> - </color> - </brush> - </colorrole> - <colorrole role="Base"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Window"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Shadow"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="AlternateBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="NoRole"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>125</red> - <green>194</green> - <blue>122</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>220</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - </disabled> - </palette> - </property> - <property name="toolTip"> - <string>This is an authenticated payment request.</string> - </property> - <property name="autoFillBackground"> - <bool>true</bool> - </property> - <property name="frameShape"> - <enum>QFrame::NoFrame</enum> - </property> - <layout class="QGridLayout" name="gridLayout_s"> - <property name="spacing"> - <number>12</number> - </property> - <item row="0" column="0"> - <widget class="QLabel" name="payToLabel_s"> - <property name="text"> - <string>Pay To:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="0" column="1"> - <layout class="QHBoxLayout" name="payToLayout_s"> - <property name="spacing"> - <number>0</number> - </property> - <item> - <widget class="QLabel" name="payTo_s"> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="deleteButton_s"> - <property name="toolTip"> - <string>Remove this entry</string> - </property> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="../bitcoin.qrc"> - <normaloff>:/icons/remove</normaloff>:/icons/remove</iconset> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="memoLabel_s"> - <property name="text"> - <string>Memo:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLabel" name="memoTextLabel_s"> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="amountLabel_s"> - <property name="text"> - <string>A&mount:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>payAmount_s</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="BitcoinAmountField" name="payAmount_s"> - <property name="acceptDrops"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </widget> + <item row="0" column="0"> + <widget class="QLabel" name="payToLabel"> + <property name="text"> + <string>Pay &To:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="buddy"> + <cstring>payTo</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="payToLayout"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="QValidatedLineEdit" name="payTo"> + <property name="toolTip"> + <string>The Bitcoin address to send the payment to</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="addressBookButton"> + <property name="toolTip"> + <string>Choose previously used address</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../bitcoin.qrc"> + <normaloff>:/icons/address-book</normaloff>:/icons/address-book</iconset> + </property> + <property name="iconSize"> + <size> + <width>22</width> + <height>22</height> + </size> + </property> + <property name="shortcut"> + <string>Alt+A</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="pasteButton"> + <property name="toolTip"> + <string>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> + <property name="shortcut"> + <string>Alt+P</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="deleteButton"> + <property name="toolTip"> + <string>Remove this entry</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../bitcoin.qrc"> + <normaloff>:/icons/remove</normaloff>:/icons/remove</iconset> + </property> + <property name="iconSize"> + <size> + <width>22</width> + <height>22</height> + </size> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labellLabel"> + <property name="text"> + <string>&Label:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="buddy"> + <cstring>addAsLabel</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="addAsLabel"> + <property name="toolTip"> + <string>Enter a label for this address to add it to the list of used addresses</string> + </property> + <property name="placeholderText"> + <string>Enter a label for this address to add it to the list of used addresses</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="amountLabel"> + <property name="text"> + <string>A&mount:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="buddy"> + <cstring>payAmount</cstring> + </property> + </widget> + </item> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayoutAmount" stretch="0,1,0"> + <item> + <widget class="BitcoinAmountField" name="payAmount" native="true"> + <property name="toolTip"> + <string>The amount to send in the selected unit</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="checkboxSubtractFeeFromAmount"> + <property name="toolTip"> + <string>The fee will be deducted from the amount being sent. The recipient will receive less bitcoins than you enter in the amount field. If multiple recipients are selected, the fee is split equally.</string> + </property> + <property name="text"> + <string>S&ubtract fee from amount</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="useAvailableBalanceButton"> + <property name="text"> + <string>Use available balance</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="messageLabel"> + <property name="text"> + <string>Message:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="messageTextLabel"> + <property name="toolTip"> + <string>A message that was attached to the bitcoin: URI which will be stored with the transaction for your reference. Note: This message will not be sent over the Bitcoin network.</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + <item row="4" column="0" colspan="2"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> </widget> <customwidgets> <customwidget> @@ -1263,8 +223,9 @@ </customwidget> <customwidget> <class>BitcoinAmountField</class> - <extends>QLineEdit</extends> + <extends>QWidget</extends> <header>qt/bitcoinamountfield.h</header> + <container>1</container> </customwidget> </customwidgets> <tabstops> @@ -1274,10 +235,6 @@ <tabstop>deleteButton</tabstop> <tabstop>addAsLabel</tabstop> <tabstop>payAmount</tabstop> - <tabstop>payAmount_is</tabstop> - <tabstop>deleteButton_is</tabstop> - <tabstop>payAmount_s</tabstop> - <tabstop>deleteButton_s</tabstop> </tabstops> <resources> <include location="../bitcoin.qrc"/> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index e3c6d8a624..2551be0af3 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -24,9 +24,6 @@ #include <util/time.h> #ifdef WIN32 -#ifndef NOMINMAX -#define NOMINMAX -#endif #include <shellapi.h> #include <shlobj.h> #include <shlwapi.h> @@ -56,6 +53,7 @@ #include <QMouseEvent> #include <QPluginLoader> #include <QProgressDialog> +#include <QRegularExpression> #include <QScreen> #include <QSettings> #include <QShortcut> @@ -292,6 +290,17 @@ QString getDefaultDataDirectory() return PathToQString(GetDefaultDataDir()); } +QString ExtractFirstSuffixFromFilter(const QString& filter) +{ + QRegularExpression filter_re(QStringLiteral(".* \\(\\*\\.(.*)[ \\)]"), QRegularExpression::InvertedGreedinessOption); + QString suffix; + QRegularExpressionMatch m = filter_re.match(filter); + if (m.hasMatch()) { + suffix = m.captured(1); + } + return suffix; +} + QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut) @@ -309,13 +318,7 @@ QString getSaveFileName(QWidget *parent, const QString &caption, const QString & /* Directly convert path to native OS path separators */ QString result = QDir::toNativeSeparators(QFileDialog::getSaveFileName(parent, caption, myDir, filter, &selectedFilter)); - /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */ - QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); - QString selectedSuffix; - if(filter_re.exactMatch(selectedFilter)) - { - selectedSuffix = filter_re.cap(1); - } + QString selectedSuffix = ExtractFirstSuffixFromFilter(selectedFilter); /* Add suffix if needed */ QFileInfo info(result); @@ -357,14 +360,8 @@ QString getOpenFileName(QWidget *parent, const QString &caption, const QString & if(selectedSuffixOut) { - /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */ - QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); - QString selectedSuffix; - if(filter_re.exactMatch(selectedFilter)) - { - selectedSuffix = filter_re.cap(1); - } - *selectedSuffixOut = selectedSuffix; + *selectedSuffixOut = ExtractFirstSuffixFromFilter(selectedFilter); + ; } return result; } diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index e38ac6026a..acbe415a91 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -123,6 +123,14 @@ namespace GUIUtil */ QString getDefaultDataDirectory(); + /** + * Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...). + * + * @param[in] filter Filter specification such as "Comma Separated Files (*.csv)" + * @return QString + */ + QString ExtractFirstSuffixFromFilter(const QString& filter); + /** Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when no suffix is provided by the user. diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index f4928951fe..4c8b33bf28 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -287,7 +287,7 @@ void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable ui->freeSpace->setText(""); } else { m_bytes_available = bytesAvailable; - if (ui->prune->isEnabled()) { + if (ui->prune->isEnabled() && !(gArgs.IsArgSet("-prune") && gArgs.GetIntArg("-prune", 0) == 0)) { ui->prune->setChecked(m_bytes_available < (m_blockchain_size_gb + m_chain_state_size_gb + 10) * GB_BYTES); } UpdateFreeSpaceLabel(); diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index f3c3af10e0..462b923d61 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -10,6 +10,7 @@ #include <qt/forms/ui_optionsdialog.h> #include <qt/bitcoinunits.h> +#include <qt/clientmodel.h> #include <qt/guiconstants.h> #include <qt/guiutil.h> #include <qt/optionsmodel.h> @@ -168,6 +169,11 @@ OptionsDialog::~OptionsDialog() delete ui; } +void OptionsDialog::setClientModel(ClientModel* client_model) +{ + m_client_model = client_model; +} + void OptionsDialog::setModel(OptionsModel *_model) { this->model = _model; @@ -278,14 +284,15 @@ void OptionsDialog::setOkButtonState(bool fState) void OptionsDialog::on_resetButton_clicked() { - if(model) - { + if (model) { // confirmation dialog QMessageBox::StandardButton btnRetVal = QMessageBox::question(this, tr("Confirm options reset"), - tr("Client restart required to activate changes.") + "<br><br>" + tr("Client will be shut down. Do you want to proceed?"), + tr("Client restart required to activate changes.") + "<br><br>" + + tr("Current settings will be backed up at \"%1\".").arg(m_client_model->dataDir()) + "<br><br>" + + tr("Client will be shut down. Do you want to proceed?"), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); - if(btnRetVal == QMessageBox::Cancel) + if (btnRetVal == QMessageBox::Cancel) return; /* reset all options and close GUI */ diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h index 0b7802536c..e5a19d5025 100644 --- a/src/qt/optionsdialog.h +++ b/src/qt/optionsdialog.h @@ -8,6 +8,7 @@ #include <QDialog> #include <QValidator> +class ClientModel; class OptionsModel; class QValidatedLineEdit; @@ -45,6 +46,7 @@ public: TAB_NETWORK, }; + void setClientModel(ClientModel* client_model); void setModel(OptionsModel *model); void setMapper(); void setCurrentTab(OptionsDialog::Tab tab); @@ -72,6 +74,7 @@ Q_SIGNALS: private: Ui::OptionsDialog *ui; + ClientModel* m_client_model{nullptr}; OptionsModel *model; QDataWidgetMapper *mapper; }; diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index 339ac580d8..af514d5a43 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -20,7 +20,7 @@ #include <QClipboard> SendCoinsEntry::SendCoinsEntry(const PlatformStyle *_platformStyle, QWidget *parent) : - QStackedWidget(parent), + QWidget(parent), ui(new Ui::SendCoinsEntry), model(nullptr), platformStyle(_platformStyle) @@ -30,25 +30,16 @@ SendCoinsEntry::SendCoinsEntry(const PlatformStyle *_platformStyle, QWidget *par ui->addressBookButton->setIcon(platformStyle->SingleColorIcon(":/icons/address-book")); ui->pasteButton->setIcon(platformStyle->SingleColorIcon(":/icons/editpaste")); ui->deleteButton->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); - ui->deleteButton_is->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); - ui->deleteButton_s->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); - - setCurrentWidget(ui->SendCoins); if (platformStyle->getUseExtraSpacing()) ui->payToLayout->setSpacing(4); - // normal bitcoin address field GUIUtil::setupAddressWidget(ui->payTo, this); - // just a label for displaying bitcoin address(es) - ui->payTo_is->setFont(GUIUtil::fixedPitchFont()); // Connect signals connect(ui->payAmount, &BitcoinAmountField::valueChanged, this, &SendCoinsEntry::payAmountChanged); connect(ui->checkboxSubtractFeeFromAmount, &QCheckBox::toggled, this, &SendCoinsEntry::subtractFeeFromAmountChanged); connect(ui->deleteButton, &QPushButton::clicked, this, &SendCoinsEntry::deleteClicked); - connect(ui->deleteButton_is, &QPushButton::clicked, this, &SendCoinsEntry::deleteClicked); - connect(ui->deleteButton_s, &QPushButton::clicked, this, &SendCoinsEntry::deleteClicked); connect(ui->useAvailableBalanceButton, &QPushButton::clicked, this, &SendCoinsEntry::useAvailableBalanceClicked); } @@ -103,14 +94,6 @@ void SendCoinsEntry::clear() ui->messageTextLabel->clear(); ui->messageTextLabel->hide(); ui->messageLabel->hide(); - // clear UI elements for unauthenticated payment request - ui->payTo_is->clear(); - ui->memoTextLabel_is->clear(); - ui->payAmount_is->clear(); - // clear UI elements for authenticated payment request - ui->payTo_s->clear(); - ui->memoTextLabel_s->clear(); - ui->payAmount_s->clear(); // update the display unit, to not use the default ("BTC") updateDisplayUnit(); @@ -219,7 +202,7 @@ void SendCoinsEntry::setAmount(const CAmount &amount) bool SendCoinsEntry::isClear() { - return ui->payTo->text().isEmpty() && ui->payTo_is->text().isEmpty() && ui->payTo_s->text().isEmpty(); + return ui->payTo->text().isEmpty(); } void SendCoinsEntry::setFocus() @@ -229,12 +212,8 @@ void SendCoinsEntry::setFocus() void SendCoinsEntry::updateDisplayUnit() { - if(model && model->getOptionsModel()) - { - // Update payAmount with the current unit + if (model && model->getOptionsModel()) { ui->payAmount->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); - ui->payAmount_is->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); - ui->payAmount_s->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); } } @@ -244,11 +223,9 @@ void SendCoinsEntry::changeEvent(QEvent* e) ui->addressBookButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/address-book"))); ui->pasteButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/editpaste"))); ui->deleteButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove"))); - ui->deleteButton_is->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove"))); - ui->deleteButton_s->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove"))); } - QStackedWidget::changeEvent(e); + QWidget::changeEvent(e); } bool SendCoinsEntry::updateLabel(const QString &address) diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index e8db1e3a5c..ea9d58fbf8 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -7,7 +7,7 @@ #include <qt/sendcoinsrecipient.h> -#include <QStackedWidget> +#include <QWidget> class WalletModel; class PlatformStyle; @@ -22,10 +22,8 @@ namespace Ui { /** * A single entry in the dialog for sending bitcoins. - * Stacked widget, with different UIs for payment requests - * with a strong payee identity. */ -class SendCoinsEntry : public QStackedWidget +class SendCoinsEntry : public QWidget { Q_OBJECT diff --git a/src/qt/test/optiontests.cpp b/src/qt/test/optiontests.cpp index 3bd0af19ad..17ffeb220b 100644 --- a/src/qt/test/optiontests.cpp +++ b/src/qt/test/optiontests.cpp @@ -4,6 +4,7 @@ #include <init.h> #include <qt/bitcoin.h> +#include <qt/guiutil.h> #include <qt/test/optiontests.h> #include <test/util/setup_common.h> #include <util/system.h> @@ -122,3 +123,12 @@ void OptionTests::parametersInteraction() QVERIFY(!settings.contains("fListen")); gArgs.ClearPathCache(); } + +void OptionTests::extractFilter() +{ + QString filter = QString("Partially Signed Transaction (Binary) (*.psbt)"); + QCOMPARE(GUIUtil::ExtractFirstSuffixFromFilter(filter), "psbt"); + + filter = QString("Image (*.png *.jpg)"); + QCOMPARE(GUIUtil::ExtractFirstSuffixFromFilter(filter), "png"); +} diff --git a/src/qt/test/optiontests.h b/src/qt/test/optiontests.h index 286d785572..57ec8bd0f2 100644 --- a/src/qt/test/optiontests.h +++ b/src/qt/test/optiontests.h @@ -22,6 +22,7 @@ private Q_SLOTS: void migrateSettings(); void integerGetArgBug(); void parametersInteraction(); + void extractFilter(); private: interfaces::Node& m_node; diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index e68095f8e5..4894cac905 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -22,7 +22,8 @@ #include <QCloseEvent> #include <QLabel> #include <QMainWindow> -#include <QRegExp> +#include <QRegularExpression> +#include <QString> #include <QTextCursor> #include <QTextTable> #include <QVBoxLayout> @@ -44,9 +45,8 @@ HelpMessageDialog::HelpMessageDialog(QWidget *parent, bool about) : /// HTML-format the license message from the core QString licenseInfoHTML = QString::fromStdString(LicenseInfo()); // Make URLs clickable - QRegExp uri("<(.*)>", Qt::CaseSensitive, QRegExp::RegExp2); - uri.setMinimal(true); // use non-greedy matching - licenseInfoHTML.replace(uri, "<a href=\"\\1\">\\1</a>"); + QRegularExpression uri(QStringLiteral("<(.*)>"), QRegularExpression::InvertedGreedinessOption); + licenseInfoHTML.replace(uri, QStringLiteral("<a href=\"\\1\">\\1</a>")); // Replace newlines with HTML breaks licenseInfoHTML.replace("\n", "<br>"); diff --git a/src/random.cpp b/src/random.cpp index 74ceb3d2a3..fca4b5041a 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -97,7 +97,7 @@ static void ReportHardwareRand() // This must be done in a separate function, as InitHardwareRand() may be indirectly called // from global constructors, before logging is initialized. if (g_rdseed_supported) { - LogPrintf("Using RdSeed as additional entropy source\n"); + LogPrintf("Using RdSeed as an additional entropy source\n"); } if (g_rdrand_supported) { LogPrintf("Using RdRand as an additional entropy source\n"); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 9766a237c7..af9458206e 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2177,7 +2177,7 @@ static RPCHelpMan getblockfilter() "\nRetrieve a BIP 157 content filter for a particular block.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hash of the block"}, - {"filtertype", RPCArg::Type::STR, RPCArg::Default{"basic"}, "The type name of the filter"}, + {"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -2192,7 +2192,7 @@ static RPCHelpMan getblockfilter() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { uint256 block_hash = ParseHashV(request.params[0], "blockhash"); - std::string filtertype_name = "basic"; + std::string filtertype_name = BlockFilterTypeName(BlockFilterType::BASIC); if (!request.params[1].isNull()) { filtertype_name = request.params[1].get_str(); } diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 2cdde14144..0d614d5ec0 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -660,6 +660,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool) ret.pushKV("maxmempool", pool.m_max_size_bytes); ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(), ::minRelayTxFee).GetFeePerK())); ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())); + ret.pushKV("incrementalrelayfee", ValueFromAmount(::incrementalRelayFee.GetFeePerK())); ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()}); return ret; } @@ -667,7 +668,7 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool) static RPCHelpMan getmempoolinfo() { return RPCHelpMan{"getmempoolinfo", - "\nReturns details on the active state of the TX memory pool.\n", + "Returns details on the active state of the TX memory pool.", {}, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -680,7 +681,8 @@ static RPCHelpMan getmempoolinfo() {RPCResult::Type::NUM, "maxmempool", "Maximum memory usage for the mempool"}, {RPCResult::Type::STR_AMOUNT, "mempoolminfee", "Minimum fee rate in " + CURRENCY_UNIT + "/kvB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee"}, {RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"}, - {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"} + {RPCResult::Type::NUM, "incrementalrelayfee", "minimum fee rate increment for mempool limiting or BIP 125 replacement in " + CURRENCY_UNIT + "/kvB"}, + {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"}, }}, RPCExamples{ HelpExampleCli("getmempoolinfo", "") diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 0a061f2451..fad92629c5 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -206,13 +206,13 @@ static RPCHelpMan getpeerinfo() obj.pushKV("conntime", count_seconds(stats.m_connected)); obj.pushKV("timeoffset", stats.nTimeOffset); if (stats.m_last_ping_time > 0us) { - obj.pushKV("pingtime", CountSecondsDouble(stats.m_last_ping_time)); + obj.pushKV("pingtime", Ticks<SecondsDouble>(stats.m_last_ping_time)); } if (stats.m_min_ping_time < std::chrono::microseconds::max()) { - obj.pushKV("minping", CountSecondsDouble(stats.m_min_ping_time)); + obj.pushKV("minping", Ticks<SecondsDouble>(stats.m_min_ping_time)); } if (fStateStats && statestats.m_ping_wait > 0s) { - obj.pushKV("pingwait", CountSecondsDouble(statestats.m_ping_wait)); + obj.pushKV("pingwait", Ticks<SecondsDouble>(statestats.m_ping_wait)); } obj.pushKV("version", stats.nVersion); // Use the sanitized form of subver here, to avoid tricksy remote peers from diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index b9b8c36bb3..792a1e13b0 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -790,6 +790,43 @@ static RPCHelpMan decodepsbt() { {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, }}, + {RPCResult::Type::STR_HEX, "taproot_key_path_sig", /*optional=*/ true, "hex-encoded signature for the Taproot key path spend"}, + {RPCResult::Type::ARR, "taproot_script_path_sigs", /*optional=*/ true, "", + { + {RPCResult::Type::OBJ, "signature", /*optional=*/ true, "The signature for the pubkey and leaf hash combination", + { + {RPCResult::Type::STR, "pubkey", "The x-only pubkey for this signature"}, + {RPCResult::Type::STR, "leaf_hash", "The leaf hash for this signature"}, + {RPCResult::Type::STR, "sig", "The signature itself"}, + }}, + }}, + {RPCResult::Type::ARR, "taproot_scripts", /*optional=*/ true, "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "script", "A leaf script"}, + {RPCResult::Type::NUM, "leaf_ver", "The version number for the leaf script"}, + {RPCResult::Type::ARR, "control_blocks", "The control blocks for this script", + { + {RPCResult::Type::STR_HEX, "control_block", "A hex-encoded control block for this script"}, + }}, + }}, + }}, + {RPCResult::Type::ARR, "taproot_bip32_derivs", /*optional=*/ true, "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "pubkey", "The x-only public key this path corresponds to"}, + {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"}, + {RPCResult::Type::STR, "path", "The path"}, + {RPCResult::Type::ARR, "leaf_hashes", "The hashes of the leaves this pubkey appears in", + { + {RPCResult::Type::STR_HEX, "hash", "The hash of a leaf this pubkey appears in"}, + }}, + }}, + }}, + {RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"}, + {RPCResult::Type::STR_HEX, "taproot_merkle_root", /*optional=*/ true, "The hex-encoded Taproot merkle root"}, {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/ true, "The unknown input fields", { {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, @@ -831,7 +868,30 @@ static RPCHelpMan decodepsbt() {RPCResult::Type::STR, "path", "The path"}, }}, }}, - {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown global fields", + {RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"}, + {RPCResult::Type::ARR, "taproot_tree", /*optional=*/ true, "The tuples that make up the Taproot tree, in depth first search order", + { + {RPCResult::Type::OBJ, "tuple", /*optional=*/ true, "A single leaf script in the taproot tree", + { + {RPCResult::Type::NUM, "depth", "The depth of this element in the tree"}, + {RPCResult::Type::NUM, "leaf_ver", "The version of this leaf"}, + {RPCResult::Type::STR, "script", "The hex-encoded script itself"}, + }}, + }}, + {RPCResult::Type::ARR, "taproot_bip32_derivs", /*optional=*/ true, "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "pubkey", "The x-only public key this path corresponds to"}, + {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"}, + {RPCResult::Type::STR, "path", "The path"}, + {RPCResult::Type::ARR, "leaf_hashes", "The hashes of the leaves this pubkey appears in", + { + {RPCResult::Type::STR_HEX, "hash", "The hash of a leaf this pubkey appears in"}, + }}, + }}, + }}, + {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown output fields", { {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, }}, @@ -1045,6 +1105,72 @@ static RPCHelpMan decodepsbt() in.pushKV("hash256_preimages", hash256_preimages); } + // Taproot key path signature + if (!input.m_tap_key_sig.empty()) { + in.pushKV("taproot_key_path_sig", HexStr(input.m_tap_key_sig)); + } + + // Taproot script path signatures + if (!input.m_tap_script_sigs.empty()) { + UniValue script_sigs(UniValue::VARR); + for (const auto& [pubkey_leaf, sig] : input.m_tap_script_sigs) { + const auto& [xonly, leaf_hash] = pubkey_leaf; + UniValue sigobj(UniValue::VOBJ); + sigobj.pushKV("pubkey", HexStr(xonly)); + sigobj.pushKV("leaf_hash", HexStr(leaf_hash)); + sigobj.pushKV("sig", HexStr(sig)); + script_sigs.push_back(sigobj); + } + in.pushKV("taproot_script_path_sigs", script_sigs); + } + + // Taproot leaf scripts + if (!input.m_tap_scripts.empty()) { + UniValue tap_scripts(UniValue::VARR); + for (const auto& [leaf, control_blocks] : input.m_tap_scripts) { + const auto& [script, leaf_ver] = leaf; + UniValue script_info(UniValue::VOBJ); + script_info.pushKV("script", HexStr(script)); + script_info.pushKV("leaf_ver", leaf_ver); + UniValue control_blocks_univ(UniValue::VARR); + for (const auto& control_block : control_blocks) { + control_blocks_univ.push_back(HexStr(control_block)); + } + script_info.pushKV("control_blocks", control_blocks_univ); + tap_scripts.push_back(script_info); + } + in.pushKV("taproot_scripts", tap_scripts); + } + + // Taproot bip32 keypaths + if (!input.m_tap_bip32_paths.empty()) { + UniValue keypaths(UniValue::VARR); + for (const auto& [xonly, leaf_origin] : input.m_tap_bip32_paths) { + const auto& [leaf_hashes, origin] = leaf_origin; + UniValue path_obj(UniValue::VOBJ); + path_obj.pushKV("pubkey", HexStr(xonly)); + path_obj.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(origin.fingerprint))); + path_obj.pushKV("path", WriteHDKeypath(origin.path)); + UniValue leaf_hashes_arr(UniValue::VARR); + for (const auto& leaf_hash : leaf_hashes) { + leaf_hashes_arr.push_back(HexStr(leaf_hash)); + } + path_obj.pushKV("leaf_hashes", leaf_hashes_arr); + keypaths.push_back(path_obj); + } + in.pushKV("taproot_bip32_derivs", keypaths); + } + + // Taproot internal key + if (!input.m_tap_internal_key.IsNull()) { + in.pushKV("taproot_internal_key", HexStr(input.m_tap_internal_key)); + } + + // Write taproot merkle root + if (!input.m_tap_merkle_root.IsNull()) { + in.pushKV("taproot_merkle_root", HexStr(input.m_tap_merkle_root)); + } + // Proprietary if (!input.m_proprietary.empty()) { UniValue proprietary(UniValue::VARR); @@ -1103,6 +1229,47 @@ static RPCHelpMan decodepsbt() out.pushKV("bip32_derivs", keypaths); } + // Taproot internal key + if (!output.m_tap_internal_key.IsNull()) { + out.pushKV("taproot_internal_key", HexStr(output.m_tap_internal_key)); + } + + // Taproot tree + if (output.m_tap_tree.has_value()) { + UniValue tree(UniValue::VARR); + const auto& tuples = output.m_tap_tree->GetTreeTuples(); + for (const auto& tuple : tuples) { + uint8_t depth = std::get<0>(tuple); + uint8_t leaf_ver = std::get<1>(tuple); + CScript script = std::get<2>(tuple); + UniValue elem(UniValue::VOBJ); + elem.pushKV("depth", (int)depth); + elem.pushKV("leaf_ver", (int)leaf_ver); + elem.pushKV("script", HexStr(script)); + tree.push_back(elem); + } + out.pushKV("taproot_tree", tree); + } + + // Taproot bip32 keypaths + if (!output.m_tap_bip32_paths.empty()) { + UniValue keypaths(UniValue::VARR); + for (const auto& [xonly, leaf_origin] : output.m_tap_bip32_paths) { + const auto& [leaf_hashes, origin] = leaf_origin; + UniValue path_obj(UniValue::VOBJ); + path_obj.pushKV("pubkey", HexStr(xonly)); + path_obj.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(origin.fingerprint))); + path_obj.pushKV("path", WriteHDKeypath(origin.path)); + UniValue leaf_hashes_arr(UniValue::VARR); + for (const auto& leaf_hash : leaf_hashes) { + leaf_hashes_arr.push_back(HexStr(leaf_hash)); + } + path_obj.pushKV("leaf_hashes", leaf_hashes_arr); + keypaths.push_back(path_obj); + } + out.pushKV("taproot_bip32_derivs", keypaths); + } + // Proprietary if (!output.m_proprietary.empty()) { UniValue proprietary(UniValue::VARR); diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 66ed18045e..e9987d73be 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -11,14 +11,18 @@ #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> +#include <util/time.h> #include <boost/signals2/signal.hpp> #include <cassert> -#include <memory> // for unique_ptr +#include <chrono> +#include <memory> #include <mutex> #include <unordered_map> +using SteadyClock = std::chrono::steady_clock; + static GlobalMutex g_rpc_warmup_mutex; static std::atomic<bool> g_rpc_running{false}; static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true; @@ -33,7 +37,7 @@ static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& req struct RPCCommandExecutionInfo { std::string method; - int64_t start; + SteadyClock::time_point start; }; struct RPCServerInfo @@ -50,7 +54,7 @@ struct RPCCommandExecution explicit RPCCommandExecution(const std::string& method) { LOCK(g_rpc_server_info.mutex); - it = g_rpc_server_info.active_commands.insert(g_rpc_server_info.active_commands.end(), {method, GetTimeMicros()}); + it = g_rpc_server_info.active_commands.insert(g_rpc_server_info.active_commands.end(), {method, SteadyClock::now()}); } ~RPCCommandExecution() { @@ -231,7 +235,7 @@ static RPCHelpMan getrpcinfo() for (const RPCCommandExecutionInfo& info : g_rpc_server_info.active_commands) { UniValue entry(UniValue::VOBJ); entry.pushKV("method", info.method); - entry.pushKV("duration", GetTimeMicros() - info.start); + entry.pushKV("duration", int64_t{Ticks<std::chrono::microseconds>(SteadyClock::now() - info.start)}); active_commands.push_back(entry); } diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index cece0b60ce..ca0170c84b 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -882,7 +882,7 @@ protected: if (!xpk.IsFullyValid()) return {}; builder.Finalize(xpk); WitnessV1Taproot output = builder.GetOutput(); - out.tr_spenddata[output].Merge(builder.GetSpendData()); + out.tr_trees[output] = builder; out.pubkeys.emplace(keys[0].GetID(), keys[0]); return Vector(GetScriptForDestination(output)); } diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 2d569d674a..a3681d26cc 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -150,6 +150,7 @@ static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, Signatur auto it = sigdata.taproot_script_sigs.find(lookup_key); if (it != sigdata.taproot_script_sigs.end()) { sig_out = it->second; + return true; } if (creator.CreateSchnorrSig(provider, sig_out, pubkey, &leaf_hash, nullptr, sigversion)) { sigdata.taproot_script_sigs[lookup_key] = sig_out; @@ -169,6 +170,17 @@ static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatu // <xonly pubkey> OP_CHECKSIG if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) { XOnlyPubKey pubkey{Span{script}.subspan(1, 32)}; + + KeyOriginInfo info; + if (provider.GetKeyOriginByXOnly(pubkey, info)) { + auto it = sigdata.taproot_misc_pubkeys.find(pubkey); + if (it == sigdata.taproot_misc_pubkeys.end()) { + sigdata.taproot_misc_pubkeys.emplace(pubkey, std::make_pair(std::set<uint256>({leaf_hash}), info)); + } else { + it->second.first.insert(leaf_hash); + } + } + std::vector<unsigned char> sig; if (CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion)) { result = Vector(std::move(sig)); @@ -205,17 +217,29 @@ static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatu static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCreator& creator, const WitnessV1Taproot& output, SignatureData& sigdata, std::vector<valtype>& result) { TaprootSpendData spenddata; + TaprootBuilder builder; // Gather information about this output. if (provider.GetTaprootSpendData(output, spenddata)) { sigdata.tr_spenddata.Merge(spenddata); } + if (provider.GetTaprootBuilder(output, builder)) { + sigdata.tr_builder = builder; + } // Try key path spending. { + KeyOriginInfo info; + if (provider.GetKeyOriginByXOnly(sigdata.tr_spenddata.internal_key, info)) { + auto it = sigdata.taproot_misc_pubkeys.find(sigdata.tr_spenddata.internal_key); + if (it == sigdata.taproot_misc_pubkeys.end()) { + sigdata.taproot_misc_pubkeys.emplace(sigdata.tr_spenddata.internal_key, std::make_pair(std::set<uint256>(), info)); + } + } + std::vector<unsigned char> sig; if (sigdata.taproot_key_path_sig.size() == 0) { - if (creator.CreateSchnorrSig(provider, sig, spenddata.internal_key, nullptr, &spenddata.merkle_root, SigVersion::TAPROOT)) { + if (creator.CreateSchnorrSig(provider, sig, sigdata.tr_spenddata.internal_key, nullptr, &sigdata.tr_spenddata.merkle_root, SigVersion::TAPROOT)) { sigdata.taproot_key_path_sig = sig; } } diff --git a/src/script/sign.h b/src/script/sign.h index 71203d08ec..5e58272154 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -70,10 +70,12 @@ struct SignatureData { CScript witness_script; ///< The witnessScript (if any) for the input. witnessScripts are used in P2WSH outputs. CScriptWitness scriptWitness; ///< The scriptWitness of an input. Contains complete signatures or the traditional partial signatures format. scriptWitness is part of a transaction input per BIP 144. TaprootSpendData tr_spenddata; ///< Taproot spending data. + std::optional<TaprootBuilder> tr_builder; ///< Taproot tree used to build tr_spenddata. std::map<CKeyID, SigPair> signatures; ///< BIP 174 style partial signatures for the input. May contain all signatures necessary for producing a final scriptSig or scriptWitness. std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> misc_pubkeys; std::vector<unsigned char> taproot_key_path_sig; /// Schnorr signature for key path spending std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> taproot_script_sigs; ///< (Partial) schnorr signatures, indexed by XOnlyPubKey and leaf_hash. + std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> taproot_misc_pubkeys; ///< Miscellaneous Taproot pubkeys involved in this input along with their leaf script hashes and key origin data. Also includes the Taproot internal key (may have no leaf script hashes). std::vector<CKeyID> missing_pubkeys; ///< KeyIDs of pubkeys which could not be found std::vector<CKeyID> missing_sigs; ///< KeyIDs of pubkeys for signatures which could not be found uint160 missing_redeem_script; ///< ScriptID of the missing redeemScript (if any) diff --git a/src/script/signingprovider.cpp b/src/script/signingprovider.cpp index 552934e0eb..c624a17628 100644 --- a/src/script/signingprovider.cpp +++ b/src/script/signingprovider.cpp @@ -48,6 +48,10 @@ bool HidingSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, T { return m_provider->GetTaprootSpendData(output_key, spenddata); } +bool HidingSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const +{ + return m_provider->GetTaprootBuilder(output_key, builder); +} bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); } bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); } @@ -61,7 +65,16 @@ bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); } bool FlatSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const { - return LookupHelper(tr_spenddata, output_key, spenddata); + TaprootBuilder builder; + if (LookupHelper(tr_trees, output_key, builder)) { + spenddata = builder.GetSpendData(); + return true; + } + return false; +} +bool FlatSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const +{ + return LookupHelper(tr_trees, output_key, builder); } FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b) @@ -75,10 +88,8 @@ FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvide ret.keys.insert(b.keys.begin(), b.keys.end()); ret.origins = a.origins; ret.origins.insert(b.origins.begin(), b.origins.end()); - ret.tr_spenddata = a.tr_spenddata; - for (const auto& [output_key, spenddata] : b.tr_spenddata) { - ret.tr_spenddata[output_key].Merge(spenddata); - } + ret.tr_trees = a.tr_trees; + ret.tr_trees.insert(b.tr_trees.begin(), b.tr_trees.end()); return ret; } diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h index f1bded1a8c..792cc903f2 100644 --- a/src/script/signingprovider.h +++ b/src/script/signingprovider.h @@ -25,6 +25,7 @@ public: virtual bool HaveKey(const CKeyID &address) const { return false; } virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; } virtual bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const { return false; } + virtual bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const { return false; } bool GetKeyByXOnly(const XOnlyPubKey& pubkey, CKey& key) const { @@ -67,6 +68,7 @@ public: bool GetKey(const CKeyID& keyid, CKey& key) const override; bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; + bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override; }; struct FlatSigningProvider final : public SigningProvider @@ -75,13 +77,14 @@ struct FlatSigningProvider final : public SigningProvider std::map<CKeyID, CPubKey> pubkeys; std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins; std::map<CKeyID, CKey> keys; - std::map<XOnlyPubKey, TaprootSpendData> tr_spenddata; /** Map from output key to spend data. */ + std::map<XOnlyPubKey, TaprootBuilder> tr_trees; /** Map from output key to Taproot tree (which can then make the TaprootSpendData */ bool GetCScript(const CScriptID& scriptid, CScript& script) const override; bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; bool GetKey(const CKeyID& keyid, CKey& key) const override; bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; + bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override; }; FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b); diff --git a/src/script/standard.cpp b/src/script/standard.cpp index e25155d3dd..5d80891485 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -485,6 +485,7 @@ WitnessV1Taproot TaprootBuilder::GetOutput() { return WitnessV1Taproot{m_output_ TaprootSpendData TaprootBuilder::GetSpendData() const { assert(IsComplete()); + assert(m_output_key.IsFullyValid()); TaprootSpendData spd; spd.merkle_root = m_branch.size() == 0 ? uint256() : m_branch[0]->hash; spd.internal_key = m_internal_key; @@ -642,3 +643,19 @@ std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const return ret; } + +std::vector<std::tuple<uint8_t, uint8_t, CScript>> TaprootBuilder::GetTreeTuples() const +{ + assert(IsComplete()); + std::vector<std::tuple<uint8_t, uint8_t, CScript>> tuples; + if (m_branch.size()) { + const auto& leaves = m_branch[0]->leaves; + for (const auto& leaf : leaves) { + assert(leaf.merkle_branch.size() <= TAPROOT_CONTROL_MAX_NODE_COUNT); + uint8_t depth = (uint8_t)leaf.merkle_branch.size(); + uint8_t leaf_ver = (uint8_t)leaf.leaf_version; + tuples.push_back(std::make_tuple(depth, leaf_ver, leaf.script)); + } + } + return tuples; +} diff --git a/src/script/standard.h b/src/script/standard.h index 6a15ba4e3d..448fdff010 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -322,6 +322,8 @@ public: static bool ValidDepths(const std::vector<int>& depths); /** Compute spending data (after Finalize()). */ TaprootSpendData GetSpendData() const; + /** Returns a vector of tuples representing the depth, leaf version, and script */ + std::vector<std::tuple<uint8_t, uint8_t, CScript>> GetTreeTuples() const; }; /** Given a TaprootSpendData and the output key, reconstruct its script tree. diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp index b7ef479675..e48accf0a4 100644 --- a/src/support/lockedpool.cpp +++ b/src/support/lockedpool.cpp @@ -10,9 +10,6 @@ #endif #ifdef WIN32 -#ifndef NOMINMAX -#define NOMINMAX -#endif #include <windows.h> #else #include <sys/mman.h> // for mmap diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 3b4a6f2637..c87ed82c88 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -305,7 +305,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) peerLogic->InitializeNode(nodes[0]); nodes[0]->fSuccessfullyConnected = true; connman->AddTestNode(*nodes[0]); - peerLogic->Misbehaving(nodes[0]->GetId(), DISCOURAGEMENT_THRESHOLD, /*message=*/""); // Should be discouraged + peerLogic->UnitTestMisbehaving(nodes[0]->GetId(), DISCOURAGEMENT_THRESHOLD); // Should be discouraged { LOCK(nodes[0]->cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(nodes[0])); @@ -328,7 +328,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) peerLogic->InitializeNode(nodes[1]); nodes[1]->fSuccessfullyConnected = true; connman->AddTestNode(*nodes[1]); - peerLogic->Misbehaving(nodes[1]->GetId(), DISCOURAGEMENT_THRESHOLD - 1, /*message=*/""); + peerLogic->UnitTestMisbehaving(nodes[1]->GetId(), DISCOURAGEMENT_THRESHOLD - 1); { LOCK(nodes[1]->cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(nodes[1])); @@ -339,7 +339,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) // [1] is not discouraged/disconnected yet. BOOST_CHECK(!banman->IsDiscouraged(addr[1])); BOOST_CHECK(!nodes[1]->fDisconnect); - peerLogic->Misbehaving(nodes[1]->GetId(), 1, /*message=*/""); // [1] reaches discouragement threshold + peerLogic->UnitTestMisbehaving(nodes[1]->GetId(), 1); // [1] reaches discouragement threshold { LOCK(nodes[1]->cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(nodes[1])); @@ -366,7 +366,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) peerLogic->InitializeNode(nodes[2]); nodes[2]->fSuccessfullyConnected = true; connman->AddTestNode(*nodes[2]); - peerLogic->Misbehaving(nodes[2]->GetId(), DISCOURAGEMENT_THRESHOLD, /*message=*/""); + peerLogic->UnitTestMisbehaving(nodes[2]->GetId(), DISCOURAGEMENT_THRESHOLD); { LOCK(nodes[2]->cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(nodes[2])); @@ -411,7 +411,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) peerLogic->InitializeNode(&dummyNode); dummyNode.fSuccessfullyConnected = true; - peerLogic->Misbehaving(dummyNode.GetId(), DISCOURAGEMENT_THRESHOLD, /*message=*/""); + peerLogic->UnitTestMisbehaving(dummyNode.GetId(), DISCOURAGEMENT_THRESHOLD); { LOCK(dummyNode.cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(&dummyNode)); diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp index 883698aff1..4b893c648e 100644 --- a/src/test/fuzz/util.cpp +++ b/src/test/fuzz/util.cpp @@ -24,10 +24,10 @@ FuzzedSock::FuzzedSock(FuzzedDataProvider& fuzzed_data_provider) FuzzedSock::~FuzzedSock() { // Sock::~Sock() will be called after FuzzedSock::~FuzzedSock() and it will call - // Sock::Reset() (not FuzzedSock::Reset()!) which will call CloseSocket(m_socket). + // close(m_socket) if m_socket is not INVALID_SOCKET. // Avoid closing an arbitrary file descriptor (m_socket is just a random very high number which // theoretically may concide with a real opened file descriptor). - Reset(); + m_socket = INVALID_SOCKET; } FuzzedSock& FuzzedSock::operator=(Sock&& other) @@ -36,11 +36,6 @@ FuzzedSock& FuzzedSock::operator=(Sock&& other) return *this; } -void FuzzedSock::Reset() -{ - m_socket = INVALID_SOCKET; -} - ssize_t FuzzedSock::Send(const void* data, size_t len, int flags) const { constexpr std::array send_errnos{ @@ -160,6 +155,45 @@ int FuzzedSock::Connect(const sockaddr*, socklen_t) const return 0; } +int FuzzedSock::Bind(const sockaddr*, socklen_t) const +{ + // Have a permanent error at bind_errnos[0] because when the fuzzed data is exhausted + // SetFuzzedErrNo() will always set the global errno to bind_errnos[0]. We want to + // avoid this method returning -1 and setting errno to a temporary error (like EAGAIN) + // repeatedly because proper code should retry on temporary errors, leading to an + // infinite loop. + constexpr std::array bind_errnos{ + EACCES, + EADDRINUSE, + EADDRNOTAVAIL, + EAGAIN, + }; + if (m_fuzzed_data_provider.ConsumeBool()) { + SetFuzzedErrNo(m_fuzzed_data_provider, bind_errnos); + return -1; + } + return 0; +} + +int FuzzedSock::Listen(int) const +{ + // Have a permanent error at listen_errnos[0] because when the fuzzed data is exhausted + // SetFuzzedErrNo() will always set the global errno to listen_errnos[0]. We want to + // avoid this method returning -1 and setting errno to a temporary error (like EAGAIN) + // repeatedly because proper code should retry on temporary errors, leading to an + // infinite loop. + constexpr std::array listen_errnos{ + EADDRINUSE, + EINVAL, + EOPNOTSUPP, + }; + if (m_fuzzed_data_provider.ConsumeBool()) { + SetFuzzedErrNo(m_fuzzed_data_provider, listen_errnos); + return -1; + } + return 0; +} + std::unique_ptr<Sock> FuzzedSock::Accept(sockaddr* addr, socklen_t* addr_len) const { constexpr std::array accept_errnos{ @@ -206,6 +240,20 @@ int FuzzedSock::SetSockOpt(int, int, const void*, socklen_t) const return 0; } +int FuzzedSock::GetSockName(sockaddr* name, socklen_t* name_len) const +{ + constexpr std::array getsockname_errnos{ + ECONNRESET, + ENOBUFS, + }; + if (m_fuzzed_data_provider.ConsumeBool()) { + SetFuzzedErrNo(m_fuzzed_data_provider, getsockname_errnos); + return -1; + } + *name_len = m_fuzzed_data_provider.ConsumeData(name, *name_len); + return 0; +} + bool FuzzedSock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const { constexpr std::array wait_errnos{ diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index 66d00b1767..4b89ad9bdc 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -55,20 +55,24 @@ public: FuzzedSock& operator=(Sock&& other) override; - void Reset() override; - ssize_t Send(const void* data, size_t len, int flags) const override; ssize_t Recv(void* buf, size_t len, int flags) const override; int Connect(const sockaddr*, socklen_t) const override; + int Bind(const sockaddr*, socklen_t) const override; + + int Listen(int backlog) const override; + std::unique_ptr<Sock> Accept(sockaddr* addr, socklen_t* addr_len) const override; int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override; int SetSockOpt(int level, int opt_name, const void* opt_val, socklen_t opt_len) const override; + int GetSockName(sockaddr* name, socklen_t* name_len) const override; + bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override; bool WaitMany(std::chrono::milliseconds timeout, EventsPerSock& events_per_sock) const override; diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index eca4fbf15c..20d670c1e1 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -369,8 +369,8 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C } // non-final txs in mempool - SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1); - const int flags{LOCKTIME_VERIFY_SEQUENCE | LOCKTIME_MEDIAN_TIME_PAST}; + SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1); + const int flags{LOCKTIME_VERIFY_SEQUENCE}; // height map std::vector<int> prevheights; diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index e7c01bd6d0..115c4b9b24 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -675,10 +675,13 @@ BOOST_AUTO_TEST_CASE(get_local_addr_for_peer_port) const uint16_t bind_port = 20001; m_node.args->ForceSetArg("-bind", strprintf("3.4.5.6:%u", bind_port)); + const uint32_t current_time = static_cast<uint32_t>(GetAdjustedTime()); + SetMockTime(current_time); + // Our address:port as seen from the peer, completely different from the above. in_addr peer_us_addr; peer_us_addr.s_addr = htonl(0x02030405); - const CAddress peer_us{CService{peer_us_addr, 20002}, NODE_NETWORK}; + const CAddress peer_us{CService{peer_us_addr, 20002}, NODE_NETWORK, current_time}; // Create a peer with a routable IPv4 address (outbound). in_addr peer_out_in_addr; @@ -699,7 +702,7 @@ BOOST_AUTO_TEST_CASE(get_local_addr_for_peer_port) // Without the fix peer_us:8333 is chosen instead of the proper peer_us:bind_port. auto chosen_local_addr = GetLocalAddrForPeer(&peer_out); BOOST_REQUIRE(chosen_local_addr); - const CService expected{peer_us_addr, bind_port}; + const CAddress expected{CService{peer_us_addr, bind_port}, NODE_NETWORK, current_time}; BOOST_CHECK(*chosen_local_addr == expected); // Create a peer with a routable IPv4 address (inbound). diff --git a/src/test/sock_tests.cpp b/src/test/sock_tests.cpp index 9e98f4f0b1..01a402833d 100644 --- a/src/test/sock_tests.cpp +++ b/src/test/sock_tests.cpp @@ -69,24 +69,6 @@ BOOST_AUTO_TEST_CASE(move_assignment) BOOST_CHECK(SocketIsClosed(s)); } -BOOST_AUTO_TEST_CASE(release) -{ - SOCKET s = CreateSocket(); - Sock* sock = new Sock(s); - BOOST_CHECK_EQUAL(sock->Release(), s); - delete sock; - BOOST_CHECK(!SocketIsClosed(s)); - BOOST_REQUIRE(CloseSocket(s)); -} - -BOOST_AUTO_TEST_CASE(reset) -{ - const SOCKET s = CreateSocket(); - Sock sock(s); - sock.Reset(); - BOOST_CHECK(SocketIsClosed(s)); -} - #ifndef WIN32 // Windows does not have socketpair(2). static void CreateSocketPair(int s[2]) diff --git a/src/test/util/net.h b/src/test/util/net.h index 37d278645a..c5dbaeca3e 100644 --- a/src/test/util/net.h +++ b/src/test/util/net.h @@ -100,7 +100,7 @@ public: m_socket = INVALID_SOCKET - 1; } - ~StaticContentsSock() override { Reset(); } + ~StaticContentsSock() override { m_socket = INVALID_SOCKET; } StaticContentsSock& operator=(Sock&& other) override { @@ -108,11 +108,6 @@ public: return *this; } - void Reset() override - { - m_socket = INVALID_SOCKET; - } - ssize_t Send(const void*, size_t len, int) const override { return len; } ssize_t Recv(void* buf, size_t len, int flags) const override @@ -127,6 +122,10 @@ public: int Connect(const sockaddr*, socklen_t) const override { return 0; } + int Bind(const sockaddr*, socklen_t) const override { return 0; } + + int Listen(int) const override { return 0; } + std::unique_ptr<Sock> Accept(sockaddr* addr, socklen_t* addr_len) const override { if (addr != nullptr) { @@ -152,6 +151,12 @@ public: int SetSockOpt(int, int, const void*, socklen_t) const override { return 0; } + int GetSockName(sockaddr* name, socklen_t* name_len) const override + { + std::memset(name, 0x0, *name_len); + return 0; + } + bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override diff --git a/src/txdb.cpp b/src/txdb.cpp index a0939873ad..c048c2d92a 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -211,7 +211,6 @@ public: bool GetKey(COutPoint &key) const override; bool GetValue(Coin &coin) const override; - unsigned int GetValueSize() const override; bool Valid() const override; void Next() override; @@ -257,11 +256,6 @@ bool CCoinsViewDBCursor::GetValue(Coin &coin) const return pcursor->GetValue(coin); } -unsigned int CCoinsViewDBCursor::GetValueSize() const -{ - return pcursor->GetValueSize(); -} - bool CCoinsViewDBCursor::Valid() const { return keyTmp.first == DB_COIN; diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 25019e98ab..69ae9fed99 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -14,7 +14,9 @@ #include <policy/policy.h> #include <policy/settings.h> #include <reverse_iterator.h> +#include <util/check.h> #include <util/moneystr.h> +#include <util/overflow.h> #include <util/system.h> #include <util/time.h> #include <validationinterface.h> @@ -82,6 +84,7 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee, entryHeight{entry_height}, spendsCoinbase{spends_coinbase}, sigOpCost{sigops_cost}, + m_modified_fee{nFee}, lockPoints{lp}, nSizeWithDescendants{GetTxSize()}, nModFeesWithDescendants{nFee}, @@ -89,11 +92,11 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee, nModFeesWithAncestors{nFee}, nSigOpCostWithAncestors{sigOpCost} {} -void CTxMemPoolEntry::UpdateFeeDelta(CAmount newFeeDelta) +void CTxMemPoolEntry::UpdateModifiedFee(CAmount fee_diff) { - nModFeesWithDescendants += newFeeDelta - feeDelta; - nModFeesWithAncestors += newFeeDelta - feeDelta; - feeDelta = newFeeDelta; + nModFeesWithDescendants = SaturatingAdd(nModFeesWithDescendants, fee_diff); + nModFeesWithAncestors = SaturatingAdd(nModFeesWithAncestors, fee_diff); + m_modified_fee = SaturatingAdd(m_modified_fee, fee_diff); } void CTxMemPoolEntry::UpdateLockPoints(const LockPoints& lp) @@ -434,7 +437,7 @@ void CTxMemPoolEntry::UpdateDescendantState(int64_t modifySize, CAmount modifyFe { nSizeWithDescendants += modifySize; assert(int64_t(nSizeWithDescendants) > 0); - nModFeesWithDescendants += modifyFee; + nModFeesWithDescendants = SaturatingAdd(nModFeesWithDescendants, modifyFee); nCountWithDescendants += modifyCount; assert(int64_t(nCountWithDescendants) > 0); } @@ -443,7 +446,7 @@ void CTxMemPoolEntry::UpdateAncestorState(int64_t modifySize, CAmount modifyFee, { nSizeWithAncestors += modifySize; assert(int64_t(nSizeWithAncestors) > 0); - nModFeesWithAncestors += modifyFee; + nModFeesWithAncestors = SaturatingAdd(nModFeesWithAncestors, modifyFee); nCountWithAncestors += modifyCount; assert(int64_t(nCountWithAncestors) > 0); nSigOpCostWithAncestors += modifySigOps; @@ -486,8 +489,10 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces // Update transaction for any feeDelta created by PrioritiseTransaction CAmount delta{0}; ApplyDelta(entry.GetTx().GetHash(), delta); + // The following call to UpdateModifiedFee assumes no previous fee modifications + Assume(entry.GetFee() == entry.GetModifiedFee()); if (delta) { - mapTx.modify(newit, [&delta](CTxMemPoolEntry& e) { e.UpdateFeeDelta(delta); }); + mapTx.modify(newit, [&delta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(delta); }); } // Update cachedInnerUsage to include contained transaction's usage. @@ -920,10 +925,10 @@ void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeD { LOCK(cs); CAmount &delta = mapDeltas[hash]; - delta += nFeeDelta; + delta = SaturatingAdd(delta, nFeeDelta); txiter it = mapTx.find(hash); if (it != mapTx.end()) { - mapTx.modify(it, [&delta](CTxMemPoolEntry& e) { e.UpdateFeeDelta(delta); }); + mapTx.modify(it, [&nFeeDelta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(nFeeDelta); }); // Now update all ancestors' modified fees with descendants setEntries setAncestors; uint64_t nNoLimit = std::numeric_limits<uint64_t>::max(); diff --git a/src/txmempool.h b/src/txmempool.h index 2e2cbe526a..f44e78fde5 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -104,7 +104,7 @@ private: const unsigned int entryHeight; //!< Chain height when entering the mempool const bool spendsCoinbase; //!< keep track of transactions that spend a coinbase const int64_t sigOpCost; //!< Total sigop cost - CAmount feeDelta{0}; //!< Used for determining the priority of the transaction for mining in a block + CAmount m_modified_fee; //!< Used for determining the priority of the transaction for mining in a block LockPoints lockPoints; //!< Track the height and time at which tx was final // Information about descendants of this transaction that are in the @@ -134,7 +134,7 @@ public: std::chrono::seconds GetTime() const { return std::chrono::seconds{nTime}; } unsigned int GetHeight() const { return entryHeight; } int64_t GetSigOpCost() const { return sigOpCost; } - CAmount GetModifiedFee() const { return nFee + feeDelta; } + CAmount GetModifiedFee() const { return m_modified_fee; } size_t DynamicMemoryUsage() const { return nUsageSize; } const LockPoints& GetLockPoints() const { return lockPoints; } @@ -142,9 +142,8 @@ public: void UpdateDescendantState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount); // Adjusts the ancestor state void UpdateAncestorState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int64_t modifySigOps); - // Updates the fee delta used for mining priority score, and the - // modified fees with descendants/ancestors. - void UpdateFeeDelta(CAmount newFeeDelta); + // Updates the modified fees with descendants/ancestors. + void UpdateModifiedFee(CAmount fee_diff); // Update the LockPoints after a reorg void UpdateLockPoints(const LockPoints& lp); diff --git a/src/util/sock.cpp b/src/util/sock.cpp index 7d5069423a..2588575d81 100644 --- a/src/util/sock.cpp +++ b/src/util/sock.cpp @@ -39,11 +39,11 @@ Sock::Sock(Sock&& other) other.m_socket = INVALID_SOCKET; } -Sock::~Sock() { Reset(); } +Sock::~Sock() { Close(); } Sock& Sock::operator=(Sock&& other) { - Reset(); + Close(); m_socket = other.m_socket; other.m_socket = INVALID_SOCKET; return *this; @@ -51,15 +51,6 @@ Sock& Sock::operator=(Sock&& other) SOCKET Sock::Get() const { return m_socket; } -SOCKET Sock::Release() -{ - const SOCKET s = m_socket; - m_socket = INVALID_SOCKET; - return s; -} - -void Sock::Reset() { CloseSocket(m_socket); } - ssize_t Sock::Send(const void* data, size_t len, int flags) const { return send(m_socket, static_cast<const char*>(data), len, flags); @@ -75,6 +66,16 @@ int Sock::Connect(const sockaddr* addr, socklen_t addr_len) const return connect(m_socket, addr, addr_len); } +int Sock::Bind(const sockaddr* addr, socklen_t addr_len) const +{ + return bind(m_socket, addr, addr_len); +} + +int Sock::Listen(int backlog) const +{ + return listen(m_socket, backlog); +} + std::unique_ptr<Sock> Sock::Accept(sockaddr* addr, socklen_t* addr_len) const { #ifdef WIN32 @@ -111,6 +112,11 @@ int Sock::SetSockOpt(int level, int opt_name, const void* opt_val, socklen_t opt return setsockopt(m_socket, level, opt_name, static_cast<const char*>(opt_val), opt_len); } +int Sock::GetSockName(sockaddr* name, socklen_t* name_len) const +{ + return getsockname(m_socket, name, name_len); +} + bool Sock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const { // We need a `shared_ptr` owning `this` for `WaitMany()`, but don't want @@ -366,6 +372,22 @@ bool Sock::IsConnected(std::string& errmsg) const } } +void Sock::Close() +{ + if (m_socket == INVALID_SOCKET) { + return; + } +#ifdef WIN32 + int ret = closesocket(m_socket); +#else + int ret = close(m_socket); +#endif + if (ret) { + LogPrintf("Error closing socket %d: %s\n", m_socket, NetworkErrorString(WSAGetLastError())); + } + m_socket = INVALID_SOCKET; +} + #ifdef WIN32 std::string NetworkErrorString(int err) { @@ -389,19 +411,3 @@ std::string NetworkErrorString(int err) return SysErrorString(err); } #endif - -bool CloseSocket(SOCKET& hSocket) -{ - if (hSocket == INVALID_SOCKET) - return false; -#ifdef WIN32 - int ret = closesocket(hSocket); -#else - int ret = close(hSocket); -#endif - if (ret) { - LogPrintf("Socket close failed: %d. Error: %s\n", hSocket, NetworkErrorString(WSAGetLastError())); - } - hSocket = INVALID_SOCKET; - return ret != SOCKET_ERROR; -} diff --git a/src/util/sock.h b/src/util/sock.h index 3245820995..b854609c22 100644 --- a/src/util/sock.h +++ b/src/util/sock.h @@ -69,18 +69,6 @@ public: [[nodiscard]] virtual SOCKET Get() const; /** - * Get the value of the contained socket and drop ownership. It will not be closed by the - * destructor after this call. - * @return socket or INVALID_SOCKET if empty - */ - virtual SOCKET Release(); - - /** - * Close if non-empty. - */ - virtual void Reset(); - - /** * send(2) wrapper. Equivalent to `send(this->Get(), data, len, flags);`. Code that uses this * wrapper can be unit tested if this method is overridden by a mock Sock implementation. */ @@ -99,6 +87,18 @@ public: [[nodiscard]] virtual int Connect(const sockaddr* addr, socklen_t addr_len) const; /** + * bind(2) wrapper. Equivalent to `bind(this->Get(), addr, addr_len)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + [[nodiscard]] virtual int Bind(const sockaddr* addr, socklen_t addr_len) const; + + /** + * listen(2) wrapper. Equivalent to `listen(this->Get(), backlog)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + [[nodiscard]] virtual int Listen(int backlog) const; + + /** * accept(2) wrapper. Equivalent to `std::make_unique<Sock>(accept(this->Get(), addr, addr_len))`. * Code that uses this wrapper can be unit tested if this method is overridden by a mock Sock * implementation. @@ -126,6 +126,13 @@ public: const void* opt_val, socklen_t opt_len) const; + /** + * getsockname(2) wrapper. Equivalent to + * `getsockname(this->Get(), name, name_len)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + [[nodiscard]] virtual int GetSockName(sockaddr* name, socklen_t* name_len) const; + using Event = uint8_t; /** @@ -252,12 +259,15 @@ protected: * Contained socket. `INVALID_SOCKET` designates the object is empty. */ SOCKET m_socket; + +private: + /** + * Close `m_socket` if it is not `INVALID_SOCKET`. + */ + void Close(); }; /** Return readable error string for a network error code */ std::string NetworkErrorString(int err); -/** Close socket and set hSocket to INVALID_SOCKET */ -bool CloseSocket(SOCKET& hSocket); - #endif // BITCOIN_UTIL_SOCK_H diff --git a/src/util/system.cpp b/src/util/system.cpp index e83ad11e80..140d51a1cc 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -62,9 +62,6 @@ #pragma warning(disable:4717) #endif -#ifndef NOMINMAX -#define NOMINMAX -#endif #include <codecvt> #include <io.h> /* for _commit */ diff --git a/src/util/time.h b/src/util/time.h index ad91a72860..0f87d66c2e 100644 --- a/src/util/time.h +++ b/src/util/time.h @@ -40,10 +40,15 @@ void UninterruptibleSleep(const std::chrono::microseconds& n); * This helper is used to convert durations/time_points before passing them over an * interface that doesn't support std::chrono (e.g. RPC, debug log, or the GUI) */ +template <typename Dur1, typename Dur2> +constexpr auto Ticks(Dur2 d) +{ + return std::chrono::duration_cast<Dur1>(d).count(); +} template <typename Duration, typename Timepoint> constexpr auto TicksSinceEpoch(Timepoint t) { - return std::chrono::time_point_cast<Duration>(t).time_since_epoch().count(); + return Ticks<Duration>(t.time_since_epoch()); } constexpr int64_t count_seconds(std::chrono::seconds t) { return t.count(); } constexpr int64_t count_milliseconds(std::chrono::milliseconds t) { return t.count(); } @@ -52,11 +57,6 @@ constexpr int64_t count_microseconds(std::chrono::microseconds t) { return t.cou using SecondsDouble = std::chrono::duration<double, std::chrono::seconds::period>; /** - * Helper to count the seconds in any std::chrono::duration type - */ -inline double CountSecondsDouble(SecondsDouble t) { return t.count(); } - -/** * DEPRECATED * Use either ClockType::now() or Now<TimePointType>() if a cast is needed. * ClockType is diff --git a/src/validation.cpp b/src/validation.cpp index e9abdb7b17..6b21d33871 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3483,15 +3483,15 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat const int nHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; // Enforce BIP113 (Median Time Past). - int nLockTimeFlags = 0; + bool enforce_locktime_median_time_past{false}; if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_CSV)) { assert(pindexPrev != nullptr); - nLockTimeFlags |= LOCKTIME_MEDIAN_TIME_PAST; + enforce_locktime_median_time_past = true; } - int64_t nLockTimeCutoff = (nLockTimeFlags & LOCKTIME_MEDIAN_TIME_PAST) - ? pindexPrev->GetMedianTimePast() - : block.GetBlockTime(); + const int64_t nLockTimeCutoff{enforce_locktime_median_time_past ? + pindexPrev->GetMedianTimePast() : + block.GetBlockTime()}; // Check that all transactions are finalized for (const auto& tx : block.vtx) { diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index 65a5c83366..d08d3664c4 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -33,12 +33,11 @@ public: CTxDestination destChange = CNoDestination(); //! Override the default change type if set, ignored if destChange is set std::optional<OutputType> m_change_type; - //! If false, only selected inputs are used - bool m_add_inputs = true; //! If false, only safe inputs will be used bool m_include_unsafe_inputs = false; - //! If false, allows unselected inputs, but requires all selected inputs be used - bool fAllowOtherInputs = false; + //! If true, the selection process can add extra unselected inputs from the wallet + //! while requires all selected inputs be used + bool m_allow_other_inputs = false; //! Includes watch only addresses which are solvable bool fAllowWatchOnly = false; //! Override automatic min/max checks on fee, m_feerate must be set if true diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index afd2b83971..5e70ed4a30 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -213,7 +213,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo for (const auto& inputs : wtx.tx->vin) { new_coin_control.Select(COutPoint(inputs.prevout)); } - new_coin_control.fAllowOtherInputs = true; + new_coin_control.m_allow_other_inputs = true; // We cannot source new unconfirmed inputs(bip125 rule 2) new_coin_control.m_min_depth = 1; diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index e2ea2b691d..0e8cee5db8 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -535,8 +535,8 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, }, true, true); - if (options.exists("add_inputs") ) { - coinControl.m_add_inputs = options["add_inputs"].get_bool(); + if (options.exists("add_inputs")) { + coinControl.m_allow_other_inputs = options["add_inputs"].get_bool(); } if (options.exists("changeAddress") || options.exists("change_address")) { @@ -823,7 +823,7 @@ RPCHelpMan fundrawtransaction() int change_position; CCoinControl coin_control; // Automatically select (additional) coins. Can be overridden by options.add_inputs. - coin_control.m_add_inputs = true; + coin_control.m_allow_other_inputs = true; FundTransaction(*pwallet, tx, fee, change_position, request.params[1], coin_control, /*override_min_fee=*/true); UniValue result(UniValue::VOBJ); @@ -1225,7 +1225,7 @@ RPCHelpMan send() CCoinControl coin_control; // Automatically select coins, unless at least one is manually selected. Can // be overridden by options.add_inputs. - coin_control.m_add_inputs = rawTx.vin.size() == 0; + coin_control.m_allow_other_inputs = rawTx.vin.size() == 0; SetOptionsInputWeights(options["inputs"], options); FundTransaction(*pwallet, rawTx, fee, change_position, options, coin_control, /*override_min_fee=*/false); @@ -1649,7 +1649,7 @@ RPCHelpMan walletcreatefundedpsbt() CCoinControl coin_control; // Automatically select coins, unless at least one is manually selected. Can // be overridden by options.add_inputs. - coin_control.m_add_inputs = rawTx.vin.size() == 0; + coin_control.m_allow_other_inputs = rawTx.vin.size() == 0; SetOptionsInputWeights(request.params[0], options); FundTransaction(wallet, rawTx, fee, change_position, options, coin_control, /*override_min_fee=*/true); diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 8633e7c62c..1fec82a485 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -2180,6 +2180,19 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& *keys = Merge(*keys, *pk_keys); } } + for (const auto& pk_pair : input.m_tap_bip32_paths) { + const XOnlyPubKey& pubkey = pk_pair.first; + for (unsigned char prefix : {0x02, 0x03}) { + unsigned char b[33] = {prefix}; + std::copy(pubkey.begin(), pubkey.end(), b + 1); + CPubKey fullpubkey; + fullpubkey.Set(b, b + 33); + std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(fullpubkey); + if (pk_keys) { + *keys = Merge(*keys, *pk_keys); + } + } + } } SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize); diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 4547dc4133..5799a9ff2a 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -168,15 +168,10 @@ CoinsResult AvailableCoins(const CWallet& wallet, const CTxOut& output = wtx.tx->vout[i]; const COutPoint outpoint(wtxid, i); - // Only consider selected coins if add_inputs is false - if (coinControl && !coinControl->m_add_inputs && !coinControl->IsSelected(outpoint)) { - continue; - } - if (output.nValue < nMinimumAmount || output.nValue > nMaximumAmount) continue; - if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(outpoint)) + if (coinControl && coinControl->HasSelected() && !coinControl->m_allow_other_inputs && !coinControl->IsSelected(outpoint)) continue; if (wallet.IsLockedCoin(outpoint)) @@ -438,23 +433,6 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec OutputGroup preset_inputs(coin_selection_params); - // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) - if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) - { - for (const COutput& out : vCoins) { - if (!out.spendable) continue; - /* Set ancestors and descendants to 0 as these don't matter for preset inputs as no actual selection is being done. - * positive_only is set to false because we want to include all preset inputs, even if they are dust. - */ - preset_inputs.Insert(out, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false); - } - SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL); - result.AddInput(preset_inputs); - if (result.GetSelectedValue() < nTargetValue) return std::nullopt; - result.ComputeAndSetWaste(coin_selection_params.m_cost_of_change); - return result; - } - // calculate value from preset inputs and store them std::set<COutPoint> preset_coins; @@ -463,15 +441,14 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec for (const COutPoint& outpoint : vPresetInputs) { int input_bytes = -1; CTxOut txout; - std::map<uint256, CWalletTx>::const_iterator it = wallet.mapWallet.find(outpoint.hash); - if (it != wallet.mapWallet.end()) { - const CWalletTx& wtx = it->second; + auto ptr_wtx = wallet.GetWalletTx(outpoint.hash); + if (ptr_wtx) { // Clearly invalid input, fail - if (wtx.tx->vout.size() <= outpoint.n) { + if (ptr_wtx->tx->vout.size() <= outpoint.n) { return std::nullopt; } - input_bytes = GetTxSpendSize(wallet, wtx, outpoint.n, false); - txout = wtx.tx->vout.at(outpoint.n); + input_bytes = GetTxSpendSize(wallet, *ptr_wtx, outpoint.n, false); + txout = ptr_wtx->tx->vout.at(outpoint.n); } else { // The input is external. We did not find the tx in mapWallet. if (!coin_control.GetExternalOutput(outpoint, txout)) { @@ -502,6 +479,15 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec preset_inputs.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false); } + // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) + if (coin_control.HasSelected() && !coin_control.m_allow_other_inputs) { + SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL); + result.AddInput(preset_inputs); + if (result.GetSelectedValue() < nTargetValue) return std::nullopt; + result.ComputeAndSetWaste(coin_selection_params.m_cost_of_change); + return result; + } + // remove preset inputs from vCoins so that Coin Selection doesn't pick them. for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();) { @@ -1033,8 +1019,6 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, vecSend.push_back(recipient); } - coinControl.fAllowOtherInputs = true; - // Acquire the locks to prevent races to the new locked unspents between the // CreateTransaction call and LockCoin calls (when lockUnspents is true). LOCK(wallet.cs_wallet); diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 76f28917a4..d6f47e9954 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -336,7 +336,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) add_coin(coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); add_coin(coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); CCoinControl coin_control; - coin_control.fAllowOtherInputs = true; + coin_control.m_allow_other_inputs = true; coin_control.Select(coins.at(0).outpoint); coin_selection_params_bnb.m_effective_feerate = CFeeRate(0); const auto result10 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb); @@ -392,7 +392,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) expected_result.Clear(); add_coin(9 * CENT, 2, expected_result); add_coin(1 * CENT, 2, expected_result); - coin_control.fAllowOtherInputs = true; + coin_control.m_allow_other_inputs = true; coin_control.Select(coins.at(1).outpoint); // pre select 9 coin const auto result13 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb); BOOST_CHECK(EquivalentResult(expected_result, *result13)); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ea306183ef..041481559b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -663,16 +663,13 @@ void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid, Walle } -void CWallet::AddToSpends(const uint256& wtxid, WalletBatch* batch) +void CWallet::AddToSpends(const CWalletTx& wtx, WalletBatch* batch) { - auto it = mapWallet.find(wtxid); - assert(it != mapWallet.end()); - const CWalletTx& thisTx = it->second; - if (thisTx.IsCoinBase()) // Coinbases don't spend anything! + if (wtx.IsCoinBase()) // Coinbases don't spend anything! return; - for (const CTxIn& txin : thisTx.tx->vin) - AddToSpends(txin.prevout, wtxid, batch); + for (const CTxIn& txin : wtx.tx->vin) + AddToSpends(txin.prevout, wtx.GetHash(), batch); } bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) @@ -967,7 +964,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const wtx.nOrderPos = IncOrderPosNext(&batch); wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); wtx.nTimeSmart = ComputeTimeSmart(wtx, rescanning_old_block); - AddToSpends(hash, &batch); + AddToSpends(wtx, &batch); } if (!fInsertedNew) @@ -1066,7 +1063,7 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx if (/* insertion took place */ ins.second) { wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); } - AddToSpends(hash); + AddToSpends(wtx); for (const CTxIn& txin : wtx.tx->vin) { auto it = mapWallet.find(txin.prevout.hash); if (it != mapWallet.end()) { @@ -2009,6 +2006,35 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp } } + // Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY + if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) { + // Figure out if any non_witness_utxos should be dropped + std::vector<unsigned int> to_drop; + for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { + const auto& input = psbtx.inputs.at(i); + int wit_ver; + std::vector<unsigned char> wit_prog; + if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) { + // There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos + to_drop.clear(); + break; + } + if (wit_ver == 0) { + // Segwit v0, so we cannot drop any non_witness_utxos + to_drop.clear(); + break; + } + if (input.non_witness_utxo) { + to_drop.push_back(i); + } + } + + // Drop the non_witness_utxos that we can drop + for (unsigned int i : to_drop) { + psbtx.inputs.at(i).non_witness_utxo = nullptr; + } + } + // Complete if every input is now signed complete = true; for (const auto& input : psbtx.inputs) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a75b6981e1..bf42b3da9e 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -262,7 +262,7 @@ private: typedef std::multimap<COutPoint, uint256> TxSpends; TxSpends mapTxSpends GUARDED_BY(cs_wallet); void AddToSpends(const COutPoint& outpoint, const uint256& wtxid, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void AddToSpends(const uint256& wtxid, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void AddToSpends(const CWalletTx& wtx, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Add a transaction to the wallet, or update it. confirm.block_* should diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 79e0a330b7..a04446c840 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -1186,13 +1186,36 @@ std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase() } /** Return object for accessing temporary in-memory database. */ -std::unique_ptr<WalletDatabase> CreateMockWalletDatabase() +std::unique_ptr<WalletDatabase> CreateMockWalletDatabase(DatabaseOptions& options) { - DatabaseOptions options; + + std::optional<DatabaseFormat> format; + if (options.require_format) format = options.require_format; + if (!format) { +#ifdef USE_BDB + format = DatabaseFormat::BERKELEY; +#endif #ifdef USE_SQLITE - return std::make_unique<SQLiteDatabase>("", "", options, true); -#elif USE_BDB + format = DatabaseFormat::SQLITE; +#endif + } + + if (format == DatabaseFormat::SQLITE) { +#ifdef USE_SQLITE + return std::make_unique<SQLiteDatabase>(":memory:", "", options, true); +#endif + assert(false); + } + +#ifdef USE_BDB return std::make_unique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "", options); #endif + assert(false); +} + +std::unique_ptr<WalletDatabase> CreateMockWalletDatabase() +{ + DatabaseOptions options; + return CreateMockWalletDatabase(options); } } // namespace wallet diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 3dfe781d56..a04ea598b6 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -301,6 +301,7 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, st std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase(); /** Return object for accessing temporary in-memory database. */ +std::unique_ptr<WalletDatabase> CreateMockWalletDatabase(DatabaseOptions& options); std::unique_ptr<WalletDatabase> CreateMockWalletDatabase(); } // namespace wallet diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json index 8672400a92..430a1802a8 100644 --- a/test/functional/data/rpc_psbt.json +++ b/test/functional/data/rpc_psbt.json @@ -27,7 +27,18 @@ "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQQAAQQBagA=", "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQUAAQUBUQA=", "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQcAAQcBUQA=", - "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQgBAAEIAwEBUQA=" + "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQgBAAEIAwEBUQA=", + "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARchAv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyAAAA", + "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARM/Fzuz02wHSvtxb+xjB6BpouRQuZXzyCeFlFq43w4kJg3NcDsMvzTeOZGEqUgawrNYbbZgHwJqd/fkk4SBvDR1AAAA", + "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARNCFzuz02wHSvtxb+xjB6BpouRQuZXzyCeFlFq43w4kJg3NcDsMvzTeOZGEqUgawrNYbbZgHwJqd/fkk4SBvDR1FwGqAAAA", + "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" ], "valid" : [ "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA", @@ -43,7 +54,13 @@ "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" ], "creator" : [ { diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index a3a5e9e27d..62e9bec663 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -192,14 +192,12 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): # Sanity check -- if we chose inputs that are too small, skip continue - tx = self.wallet.create_self_transfer_multi( + self.wallet.send_self_transfer_multi( from_node=node, utxos_to_spend=utxos_to_spend, num_outputs=3, - fee_per_output=FEE // 3) - - # Send the transaction to get into the mempool (skip fee-checks to run faster) - node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0) + fee_per_output=FEE // 3, + ) num_transactions += 1 def run_test(self): diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index 422612a78e..b0cbcf4edf 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -51,9 +51,9 @@ def small_txpuzzle_randfee( if total_in <= amount + fee: raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}") tx = wallet.create_self_transfer_multi( - from_node=from_node, utxos_to_spend=utxos_to_spend, - fee_per_output=0) + fee_per_output=0, + )["tx"] tx.vout[0].nValue = int((total_in - amount - fee) * COIN) tx.vout.append(deepcopy(tx.vout[0])) tx.vout[1].nValue = int(amount * COIN) diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 4c1e86e93d..91dc222bab 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -472,12 +472,10 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Now attempt to submit a tx that double-spends all the root tx inputs, which # would invalidate `num_txs_invalidated` transactions. - double_tx = wallet.create_self_transfer_multi( - from_node=normal_node, + tx_hex = wallet.create_self_transfer_multi( utxos_to_spend=root_utxos, fee_per_output=10_000_000, # absurdly high feerate - ) - tx_hex = double_tx.serialize().hex() + )["hex"] if failure_expected: assert_raises_rpc_error( @@ -712,6 +710,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Higher fee, higher feerate, different txid, but the replacement does not provide a relay # fee conforming to node's `incrementalrelayfee` policy of 1000 sat per KB. + assert_equal(self.nodes[0].getmempoolinfo()["incrementalrelayfee"], Decimal("0.00001")) tx.vout[0].nValue -= 1 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex()) diff --git a/test/functional/feature_signet.py b/test/functional/feature_signet.py index 6578caee3f..4c1e48af6d 100755 --- a/test/functional/feature_signet.py +++ b/test/functional/feature_signet.py @@ -39,6 +39,14 @@ class SignetBasicTest(BitcoinTestFramework): shared_args3, shared_args3, ] + def setup_network(self): + self.setup_nodes() + + # Setup the three signets, which are incompatible with each other + self.connect_nodes(0, 1) + self.connect_nodes(2, 3) + self.connect_nodes(4, 5) + def run_test(self): self.log.info("basic tests using OP_TRUE challenge") diff --git a/test/functional/interface_usdt_utxocache.py b/test/functional/interface_usdt_utxocache.py index 0c7f351e66..f48ff9699d 100755 --- a/test/functional/interface_usdt_utxocache.py +++ b/test/functional/interface_usdt_utxocache.py @@ -169,8 +169,7 @@ class UTXOCacheTracepointTest(BitcoinTestFramework): # Create a transaction and invalidate it by changing the txid of the previous # output to the coinbase txid of the block at height 1. - invalid_tx = self.wallet.create_self_transfer( - from_node=self.nodes[0])["tx"] + invalid_tx = self.wallet.create_self_transfer()["tx"] invalid_tx.vin[0].prevout.hash = int(block_1_coinbase_txid, 16) self.log.info("hooking into the utxocache:uncache tracepoint") diff --git a/test/functional/mempool_reorg.py b/test/functional/mempool_reorg.py index 8dd6cc9cea..47ff520713 100755 --- a/test/functional/mempool_reorg.py +++ b/test/functional/mempool_reorg.py @@ -68,10 +68,8 @@ class MempoolCoinbaseTest(BitcoinTestFramework): assert_raises_rpc_error(-26, 'non-final', self.nodes[0].sendrawtransaction, timelock_tx) self.log.info("Create spend_2_1 and spend_3_1") - spend_2_utxo = wallet.get_utxo(txid=spend_2['txid']) - spend_2_1 = wallet.create_self_transfer(utxo_to_spend=spend_2_utxo) - spend_3_utxo = wallet.get_utxo(txid=spend_3['txid']) - spend_3_1 = wallet.create_self_transfer(utxo_to_spend=spend_3_utxo) + spend_2_1 = wallet.create_self_transfer(utxo_to_spend=spend_2["new_utxo"]) + spend_3_1 = wallet.create_self_transfer(utxo_to_spend=spend_3["new_utxo"]) self.log.info("Broadcast and mine spend_3_1") spend_3_1_id = self.nodes[0].sendrawtransaction(spend_3_1['hex']) diff --git a/test/functional/mempool_unbroadcast.py b/test/functional/mempool_unbroadcast.py index 37ef4a9157..7587adc257 100755 --- a/test/functional/mempool_unbroadcast.py +++ b/test/functional/mempool_unbroadcast.py @@ -40,7 +40,7 @@ class MempoolUnbroadcastTest(BitcoinTestFramework): wallet_tx_hsh = node.sendtoaddress(addr, 0.0001) # generate a txn using sendrawtransaction - txFS = self.wallet.create_self_transfer(from_node=node) + txFS = self.wallet.create_self_transfer() rpc_tx_hsh = node.sendrawtransaction(txFS["hex"]) # check transactions are in unbroadcast using rpc diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py index e0f2446b2d..fb3974c1d5 100755 --- a/test/functional/mining_prioritisetransaction.py +++ b/test/functional/mining_prioritisetransaction.py @@ -212,7 +212,7 @@ class PrioritiseTransactionTest(BitcoinTestFramework): assert x not in mempool # Create a free transaction. Should be rejected. - tx_res = self.wallet.create_self_transfer(from_node=self.nodes[0], fee_rate=0) + tx_res = self.wallet.create_self_transfer(fee_rate=0) tx_hex = tx_res['hex'] tx_id = tx_res['txid'] diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py index 1dc3a5b9a0..185011c2df 100755 --- a/test/functional/p2p_permissions.py +++ b/test/functional/p2p_permissions.py @@ -91,6 +91,7 @@ class P2PPermissionsTests(BitcoinTestFramework): self.nodes[1].assert_start_raises_init_error(["-whitelist=oopsie@127.0.0.1"], "Invalid P2P permission", match=ErrorMatch.PARTIAL_REGEX) self.nodes[1].assert_start_raises_init_error(["-whitelist=noban@127.0.0.1:230"], "Invalid netmask specified in", match=ErrorMatch.PARTIAL_REGEX) self.nodes[1].assert_start_raises_init_error(["-whitebind=noban@127.0.0.1/10"], "Cannot resolve -whitebind address", match=ErrorMatch.PARTIAL_REGEX) + self.nodes[1].assert_start_raises_init_error(["-whitebind=noban@127.0.0.1", "-bind=127.0.0.1", "-listen=0"], "Cannot set -bind or -whitebind together with -listen=0", match=ErrorMatch.PARTIAL_REGEX) def check_tx_relay(self): block_op_true = self.nodes[0].getblock(self.generatetoaddress(self.nodes[0], 100, ADDRESS_BCRT1_P2WSH_OP_TRUE)[0]) diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index 89ddfd3bcf..952f1e5cc5 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -1998,7 +1998,7 @@ class SegWitTest(BitcoinTestFramework): def serialize(self): return serialize_with_bogus_witness(self.tx) - tx = self.wallet.create_self_transfer(from_node=self.nodes[0])['tx'] + tx = self.wallet.create_self_transfer()['tx'] assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, hexstring=serialize_with_bogus_witness(tx).hex(), iswitness=True) with self.nodes[0].assert_debug_log(['Unknown transaction optional data']): self.test_node.send_and_ping(msg_bogus_tx(tx)) diff --git a/test/functional/rpc_mempool_info.py b/test/functional/rpc_mempool_info.py index cd7a48d387..9b5a3b9112 100755 --- a/test/functional/rpc_mempool_info.py +++ b/test/functional/rpc_mempool_info.py @@ -4,7 +4,6 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test RPCs that retrieve information from the mempool.""" -from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -19,7 +18,6 @@ class RPCMempoolInfoTest(BitcoinTestFramework): def run_test(self): self.wallet = MiniWallet(self.nodes[0]) - self.generate(self.wallet, COINBASE_MATURITY + 1) self.wallet.rescan_utxos() confirmed_utxo = self.wallet.get_utxo() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 444e56610e..d2a888fd31 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -9,7 +9,7 @@ from decimal import Decimal from itertools import product from test_framework.descriptors import descsum_create -from test_framework.key import ECKey +from test_framework.key import ECKey, H_POINT from test_framework.messages import ( ser_compact_size, WITNESS_SCALE_FACTOR, @@ -723,5 +723,46 @@ class PSBTTest(BitcoinTestFramework): ) assert_equal(psbt2["fee"], psbt3["fee"]) + self.log.info("Test signing inputs that the wallet has keys for but is not watching the scripts") + self.nodes[1].createwallet(wallet_name="scriptwatchonly", disable_private_keys=True) + watchonly = self.nodes[1].get_wallet_rpc("scriptwatchonly") + + eckey = ECKey() + eckey.generate() + privkey = bytes_to_wif(eckey.get_bytes()) + + desc = descsum_create("wsh(pkh({}))".format(eckey.get_pubkey().get_bytes().hex())) + if self.options.descriptors: + res = watchonly.importdescriptors([{"desc": desc, "timestamp": "now"}]) + else: + res = watchonly.importmulti([{"desc": desc, "timestamp": "now"}]) + assert res[0]["success"] + addr = self.nodes[0].deriveaddresses(desc)[0] + self.nodes[0].sendtoaddress(addr, 10) + self.generate(self.nodes[0], 1) + self.nodes[0].importprivkey(privkey) + + psbt = watchonly.sendall([wallet.getnewaddress()])["psbt"] + psbt = self.nodes[0].walletprocesspsbt(psbt)["psbt"] + self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"]) + + # Same test but for taproot + if self.options.descriptors: + eckey = ECKey() + eckey.generate() + privkey = bytes_to_wif(eckey.get_bytes()) + + desc = descsum_create("tr({},pk({}))".format(H_POINT, eckey.get_pubkey().get_bytes().hex())) + res = watchonly.importdescriptors([{"desc": desc, "timestamp": "now"}]) + assert res[0]["success"] + addr = self.nodes[0].deriveaddresses(desc)[0] + self.nodes[0].sendtoaddress(addr, 10) + self.generate(self.nodes[0], 1) + self.nodes[0].importdescriptors([{"desc": descsum_create("tr({})".format(privkey)), "timestamp":"now"}]) + + psbt = watchonly.sendall([wallet.getnewaddress()])["psbt"] + psbt = self.nodes[0].walletprocesspsbt(psbt)["psbt"] + self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"]) + if __name__ == '__main__': PSBTTest().main() diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index dee6d777af..26a5da85b7 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -285,7 +285,7 @@ class RawTransactionsTest(BitcoinTestFramework): # Test a transaction with a large fee. # Fee rate is 0.20000000 BTC/kvB - tx = self.wallet.create_self_transfer(from_node=self.nodes[0], fee_rate=Decimal("0.20000000")) + tx = self.wallet.create_self_transfer(fee_rate=Decimal("0.20000000")) # Thus, testmempoolaccept should reject testres = self.nodes[2].testmempoolaccept([tx['hex']])[0] assert_equal(testres['allowed'], False) diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py index e5dea66963..68afc1383d 100644 --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -15,6 +15,10 @@ import unittest from .util import modinv +# Point with no known discrete log. +H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" + + def TaggedHash(tag, data): ss = hashlib.sha256(tag.encode('utf-8')).digest() ss += ss diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 3f02d21d42..c880aabd21 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -581,6 +581,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): def connect_nodes(self, a, b): from_connection = self.nodes[a] to_connection = self.nodes[b] + from_num_peers = 1 + len(from_connection.getpeerinfo()) + to_num_peers = 1 + len(to_connection.getpeerinfo()) ip_port = "127.0.0.1:" + str(p2p_port(b)) from_connection.addnode(ip_port, "onetry") # poll until version handshake complete to avoid race conditions @@ -588,10 +590,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): # See comments in net_processing: # * Must have a version message before anything else # * Must have a verack message before anything else - wait_until_helper(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo())) - wait_until_helper(lambda: all(peer['version'] != 0 for peer in to_connection.getpeerinfo())) - wait_until_helper(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo())) - wait_until_helper(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in to_connection.getpeerinfo())) + self.wait_until(lambda: sum(peer['version'] != 0 for peer in from_connection.getpeerinfo()) == from_num_peers) + self.wait_until(lambda: sum(peer['version'] != 0 for peer in to_connection.getpeerinfo()) == to_num_peers) + self.wait_until(lambda: sum(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo()) == from_num_peers) + self.wait_until(lambda: sum(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in to_connection.getpeerinfo()) == to_num_peers) def disconnect_nodes(self, a, b): def disconnect_nodes_helper(from_connection, node_num): @@ -620,7 +622,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): raise # wait to disconnect - wait_until_helper(lambda: not get_peer_ids(), timeout=5) + self.wait_until(lambda: not get_peer_ids(), timeout=5) disconnect_nodes_helper(self.nodes[a], b) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 1c64fa4028..6a588275ea 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -527,9 +527,8 @@ def create_lots_of_big_transactions(mini_wallet, node, fee, tx_batch_size, txout use_internal_utxos = utxos is None for _ in range(tx_batch_size): tx = mini_wallet.create_self_transfer( - from_node=node, - utxo_to_spend=None if use_internal_utxos else utxos.pop(), - fee_rate=0, + utxo_to_spend=None if use_internal_utxos else utxos.pop(), + fee_rate=0, )["tx"] tx.vout[0].nValue -= fee_sats tx.vout.extend(txouts) diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index e192c3b3e9..68d5dfa880 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -86,8 +86,7 @@ class MiniWallet: def __init__(self, test_node, *, mode=MiniWalletMode.ADDRESS_OP_TRUE): self._test_node = test_node self._utxos = [] - self._priv_key = None - self._address = None + self._mode = mode assert isinstance(mode, MiniWalletMode) if mode == MiniWalletMode.RAW_OP_TRUE: @@ -102,6 +101,9 @@ class MiniWallet: self._address, self._internal_key = create_deterministic_address_bcrt1_p2tr_op_true() self._scriptPubKey = bytes.fromhex(self._test_node.validateaddress(self._address)['scriptPubKey']) + def _create_utxo(self, *, txid, vout, value, height): + return {"txid": txid, "vout": vout, "value": value, "height": height} + def get_balance(self): return sum(u['value'] for u in self._utxos) @@ -111,17 +113,26 @@ class MiniWallet: res = self._test_node.scantxoutset(action="start", scanobjects=[self.get_descriptor()]) assert_equal(True, res['success']) for utxo in res['unspents']: - self._utxos.append({'txid': utxo['txid'], 'vout': utxo['vout'], 'value': utxo['amount'], 'height': utxo['height']}) + self._utxos.append(self._create_utxo(txid=utxo["txid"], vout=utxo["vout"], value=utxo["amount"], height=utxo["height"])) def scan_tx(self, tx): - """Scan the tx for self._scriptPubKey outputs and add them to self._utxos""" + """Scan the tx and adjust the internal list of owned utxos""" + for spent in tx["vin"]: + # Mark spent. This may happen when the caller has ownership of a + # utxo that remained in this wallet. For example, by passing + # mark_as_spent=False to get_utxo or by using an utxo returned by a + # create_self_transfer* call. + try: + self.get_utxo(txid=spent["txid"], vout=spent["vout"]) + except StopIteration: + pass for out in tx['vout']: if out['scriptPubKey']['hex'] == self._scriptPubKey.hex(): - self._utxos.append({'txid': tx['txid'], 'vout': out['n'], 'value': out['value'], 'height': 0}) + self._utxos.append(self._create_utxo(txid=tx["txid"], vout=out["n"], value=out["value"], height=0)) def sign_tx(self, tx, fixed_length=True): """Sign tx that has been created by MiniWallet in P2PK mode""" - assert self._priv_key is not None + assert_equal(self._mode, MiniWalletMode.RAW_P2PK) (sighash, err) = LegacySignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL) assert err is None # for exact fee calculation, create only signatures with fixed size by default (>49.89% probability): @@ -136,12 +147,16 @@ class MiniWallet: tx.rehash() def generate(self, num_blocks, **kwargs): - """Generate blocks with coinbase outputs to the internal address, and append the outputs to the internal list""" + """Generate blocks with coinbase outputs to the internal address, and call rescan_utxos""" blocks = self._test_node.generatetodescriptor(num_blocks, self.get_descriptor(), **kwargs) - for b in blocks: - block_info = self._test_node.getblock(blockhash=b, verbosity=2) - cb_tx = block_info['tx'][0] - self._utxos.append({'txid': cb_tx['txid'], 'vout': 0, 'value': cb_tx['vout'][0]['value'], 'height': block_info['height']}) + # Calling rescan_utxos here makes sure that after a generate the utxo + # set is in a clean state. For example, the wallet will update + # - if the caller consumed utxos, but never used them + # - if the caller sent a transaction that is not mined or got rbf'd + # - after block re-orgs + # - the utxo height for mined mempool txs + # - However, the wallet will not consider remaining mempool txs + self.rescan_utxos() return blocks def get_scriptPubKey(self): @@ -151,6 +166,7 @@ class MiniWallet: return descsum_create(f'raw({self._scriptPubKey.hex()})') def get_address(self): + assert_equal(self._mode, MiniWalletMode.ADDRESS_OP_TRUE) return self._address def get_utxo(self, *, txid: str = '', vout: Optional[int] = None, mark_as_spent=True) -> dict: @@ -180,10 +196,10 @@ class MiniWallet: self._utxos = [] return utxos - def send_self_transfer(self, **kwargs): + def send_self_transfer(self, *, from_node, **kwargs): """Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" tx = self.create_self_transfer(**kwargs) - self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx['hex']) + self.sendrawtransaction(from_node=from_node, tx_hex=tx['hex']) return tx def send_to(self, *, from_node, scriptPubKey, amount, fee=1000): @@ -198,35 +214,27 @@ class MiniWallet: Returns a tuple (txid, n) referring to the created external utxo outpoint. """ - tx = self.create_self_transfer(from_node=from_node, fee_rate=0)["tx"] + tx = self.create_self_transfer(fee_rate=0)["tx"] assert_greater_than_or_equal(tx.vout[0].nValue, amount + fee) tx.vout[0].nValue -= (amount + fee) # change output -> MiniWallet tx.vout.append(CTxOut(amount, scriptPubKey)) # arbitrary output -> to be returned txid = self.sendrawtransaction(from_node=from_node, tx_hex=tx.serialize().hex()) return txid, 1 - def send_self_transfer_multi(self, **kwargs): - """ - Create and send a transaction that spends the given UTXOs and creates a - certain number of outputs with equal amounts. - - Returns a dictionary with - - txid - - serialized transaction in hex format - - transaction as CTransaction instance - - list of newly created UTXOs, ordered by vout index - """ + def send_self_transfer_multi(self, *, from_node, **kwargs): + """Call create_self_transfer_multi and send the transaction.""" tx = self.create_self_transfer_multi(**kwargs) - txid = self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx.serialize().hex()) - return {'new_utxos': [self.get_utxo(txid=txid, vout=vout) for vout in range(len(tx.vout))], - 'txid': txid, 'hex': tx.serialize().hex(), 'tx': tx} + self.sendrawtransaction(from_node=from_node, tx_hex=tx["hex"]) + return tx def create_self_transfer_multi( - self, *, from_node, - utxos_to_spend: Optional[List[dict]] = None, - num_outputs=1, - sequence=0, - fee_per_output=1000): + self, + *, + utxos_to_spend: Optional[List[dict]] = None, + num_outputs=1, + sequence=0, + fee_per_output=1000, + ): """ Create and return a transaction that spends the given UTXOs and creates a certain number of outputs with equal amounts. @@ -234,7 +242,7 @@ class MiniWallet: utxos_to_spend = utxos_to_spend or [self.get_utxo()] # create simple tx template (1 input, 1 output) tx = self.create_self_transfer( - fee_rate=0, from_node=from_node, + fee_rate=0, utxo_to_spend=utxos_to_spend[0], sequence=sequence)["tx"] # duplicate inputs, witnesses and outputs @@ -251,39 +259,50 @@ class MiniWallet: outputs_value_total = inputs_value_total - fee_per_output * num_outputs for o in tx.vout: o.nValue = outputs_value_total // num_outputs - return tx - - def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node=None, utxo_to_spend=None, locktime=0, sequence=0): + txid = tx.rehash() + return { + "new_utxos": [self._create_utxo( + txid=txid, + vout=i, + value=Decimal(tx.vout[i].nValue) / COIN, + height=0, + ) for i in range(len(tx.vout))], + "txid": txid, + "hex": tx.serialize().hex(), + "tx": tx, + } + + def create_self_transfer(self, *, fee_rate=Decimal("0.003"), utxo_to_spend=None, locktime=0, sequence=0): """Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" - from_node = from_node or self._test_node utxo_to_spend = utxo_to_spend or self.get_utxo() - if self._priv_key is None: + if self._mode in (MiniWalletMode.RAW_OP_TRUE, MiniWalletMode.ADDRESS_OP_TRUE): vsize = Decimal(104) # anyone-can-spend - else: + elif self._mode == MiniWalletMode.RAW_P2PK: vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other) - send_value = int(COIN * (utxo_to_spend['value'] - fee_rate * (vsize / 1000))) + else: + assert False + send_value = utxo_to_spend["value"] - (fee_rate * vsize / 1000) assert send_value > 0 tx = CTransaction() tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=sequence)] - tx.vout = [CTxOut(send_value, self._scriptPubKey)] + tx.vout = [CTxOut(int(COIN * send_value), self._scriptPubKey)] tx.nLockTime = locktime - if not self._address: - # raw script - if self._priv_key is not None: - # P2PK, need to sign - self.sign_tx(tx) - else: - # anyone-can-spend - tx.vin[0].scriptSig = CScript([OP_NOP] * 43) # pad to identical size - else: + if self._mode == MiniWalletMode.RAW_P2PK: + self.sign_tx(tx) + elif self._mode == MiniWalletMode.RAW_OP_TRUE: + tx.vin[0].scriptSig = CScript([OP_NOP] * 43) # pad to identical size + elif self._mode == MiniWalletMode.ADDRESS_OP_TRUE: tx.wit.vtxinwit = [CTxInWitness()] tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key] + else: + assert False tx_hex = tx.serialize().hex() assert_equal(tx.get_vsize(), vsize) + new_utxo = self._create_utxo(txid=tx.rehash(), vout=0, value=send_value, height=0) - return {'txid': tx.rehash(), 'wtxid': tx.getwtxid(), 'hex': tx_hex, 'tx': tx} + return {"txid": new_utxo["txid"], "wtxid": tx.getwtxid(), "hex": tx_hex, "tx": tx, "new_utxo": new_utxo} def sendrawtransaction(self, *, from_node, tx_hex, maxfeerate=0, **kwargs): txid = from_node.sendrawtransaction(hexstring=tx_hex, maxfeerate=maxfeerate, **kwargs) diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py index ac74ff2484..ff11f421a1 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -454,7 +454,7 @@ class ImportDescriptorsTest(BitcoinTestFramework): send_txid = wmulti_priv.sendtoaddress(w0.getnewaddress(), 8) decoded = wmulti_priv.gettransaction(txid=send_txid, verbose=True)['decoded'] assert_equal(len(decoded['vin'][0]['txinwitness']), 4) - self.generate(self.nodes[0], 6) + self.sync_all() self.nodes[1].createwallet(wallet_name="wmulti_pub", disable_private_keys=True, blank=True, descriptors=True) wmulti_pub = self.nodes[1].get_wallet_rpc("wmulti_pub") diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py index d238c50bca..c8d4a1da45 100755 --- a/test/functional/wallet_taproot.py +++ b/test/functional/wallet_taproot.py @@ -8,6 +8,7 @@ import random from decimal import Decimal from test_framework.address import output_key_to_p2tr +from test_framework.key import H_POINT from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal from test_framework.descriptors import descsum_create @@ -157,9 +158,6 @@ KEYS = [ CHANGE_XPRV = "tprv8ZgxMBicQKsPcyDrWwiecVnTtFmfRwbfFqEfR4ZGWvq5aTTwLBWmAm5zrbMcYtb9gQNFfhRfqhhrBG37U3nhmXxEgeEPBJGHAPrHCrAd1WX" CHANGE_XPUB = "tpubD6NzVbkrYhZ4WSFeQbPF1uSaTHHbbGnZq8qShabZwCdUQwihxaLMMFhs2kidGF2qrRKiQVqw8VoyuTHj1bZqmMXMeciaU1gBjWA1sim2zUB" -# Point with no known discrete log. -H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" - def key(hex_key): """Construct an x-only pubkey from its hex representation.""" @@ -301,9 +299,21 @@ class WalletTaprootTest(BitcoinTestFramework): test_balance = int(self.psbt_online.getbalance() * 100000000) ret_amnt = random.randrange(100000, test_balance) # Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends. - psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200})['psbt'] - res = self.psbt_offline.walletprocesspsbt(psbt) - assert(res['complete']) + psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200, "change_type": "bech32m"})['psbt'] + res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) + + decoded = self.psbt_offline.decodepsbt(res["psbt"]) + if pattern.startswith("tr("): + for psbtin in decoded["inputs"]: + assert "non_witness_utxo" not in psbtin + assert "witness_utxo" in psbtin + assert "taproot_internal_key" in psbtin + assert "taproot_bip32_derivs" in psbtin + assert "taproot_key_path_sig" in psbtin or "taproot_script_path_sigs" in psbtin + if "taproot_script_path_sigs" in psbtin: + assert "taproot_merkle_root" in psbtin + assert "taproot_scripts" in psbtin + rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] txid = self.nodes[0].sendrawtransaction(rawtx) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) @@ -442,8 +452,7 @@ class WalletTaprootTest(BitcoinTestFramework): assert(self.rpc_online.gettransaction(txid)["confirmations"] > 0) psbt = self.psbt_online.sendall(recipients=[self.boring.getnewaddress()], options={"psbt": True})["psbt"] - res = self.psbt_offline.walletprocesspsbt(psbt) - assert(res['complete']) + res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] txid = self.nodes[0].sendrawtransaction(rawtx) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index e6cfe5f81a..67ef512895 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -1,10 +1,10 @@ # -fsanitize=undefined suppressions # ================================= -# This would be `signed-integer-overflow:CTxMemPool::PrioritiseTransaction`, +# The suppressions would be `sanitize-type:ClassName::MethodName`, # however due to a bug in clang the symbolizer is disabled and thus no symbol # names can be used. # See https://github.com/google/sanitizers/issues/1364 -signed-integer-overflow:txmempool.cpp + # https://github.com/bitcoin/bitcoin/pull/21798#issuecomment-829180719 signed-integer-overflow:policy/feerate.cpp |