diff options
188 files changed, 1714 insertions, 1963 deletions
diff --git a/ci/test/06_script_b.sh b/ci/test/06_script_b.sh index 075524741a..115d727ca3 100755 --- a/ci/test/06_script_b.sh +++ b/ci/test/06_script_b.sh @@ -60,6 +60,7 @@ if [ "${RUN_TIDY}" = "true" ]; then " src/rpc/signmessage.cpp"\ " src/test/fuzz/txorphan.cpp"\ " src/test/fuzz/util/"\ + " src/test/util/coins.cpp"\ " src/uint256.cpp"\ " src/util/bip32.cpp"\ " src/util/bytevectorhash.cpp"\ diff --git a/configure.ac b/configure.ac index 0f809169fc..72503f2b1c 100644 --- a/configure.ac +++ b/configure.ac @@ -963,11 +963,11 @@ if test "$use_hardening" != "no"; then dnl However, FORTIFY_SOURCE requires that there is some level of optimization, otherwise it does nothing and just creates a compiler warning. dnl Since FORTIFY_SOURCE is a no-op without optimizations, do not enable it when enable_debug is yes. if test "$enable_debug" != "yes"; then - AX_CHECK_PREPROC_FLAG([-D_FORTIFY_SOURCE=2],[ + AX_CHECK_PREPROC_FLAG([-D_FORTIFY_SOURCE=3],[ AX_CHECK_PREPROC_FLAG([-U_FORTIFY_SOURCE],[ HARDENED_CPPFLAGS="$HARDENED_CPPFLAGS -U_FORTIFY_SOURCE" ]) - HARDENED_CPPFLAGS="$HARDENED_CPPFLAGS -D_FORTIFY_SOURCE=2" + HARDENED_CPPFLAGS="$HARDENED_CPPFLAGS -D_FORTIFY_SOURCE=3" ]) fi diff --git a/contrib/devtools/security-check.py b/contrib/devtools/security-check.py index 8377b92736..6cd022ef17 100755 --- a/contrib/devtools/security-check.py +++ b/contrib/devtools/security-check.py @@ -34,7 +34,7 @@ def check_ELF_RELRO(binary) -> bool: flags = binary.get(lief.ELF.DYNAMIC_TAGS.FLAGS) if flags.value & lief.ELF.DYNAMIC_FLAGS.BIND_NOW: have_bindnow = True - except: + except Exception: have_bindnow = False return have_gnu_relro and have_bindnow diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py index 4b1cceb57c..f26236dd59 100755 --- a/contrib/devtools/symbol-check.py +++ b/contrib/devtools/symbol-check.py @@ -15,19 +15,19 @@ from typing import List, Dict import lief #type:ignore -# Debian 9 (Stretch) EOL: 2022. https://wiki.debian.org/DebianReleases#Production_Releases +# Debian 10 (Buster) EOL: 2024. https://wiki.debian.org/LTS # -# - g++ version 6.3.0 (https://packages.debian.org/search?suite=stretch&arch=any&searchon=names&keywords=g%2B%2B) -# - libc version 2.24 (https://packages.debian.org/search?suite=stretch&arch=any&searchon=names&keywords=libc6) +# - libgcc version 8.3.0 (https://packages.debian.org/search?suite=buster&arch=any&searchon=names&keywords=libgcc1) +# - libc version 2.28 (https://packages.debian.org/search?suite=buster&arch=any&searchon=names&keywords=libc6) # -# Ubuntu 16.04 (Xenial) EOL: 2026. https://wiki.ubuntu.com/Releases +# Ubuntu 18.04 (Bionic) EOL: 2028. https://wiki.ubuntu.com/ReleaseTeam # -# - g++ version 5.3.1 -# - libc version 2.23 +# - libgcc version 8.4.0 (https://packages.ubuntu.com/bionic/libgcc1) +# - libc version 2.27 (https://packages.ubuntu.com/bionic/libc6) # # CentOS Stream 8 EOL: 2024. https://wiki.centos.org/About/Product # -# - g++ version 8.5.0 (http://mirror.centos.org/centos/8-stream/AppStream/x86_64/os/Packages/) +# - libgcc version 8.5.0 (http://mirror.centos.org/centos/8-stream/AppStream/x86_64/os/Packages/) # - libc version 2.28 (http://mirror.centos.org/centos/8-stream/AppStream/x86_64/os/Packages/) # # See https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html for more info. @@ -35,10 +35,10 @@ import lief #type:ignore MAX_VERSIONS = { 'GCC': (4,8,0), 'GLIBC': { - lief.ELF.ARCH.x86_64: (2,18), - lief.ELF.ARCH.ARM: (2,18), - lief.ELF.ARCH.AARCH64:(2,18), - lief.ELF.ARCH.PPC64: (2,18), + lief.ELF.ARCH.x86_64: (2,27), + lief.ELF.ARCH.ARM: (2,27), + lief.ELF.ARCH.AARCH64:(2,27), + lief.ELF.ARCH.PPC64: (2,27), lief.ELF.ARCH.RISCV: (2,27), }, 'LIBATOMIC': (1,0), diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py index de73b02090..e304880140 100755 --- a/contrib/devtools/test-symbol-check.py +++ b/contrib/devtools/test-symbol-check.py @@ -38,31 +38,6 @@ class TestSymbolChecks(unittest.TestCase): executable = 'test1' cc = determine_wellknown_cmd('CC', 'gcc') - # there's no way to do this test for RISC-V at the moment; we build for - # RISC-V in a glibc 2.27 environment and we allow all symbols from 2.27. - if 'riscv' in get_machine(cc): - self.skipTest("test not available for RISC-V") - - # nextup was introduced in GLIBC 2.24, so is newer than our supported - # glibc (2.18), and available in our release build environment (2.24). - with open(source, 'w', encoding="utf8") as f: - f.write(''' - #define _GNU_SOURCE - #include <math.h> - - double nextup(double x); - - int main() - { - nextup(3.14); - return 0; - } - ''') - - self.assertEqual(call_symbol_check(cc, source, executable, ['-lm']), - (1, executable + ': symbol nextup from unsupported version GLIBC_2.24(3)\n' + - executable + ': failed IMPORTED_SYMBOLS')) - # -lutil is part of the libc6 package so a safe bet that it's installed # it's also out of context enough that it's unlikely to ever become a real dependency source = 'test2.c' diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index f2be3677eb..08a6c72a95 100755 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -238,13 +238,6 @@ case "$HOST" in *mingw*) HOST_LDFLAGS="-Wl,--no-insert-timestamp" ;; esac -# Using --no-tls-get-addr-optimize retains compatibility with glibc 2.18, by -# avoiding a PowerPC64 optimisation available in glibc 2.22 and later. -# https://sourceware.org/binutils/docs-2.35/ld/PowerPC64-ELF64.html -case "$HOST" in - *powerpc64*) HOST_LDFLAGS="${HOST_LDFLAGS} -Wl,--no-tls-get-addr-optimize" ;; -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 8e5c89cc5e..379ad898c4 100644 --- a/contrib/guix/manifest.scm +++ b/contrib/guix/manifest.scm @@ -147,7 +147,7 @@ chain for " target " development.")) #:key (base-gcc-for-libc base-gcc) (base-kernel-headers base-linux-kernel-headers) - (base-libc (make-glibc-with-bind-now (make-glibc-without-werror glibc-2.24))) + (base-libc (hardened-glibc (make-glibc-without-werror glibc-2.27))) (base-gcc (make-gcc-rpath-link (hardened-gcc base-gcc)))) "Convenience wrapper around MAKE-CROSS-TOOLCHAIN with default values desirable for building Bitcoin Core release binaries." @@ -537,33 +537,14 @@ inspecting signatures in Mach-O binaries.") (define (make-glibc-without-werror glibc) (package-with-extra-configure-variable glibc "enable_werror" "no")) -(define (make-glibc-with-stack-protector glibc) - (package-with-extra-configure-variable glibc "--enable-stack-protector" "all")) - -(define (make-glibc-with-bind-now glibc) - (package-with-extra-configure-variable glibc "--enable-bind-now" "yes")) - -(define-public glibc-2.24 - (package - (inherit glibc-2.31) - (version "2.24") - (source (origin - (method git-fetch) - (uri (git-reference - (url "https://sourceware.org/git/glibc.git") - (commit "0d7f1ed30969886c8dde62fbf7d2c79967d4bace"))) - (file-name (git-file-name "glibc" "0d7f1ed30969886c8dde62fbf7d2c79967d4bace")) - (sha256 - (base32 - "0g5hryia5v1k0qx97qffgwzrz4lr4jw3s5kj04yllhswsxyjbic3")) - (patches (search-our-patches "glibc-ldd-x86_64.patch" - "glibc-versioned-locpath.patch" - "glibc-2.24-elfm-loadaddr-dynamic-rewrite.patch" - "glibc-2.24-no-build-time-cxx-header-run.patch" - "glibc-2.24-fcommon.patch" - "glibc-2.24-guix-prefix.patch")))))) +;; https://www.gnu.org/software/libc/manual/html_node/Configuring-and-compiling.html +(define (hardened-glibc glibc) + (package-with-extra-configure-variable ( + package-with-extra-configure-variable glibc + "--enable-stack-protector" "all") + "--enable-bind-now" "yes")) -(define-public glibc-2.27/bitcoin-patched +(define-public glibc-2.27 (package (inherit glibc-2.31) (version "2.27") @@ -571,14 +552,15 @@ inspecting signatures in Mach-O binaries.") (method git-fetch) (uri (git-reference (url "https://sourceware.org/git/glibc.git") - (commit "23158b08a0908f381459f273a984c6fd328363cb"))) - (file-name (git-file-name "glibc" "23158b08a0908f381459f273a984c6fd328363cb")) + (commit "73886db6218e613bd6d4edf529f11e008a6c2fa6"))) + (file-name (git-file-name "glibc" "73886db6218e613bd6d4edf529f11e008a6c2fa6")) (sha256 (base32 - "1b2n1gxv9f4fd5yy68qjbnarhf8mf4vmlxk10i3328c1w5pmp0ca")) + "0azpb9cvnbv25zg8019rqz48h8i2257ngyjg566dlnp74ivrs9vq")) (patches (search-our-patches "glibc-ldd-x86_64.patch" + "glibc-versioned-locpath.patch" "glibc-2.27-riscv64-Use-__has_include-to-include-asm-syscalls.h.patch" - "glibc-2.27-dont-redefine-nss-database.patch" + "glibc-2.27-fcommon.patch" "glibc-2.27-guix-prefix.patch")))))) (packages->manifest @@ -627,12 +609,7 @@ inspecting signatures in Mach-O binaries.") (make-nsis-for-gcc-10 nsis-x86_64) osslsigncode)) ((string-contains target "-linux-") - (list (cond ((string-contains target "riscv64-") - (make-bitcoin-cross-toolchain target - #:base-libc (make-glibc-with-stack-protector - (make-glibc-with-bind-now (make-glibc-without-werror glibc-2.27/bitcoin-patched))))) - (else - (make-bitcoin-cross-toolchain target))))) + (list (make-bitcoin-cross-toolchain target))) ((string-contains target "darwin") (list clang-toolchain-10 binutils cmake xorriso python-signapple)) (else '()))))) diff --git a/contrib/guix/patches/glibc-2.24-elfm-loadaddr-dynamic-rewrite.patch b/contrib/guix/patches/glibc-2.24-elfm-loadaddr-dynamic-rewrite.patch deleted file mode 100644 index 5c4d0c6ebe..0000000000 --- a/contrib/guix/patches/glibc-2.24-elfm-loadaddr-dynamic-rewrite.patch +++ /dev/null @@ -1,62 +0,0 @@ -https://sourceware.org/git/?p=glibc.git;a=commit;h=a68ba2f3cd3cbe32c1f31e13c20ed13487727b32 - -commit 6b02af31e9a721bb15a11380cd22d53b621711f8 -Author: Szabolcs Nagy <szabolcs.nagy@arm.com> -Date: Wed Oct 18 17:26:23 2017 +0100 - - [AARCH64] Rewrite elf_machine_load_address using _DYNAMIC symbol - - This patch rewrites aarch64 elf_machine_load_address to use special _DYNAMIC - symbol instead of _dl_start. - - The static address of _DYNAMIC symbol is stored in the first GOT entry. - Here is the change which makes this solution work (part of binutils 2.24): - https://sourceware.org/ml/binutils/2013-06/msg00248.html - - i386, x86_64 targets use the same method to do this as well. - - The original implementation relies on a trick that R_AARCH64_ABS32 relocation - being resolved at link time and the static address fits in the 32bits. - However, in LP64, normally, the address is defined to be 64 bit. - - Here is the C version one which should be portable in all cases. - - * sysdeps/aarch64/dl-machine.h (elf_machine_load_address): Use - _DYNAMIC symbol to calculate load address. - -diff --git a/sysdeps/aarch64/dl-machine.h b/sysdeps/aarch64/dl-machine.h -index e86d8b5b63..5a5b8a5de5 100644 ---- a/sysdeps/aarch64/dl-machine.h -+++ b/sysdeps/aarch64/dl-machine.h -@@ -49,26 +49,11 @@ elf_machine_load_address (void) - /* To figure out the load address we use the definition that for any symbol: - dynamic_addr(symbol) = static_addr(symbol) + load_addr - -- The choice of symbol is arbitrary. The static address we obtain -- by constructing a non GOT reference to the symbol, the dynamic -- address of the symbol we compute using adrp/add to compute the -- symbol's address relative to the PC. -- This depends on 32bit relocations being resolved at link time -- and that the static address fits in the 32bits. */ -- -- ElfW(Addr) static_addr; -- ElfW(Addr) dynamic_addr; -- -- asm (" \n" --" adrp %1, _dl_start; \n" --" add %1, %1, #:lo12:_dl_start \n" --" ldr %w0, 1f \n" --" b 2f \n" --"1: \n" --" .word _dl_start \n" --"2: \n" -- : "=r" (static_addr), "=r" (dynamic_addr)); -- return dynamic_addr - static_addr; -+ _DYNAMIC sysmbol is used here as its link-time address stored in -+ the special unrelocated first GOT entry. */ -+ -+ extern ElfW(Dyn) _DYNAMIC[] attribute_hidden; -+ return (ElfW(Addr)) &_DYNAMIC - elf_machine_dynamic (); - } - - /* Set up the loaded object described by L so its unrelocated PLT diff --git a/contrib/guix/patches/glibc-2.24-guix-prefix.patch b/contrib/guix/patches/glibc-2.24-guix-prefix.patch deleted file mode 100644 index 875e8cd611..0000000000 --- a/contrib/guix/patches/glibc-2.24-guix-prefix.patch +++ /dev/null @@ -1,25 +0,0 @@ -Without ffile-prefix-map, the debug symbols will contain paths for the -guix store which will include the hashes of each package. However, the -hash for the same package will differ when on different architectures. -In order to be reproducible regardless of the architecture used to build -the package, map all guix store prefixes to something fixed, e.g. /usr. - -We might be able to drop this in favour of using --with-nonshared-cflags -when we being using newer versions of glibc. - ---- a/Makeconfig -+++ b/Makeconfig -@@ -950,6 +950,10 @@ object-suffixes-for-libc += .oS - # shared objects. We don't want to use CFLAGS-os because users may, for - # example, make that processor-specific. - CFLAGS-.oS = $(CFLAGS-.o) $(PIC-ccflag) -+ -+# Map Guix store paths to /usr -+CFLAGS-.oS += `find /gnu/store -maxdepth 1 -mindepth 1 -type d -exec echo -n " -ffile-prefix-map={}=/usr" \;` -+ - CPPFLAGS-.oS = $(CPPFLAGS-.o) -DPIC -DLIBC_NONSHARED=1 - libtype.oS = lib%_nonshared.a - endif --- -2.35.1 - diff --git a/contrib/guix/patches/glibc-2.24-no-build-time-cxx-header-run.patch b/contrib/guix/patches/glibc-2.24-no-build-time-cxx-header-run.patch deleted file mode 100644 index 11fe7fdc99..0000000000 --- a/contrib/guix/patches/glibc-2.24-no-build-time-cxx-header-run.patch +++ /dev/null @@ -1,100 +0,0 @@ -https://sourceware.org/git/?p=glibc.git;a=commit;h=fc3e1337be1c6935ab58bd13520f97a535cf70cc - -commit dc23a45db566095e83ff0b7a57afc87fb5ca89a1 -Author: Florian Weimer <fweimer@redhat.com> -Date: Wed Sep 21 10:45:32 2016 +0200 - - Avoid running $(CXX) during build to obtain header file paths - - This reduces the build time somewhat and is particularly noticeable - during rebuilds with few code changes. - -diff --git a/Makerules b/Makerules -index 7e4077ee50..c338850de5 100644 ---- a/Makerules -+++ b/Makerules -@@ -121,14 +121,10 @@ ifneq (,$(CXX)) - # will be used instead of /usr/include/stdlib.h and /usr/include/math.h. - before-compile := $(common-objpfx)cstdlib $(common-objpfx)cmath \ - $(before-compile) --cstdlib=$(shell echo "\#include <cstdlib>" | $(CXX) -M -MP -x c++ - \ -- | sed -n "/cstdlib:/{s/:$$//;p}") --$(common-objpfx)cstdlib: $(cstdlib) -+$(common-objpfx)cstdlib: $(c++-cstdlib-header) - $(INSTALL_DATA) $< $@T - $(move-if-change) $@T $@ --cmath=$(shell echo "\#include <cmath>" | $(CXX) -M -MP -x c++ - \ -- | sed -n "/cmath:/{s/:$$//;p}") --$(common-objpfx)cmath: $(cmath) -+$(common-objpfx)cmath: $(c++-cmath-header) - $(INSTALL_DATA) $< $@T - $(move-if-change) $@T $@ - endif -diff --git a/config.make.in b/config.make.in -index 95c6f36876..04a8b3ed7f 100644 ---- a/config.make.in -+++ b/config.make.in -@@ -45,6 +45,8 @@ defines = @DEFINES@ - sysheaders = @sysheaders@ - sysincludes = @SYSINCLUDES@ - c++-sysincludes = @CXX_SYSINCLUDES@ -+c++-cstdlib-header = @CXX_CSTDLIB_HEADER@ -+c++-cmath-header = @CXX_CMATH_HEADER@ - all-warnings = @all_warnings@ - enable-werror = @enable_werror@ - -diff --git a/configure b/configure -index 17625e1041..6ff252744b 100755 ---- a/configure -+++ b/configure -@@ -635,6 +635,8 @@ BISON - INSTALL_INFO - PERL - BASH_SHELL -+CXX_CMATH_HEADER -+CXX_CSTDLIB_HEADER - CXX_SYSINCLUDES - SYSINCLUDES - AUTOCONF -@@ -5054,6 +5056,18 @@ fi - - - -+# Obtain some C++ header file paths. This is used to make a local -+# copy of those headers in Makerules. -+if test -n "$CXX"; then -+ find_cxx_header () { -+ echo "#include <$1>" | $CXX -M -MP -x c++ - | sed -n "/$1:/{s/:\$//;p}" -+ } -+ CXX_CSTDLIB_HEADER="$(find_cxx_header cstdlib)" -+ CXX_CMATH_HEADER="$(find_cxx_header cmath)" -+fi -+ -+ -+ - # Test if LD_LIBRARY_PATH contains the notation for the current directory - # since this would lead to problems installing/building glibc. - # LD_LIBRARY_PATH contains the current directory if one of the following -diff --git a/configure.ac b/configure.ac -index 33bcd62180..9938ab0dc2 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -1039,6 +1039,18 @@ fi - AC_SUBST(SYSINCLUDES) - AC_SUBST(CXX_SYSINCLUDES) - -+# Obtain some C++ header file paths. This is used to make a local -+# copy of those headers in Makerules. -+if test -n "$CXX"; then -+ find_cxx_header () { -+ echo "#include <$1>" | $CXX -M -MP -x c++ - | sed -n "/$1:/{s/:\$//;p}" -+ } -+ CXX_CSTDLIB_HEADER="$(find_cxx_header cstdlib)" -+ CXX_CMATH_HEADER="$(find_cxx_header cmath)" -+fi -+AC_SUBST(CXX_CSTDLIB_HEADER) -+AC_SUBST(CXX_CMATH_HEADER) -+ - # Test if LD_LIBRARY_PATH contains the notation for the current directory - # since this would lead to problems installing/building glibc. - # LD_LIBRARY_PATH contains the current directory if one of the following diff --git a/contrib/guix/patches/glibc-2.27-dont-redefine-nss-database.patch b/contrib/guix/patches/glibc-2.27-dont-redefine-nss-database.patch deleted file mode 100644 index 16a595d613..0000000000 --- a/contrib/guix/patches/glibc-2.27-dont-redefine-nss-database.patch +++ /dev/null @@ -1,87 +0,0 @@ -commit 78a90c2f74a2012dd3eff302189e47ff6779a757 -Author: Andreas Schwab <schwab@linux-m68k.org> -Date: Fri Mar 2 23:07:14 2018 +0100 - - Fix multiple definitions of __nss_*_database (bug 22918) - - (cherry picked from commit eaf6753f8aac33a36deb98c1031d1bad7b593d2d) - -diff --git a/nscd/gai.c b/nscd/gai.c -index d081747797..576fd0045b 100644 ---- a/nscd/gai.c -+++ b/nscd/gai.c -@@ -45,3 +45,6 @@ - #ifdef HAVE_LIBIDN - # include <libidn/idn-stub.c> - #endif -+ -+/* Some variables normally defined in libc. */ -+service_user *__nss_hosts_database attribute_hidden; -diff --git a/nss/nsswitch.c b/nss/nsswitch.c -index d5e655974f..b0f0c11a3e 100644 ---- a/nss/nsswitch.c -+++ b/nss/nsswitch.c -@@ -62,7 +62,7 @@ static service_library *nss_new_service (name_database *database, - - /* Declare external database variables. */ - #define DEFINE_DATABASE(name) \ -- extern service_user *__nss_##name##_database attribute_hidden; \ -+ service_user *__nss_##name##_database attribute_hidden; \ - weak_extern (__nss_##name##_database) - #include "databases.def" - #undef DEFINE_DATABASE -diff --git a/nss/nsswitch.h b/nss/nsswitch.h -index eccb535ef5..63573b9ebc 100644 ---- a/nss/nsswitch.h -+++ b/nss/nsswitch.h -@@ -226,10 +226,10 @@ libc_hidden_proto (__nss_hostname_digits_dots) - #define MAX_NR_ADDRS 48 - - /* Prototypes for __nss_*_lookup2 functions. */ --#define DEFINE_DATABASE(arg) \ -- service_user *__nss_##arg##_database attribute_hidden; \ -- int __nss_##arg##_lookup2 (service_user **, const char *, \ -- const char *, void **); \ -+#define DEFINE_DATABASE(arg) \ -+ extern service_user *__nss_##arg##_database attribute_hidden; \ -+ int __nss_##arg##_lookup2 (service_user **, const char *, \ -+ const char *, void **); \ - libc_hidden_proto (__nss_##arg##_lookup2) - #include "databases.def" - #undef DEFINE_DATABASE -diff --git a/posix/tst-rfc3484-2.c b/posix/tst-rfc3484-2.c -index f509534ca9..8c64ac59ff 100644 ---- a/posix/tst-rfc3484-2.c -+++ b/posix/tst-rfc3484-2.c -@@ -58,6 +58,7 @@ _res_hconf_init (void) - #undef USE_NSCD - #include "../sysdeps/posix/getaddrinfo.c" - -+service_user *__nss_hosts_database attribute_hidden; - - /* This is the beginning of the real test code. The above defines - (among other things) the function rfc3484_sort. */ -diff --git a/posix/tst-rfc3484-3.c b/posix/tst-rfc3484-3.c -index ae44087a10..1c61aaf844 100644 ---- a/posix/tst-rfc3484-3.c -+++ b/posix/tst-rfc3484-3.c -@@ -58,6 +58,7 @@ _res_hconf_init (void) - #undef USE_NSCD - #include "../sysdeps/posix/getaddrinfo.c" - -+service_user *__nss_hosts_database attribute_hidden; - - /* This is the beginning of the real test code. The above defines - (among other things) the function rfc3484_sort. */ -diff --git a/posix/tst-rfc3484.c b/posix/tst-rfc3484.c -index 7f191abbbc..8f45848e44 100644 ---- a/posix/tst-rfc3484.c -+++ b/posix/tst-rfc3484.c -@@ -58,6 +58,7 @@ _res_hconf_init (void) - #undef USE_NSCD - #include "../sysdeps/posix/getaddrinfo.c" - -+service_user *__nss_hosts_database attribute_hidden; - - /* This is the beginning of the real test code. The above defines - (among other things) the function rfc3484_sort. */ diff --git a/contrib/guix/patches/glibc-2.24-fcommon.patch b/contrib/guix/patches/glibc-2.27-fcommon.patch index 2bc32ede90..f3baacab98 100644 --- a/contrib/guix/patches/glibc-2.24-fcommon.patch +++ b/contrib/guix/patches/glibc-2.27-fcommon.patch @@ -18,15 +18,15 @@ Date: Fri May 6 11:03:04 2022 +0100 https://sourceware.org/git/?p=glibc.git;a=commit;h=7650321ce037302bfc2f026aa19e0213b8d02fe6 diff --git a/Makeconfig b/Makeconfig -index ee379f5852..63c4a2f234 100644 +index 86a71e5802..aa2166be60 100644 --- a/Makeconfig +++ b/Makeconfig -@@ -824,7 +824,7 @@ ifeq "$(strip $(+cflags))" "" - +cflags := $(default_cflags) +@@ -896,7 +896,7 @@ ifeq "$(strip $(+cflags))" "" endif # $(+cflags) == "" --+cflags += $(cflags-cpu) $(+gccwarn) $(+merge-constants) $(+math-flags) -++cflags += $(cflags-cpu) $(+gccwarn) $(+merge-constants) $(+math-flags) -fcommon + +cflags += $(cflags-cpu) $(+gccwarn) $(+merge-constants) $(+math-flags) \ +- $(+stack-protector) ++ $(+stack-protector) -fcommon +gcc-nowarn := -w # Don't duplicate options if we inherited variables from the parent. diff --git a/contrib/guix/patches/glibc-2.27-guix-prefix.patch b/contrib/guix/patches/glibc-2.27-guix-prefix.patch index d777af74f0..6648bc6c05 100644 --- a/contrib/guix/patches/glibc-2.27-guix-prefix.patch +++ b/contrib/guix/patches/glibc-2.27-guix-prefix.patch @@ -20,6 +20,3 @@ when we being using newer versions of glibc. libtype.o := lib%.a object-suffixes += .o ifeq (yes,$(build-shared)) --- -2.35.1 - diff --git a/contrib/guix/patches/glibc-ldd-x86_64.patch b/contrib/guix/patches/glibc-ldd-x86_64.patch index b1b6d5a548..a23b095caa 100644 --- a/contrib/guix/patches/glibc-ldd-x86_64.patch +++ b/contrib/guix/patches/glibc-ldd-x86_64.patch @@ -1,8 +1,8 @@ By default, 'RTDLLIST' in 'ldd' refers to 'lib64/ld-linux-x86-64.so', whereas it's in 'lib/' for us. This patch fixes that. ---- glibc-2.17/sysdeps/unix/sysv/linux/x86_64/ldd-rewrite.sed 2012-12-25 04:02:13.000000000 +0100 -+++ glibc-2.17/sysdeps/unix/sysv/linux/x86_64/ldd-rewrite.sed 2013-09-15 23:08:03.000000000 +0200 +--- a/sysdeps/unix/sysv/linux/x86_64/ldd-rewrite.sed ++++ b/sysdeps/unix/sysv/linux/x86_64/ldd-rewrite.sed @@ -1,3 +1,3 @@ /LD_TRACE_LOADED_OBJECTS=1/a\ add_env="$add_env LD_LIBRARY_VERSION=\\$verify_out" diff --git a/contrib/signet/getcoins.py b/contrib/signet/getcoins.py index d4e436626f..19751ae269 100755 --- a/contrib/signet/getcoins.py +++ b/contrib/signet/getcoins.py @@ -142,7 +142,7 @@ if args.captcha != '': # Retrieve a captcha try: res = session.post(args.faucet, data=data) -except: +except Exception: raise SystemExit(f"Unexpected error when contacting faucet: {sys.exc_info()[0]}") # Display the output as per the returned status code diff --git a/contrib/verify-commits/README.md b/contrib/verify-commits/README.md index b8b15280ba..020890c366 100644 --- a/contrib/verify-commits/README.md +++ b/contrib/verify-commits/README.md @@ -27,6 +27,10 @@ Note that the above isn't a good UI/UX yet, and needs significant improvements to make it more convenient and reduce the chance of errors; pull-reqs improving this process would be much appreciated. +Unless `--clean-merge 0` is specified, `verify-commits.py` will attempt to verify that +each merge commit applies cleanly (with some exceptions). This requires using at least +git v2.38.0. + Configuration files ------------------- diff --git a/contrib/verify-commits/allow-incorrect-sha512-commits b/contrib/verify-commits/allow-incorrect-sha512-commits index c572806f26..e69de29bb2 100644 --- a/contrib/verify-commits/allow-incorrect-sha512-commits +++ b/contrib/verify-commits/allow-incorrect-sha512-commits @@ -1,2 +0,0 @@ -f8feaa4636260b599294c7285bcf1c8b7737f74e -8040ae6fc576e9504186f2ae3ff2c8125de1095c diff --git a/contrib/verify-commits/allow-revsig-commits b/contrib/verify-commits/allow-revsig-commits index 0c43d9cce5..e69de29bb2 100644 --- a/contrib/verify-commits/allow-revsig-commits +++ b/contrib/verify-commits/allow-revsig-commits @@ -1,820 +0,0 @@ -a06ede9a138d0fb86b0de17c42b936d9fe6e2158 -923dc447eaa8e017985b2afbbb12dd1283fbea0e -71148b8947fe8b4d756822420a7f31c380159425 -6696b4635ceb9b47aaa63244bff9032fa7b08354 -812714fd80e96e28cd288c553c83838cecbfc2d9 -8a445c5651edb9a1f51497055b7ddf4402be9188 -e126d0c12ca66278d9e7b12187c5ff4fc02a7e6c -3908fc4728059719bed0e1c7b1c8b388c2d4a8da -8b66bf74e2a349e71eaa183af81fa63eaee76ad2 -05950427d310654774031764a7141a1a4fd9c6e4 -07fd147b9f12e9205afd66a624edce357977d615 -12e31127948fa4bb01c3bddc1b8c85b432f7465b -8c87f175d335e9d9e93f987d871ae9f05f6a10a7 -46b249e578e8a3dfbe85bc7253a12e82ef4b658b -a55716abe5662ec74c2f8af93023f1e7cca901fc -f646275b90b1de93bc62b4c4d045d75ac0b96eee -c252685aa5867631e9a5ef07ccae7c7c25cae8ff -a7d55c93385359952d85decd5037843ac70ba3d4 -7dac1e5e9e887f5f6ff146e812a05bd3bf281eae -2a524b8e8fe69ef487fd8ea1b4f7a03f473ed201 -ce5c1f4acae43477989cdf9a82ed33703919cda2 -2db4cbcc437f51f5dac82cc4de46f383b92e6f11 -7aa700424cbda387536373d8dfec88aee43f950e -b99a093afed880f23fb279c443cc6ae5e379cc43 -b83264d9c7a8ddb79f64bd9540caddc8632ef31f -57e337d40e94ba33d8cd265c134d6ef857b32b59 -a1dcf2e1087beaf3981739fd2bb74f35ecad630a -d38b0d7a6b6056cba26999b702815775e2437d87 -815640ec6af9a38d6a2da4a4400056e2f4105080 -09c4fd157c5b88df2d97fad4826c79b094db90c9 -2efcfa5acfacb958973d9e8125e1d81f102e2dfd -dc6dee41f7cf2ba93fcd0fea7c157e4b2775d439 -ad826b3df9f763b49f1e3e3d50c4efdd438c7547 -c1a52276848d8caa9a9789dff176408c1aa6b1ed -3bf06e9bac57b5b5a746677b75e297a7b154bdbd -72ae6f8cf0224370e8121d6769b21e612ca15d6f -a143b88dbd4971ecfdd1d39a494489c8f2db0344 -76fec09d878d6dbf214bdb6228d480bd9195db4c -93566e0c37c5ae104095474fea89f00dcb40f551 -407d9232ef5cb1ebf6cff21f3d13e07ea4158eeb -9346f8429957e356d21c665bab59fe45bcf1f74e -6eeac6e30d65f9a972067c1ea8c49978c8e631ac -dc6b9406bdfab2af8c86cb080cb3e6cf8f2385d8 -9f554e03ebe5701c1b75ff03b3d6152095c0cad3 -05009935f9ac070197113954d680bc2c9150b9b3 -508404de98a8a5435f52916cef8f328e82651961 -ed0cc50afed146c27f6d8129c683c225fb940093 -6429cfa8a70308241c576aeb92ffe3db5203b2ef -6898213409811b140843c3d89af43328c3b22fad -5b2ea29cf4fd298346437bb16a54407f8c1f9dca -e2a1a1ee895149c544d4ae295466611f0cec3094 -e82fb872ff5cc8fd22d43327c1ee3e755f61c562 -19b0f33de0efd9da788e8e4f3fdc2a9e159abdb1 -89de1538ce1f8c00f80e8d11f43e1b77e24d7dea -de07fdcf77e97b8613091285e4d0a734f5de7492 -01680195f8aa586c55c44767397380def3a23b54 -05e1c85fb687c82ae477c72d4a7e2d6b0c692167 -c072b8fd95cd4fa84f08189a0cd8b173ea2dbb8e -9a0ed08b40b15ae2b791aa8549b53e69934b4ea7 -53f8f226bd1d627c4a6dec5862a1d4ea5a933e45 -9d0f43b7ca7241d8a018fd35dd3bc01555235ec6 -f12d2b5a8ac397e4bcaefcc19898f8ff5705dea5 -8250de13587ed05ca45df3e12c5dc9bcb1500e2c -d727f77e390426e9e463336bda08d50c451c7086 -484312bda2d43e3ea60047be076332299463adf8 -c7e05b35ab0a791c7a8e2d863e716fdec6f3f671 -b9c1cd81848da9de1baf9c2f29c19c50e549de13 -8ea7d31e384975019733b5778feabbd9955c79d8 -f798b891bcecea9548eedacae70eeb9906c1ddbf -ebefe7a00b46579cdd1e033a8c7fd8ce9aa578e4 -ad087638ee4864d6244ec9381ff764bfa6ee5086 -66db2d62d59817320c9182fc18e75a93b76828ea -7ce9ac5c83b1844a518ef2e12e87aae3cacdfe58 -4286f43025149cf44207c3ad98e4a1f068520ada -cd0c5135ab2291aaa5410ac919bad3fc87249a4a -66ed450d771a8fc01c159a8402648ebd1c35eb4c -a82f03393a32842d49236e8666ee57805ca701f8 -f972b04d63eb8af79ff3cec1dc561ed13dfa6053 -ec45cc5e27668171b55271b0c735194c70e7da41 -715e9fd7454f7a48d7adba7d42f662c20a3e3367 -2e0a99037dcc35bc63ba0d54371bc678af737c8e -7fa8d758598407f3bf0beb0118dc122ea5340736 -6a22373771edbc3c7513cacb9355f880c73c2cbf -b89ef131147f71a96152a7b5c4374266cdf539b2 -01d8359983e2f77b5118fede3ffa947072c666c8 -58f0c929a3d70a4bff79cc200f1c186f71ef1675 -950be19727a581970591d8f8138dfe4725750382 -425278d17bd0edf8a3a7cc81e55016f7fd8e7726 -c028c7b7557da2baff7af8840108e8be4db8e0c6 -47a7cfb0aa2498f6801026d258a59f9de48f60b0 -f6b7df3155ddb4cedfbcf5d3eb3383d4614b3a85 -d72098038f3b55a714ed8adb34fab547b15eb0d5 -c49c825bd9f4764536b45df5a684d97173673fc7 -33799afe83eec4200ff140e9bf5eae83701a4d7f -5c3f8ddcaa1164079105c452429fccf8127b01b6 -1f01443567b03ac75a91c810f1733f5c21b5699d -b3e42b6d02e8d19658a9135e427ebceab5367779 -69b3a6dd9d9a0adf5506c8b9fde42187356bd4a8 -bafd075c5e6a1088ef0f1aa0b0b224e026a3d3e0 -7daa3adb242d9c8728fdb15c6af6596aaad5502f -514993554c370f4cf30a109ac28d5d64893dbf0a -c8d2473e6cb042e7275a10c49d3f6a4a91bf0166 -386f4385ab04b0b2c3d47bddc0dc0f2de7354964 -9f33dba05c01ecc5c56eb1284ab7d64d42f55171 -7466a26cab5d66665991433947964a638f5b957e -b43aba89e356ff95b706e80d4802f60fc46a569a -02b7e8319aef2a870264ad4fa2e3bb18664dcc36 -f686002a8eba820a40ac2f34a6e8f57b2b5cc54c -2b1c50b9352ab1dc40b0f877db23c1fa4048fae3 -2405ce1df043f778b8efb9205009500cbc17313a -4ad3b3c72c73d61e0a0cab541dca20acf651320d -4ba3d4f4393d81148422d24d222fe7ed00130194 -8ee5c7b747171e335793c74cd9d2f7491da58164 -872c921c0a208b04bd0713758e52fcab5b7c1684 -00d1680498c5550e7db1f359202d3433a092fafd -585db41e9ab7a6fb262c8bad7f427cdbdc497188 -18462960c0f13bd07d8f52b61e7d7bc17e991eea -0630974647dacaf25e7fcb7f9cbb785bb078ede6 -0f58d7f3d62f012f2584f5e781fc73de4763dd9e -3d16f581538b0974853e820508e8b3093269d2fd -66e91420ab233cf1dac64504e0dc129019bf8c0d -d8d9162f5bad39b2720dd2b2da237c6159e4755f -29fad97c320c892ab6a480c81e2078ec22ab354b -791c3ea61b4e49fd46a1a71b84ca99ddf69d2ff7 -a312e201ba56742499a5480b5f2115f01505c217 -ce56fdd2e8cdf94fd0ab76d71adbfa755e23ce7d -480f42630cbd598c04fa59ee0e406f56904ecffb -6012f1caf744ac9b53383d7d10a8f1b70ca2c0e1 -ded6a2afa549f693dcabb430ce0862f8631360c8 -07090c5339436f856e79a8036d1c85deeb453803 -0e265916d1c6a63e4a3821dab9db597b5ec64b46 -e4ffcacc2187d3419c8ea12b82fb06d82d8751d2 -e117cfe45eee9169409e74a44ef4a866be25bc35 -dcfe218626b05204e9fbc95ba5d95ca0eb72ec9b -23481fa50301201ef5a60675ef899aa6ce94ca03 -27c59dc502f29cf1d76290556c21e366145e3b2e -4a62ddd01873d18dbca96c81d756be1020249b45 -a233fb4f1d037e68ff70eef3a9f5b7bf1d631918 -b2089c51cc4af2f7e1c0ec75be9449ee222b1d69 -c997f8808256521397f1c003bb1e9896fee6eaa0 -5dc00f68c49c46a380a98d06233f90528b8e2557 -fe53d5f3636aed064823bc220d828c7ff08d1d52 -935eb8de039dec65669a96a1c3b86f4b03a1b86c -0277173b1defb63216d40a8d8805ae6d5d563c26 -2a30e67d20f76bbcd9a7d445f616f005316e0a1a -d32528e733f2711b34dbc41fbb2bb0f153bf7e9a -4cad91663df381d0dff8526f3b4aa74569dfb626 -1b06ed136f17b526360617a70026aed5ded5746c -895fbd768f0c89cea3f78acac58b233d4e3a145e -f0295becbf3ef1fb78095306408789253fe0c114 -8d573198638e52e2dbd9abc609861430f9d2bcc3 -9d9c4185fadaf243bb97c226e2fef16b65299699 -eebe4580bc8d6484d79ecb24dd87412221cf2ea7 -9cf6393a4f82b9c81d3b4b468a17a89db10531a2 -598a9c4e4dcd03c6d80fba005de729a6a3aeba7e -6970b30c6f1d2be7947295fe18f2390649b17a4b -f359afcc410432ed5d30001acda0c66741ee8935 -126000ba9e7ff16271be2f4eef3df99ade8d624f -b5e4b9b5100ec15217d43edb5f4149439f4b20a5 -b987ca4ee495a7fff82f0ac14ef0753bfb7586e2 -b03013396cb2f4bf25746388b3982a2c3616e16b -9a97f39afaa890caa7987c6bc001b9a66e3e74e8 -cad504bf4c302f7a72e0a0e191f3fdbafda7340f -45cf8a03cb57b8639a8d47323bde46ba22d9eeaf -b7450cdbd89a1c862f4d4d8bf093f8a0b5448f9c -0910cbe4ef31eb95fd76c7c2f820419fe64a3150 -92a810d04b906722c9efe60e3997243c71ff3d4c -45173fa6fca9537abb0a0554f731d14b9f89c456 -fd4ca17360e6fc0c9bb76bf6b5b07c9102c12728 -ddff3447f29b62d79a33f728791f42fa9436216e -36a5a4404836da323c755523fbd27563a8e84f94 -c991b304dee368f506cfee27ddaa333f1f82c518 -d38d1a3e75aa97ffa8755ddd431754a6d0942964 -a332a7d5a15214015f9553fdb2bcf80a1a4b8dc0 -604e08c83cf58ca7e7cda2ab284c1ace7bb12977 -18a1bbad98bd4321f15e7921d9aec91661499d90 -8049241e226c16bd07b029c0cb4b62ac40f0c923 -797441ee995aac59f55d59a93ecb55e8ecbe7dbc -62fdf9b07087b80d2142799bdd2324f61483359d -f60b4ad57912b78a96af08046a503f7905610a8c -13e31dd6548d64a5992f439e74bb424bf88aca04 -fbce66a982679b5409a295be5c99a2eef429cabf -9f2c2dba21855b8cb9b193b1819be73fa4a23a99 -a89221873a3ee2451c73b41bbe2d99d36f439d31 -3d6ad407770e13958e157bf026cae0bfb9254899 -901ba3e3819405306414628306746552b0aa1d28 -7a43fbb959c38e025e558e472ad57de357539894 -0d89fa0877930c6c8a539a656c1009ad8ab6755b -54aedc013744c86b11157423fa3cffc9a51eef02 -f0c1f8abb0182da557d07372b938f3a0a4bb906f -4ed818060ecf4a38a02c8cb48f6cbc78d2ee7708 -3bdf242fc68a8d767932c6214455d4d413effbc9 -5e468994fbb349e8eefc996954a31a67a34aaa15 -41aa9c4a801a01eca1fad22a7095372d23dace60 -2adbddb03840ad71e843c6c4a207a13e871cd1d4 -13e352dc53dec0127c5f94a60055d0ca829420dc -95e14dc81dd30ee0d396ad08dca9a6980d16eee1 -61fb80660f73e5aa5b69302ecc7ac33da206ba5a -05a761932edd05cf94ffe938908baf058f38632a -ee92243e66f2df03b3a759a8ffb75dc06f0cea0d -22cdf93c062eeaa0f8f9d6220f01b67240073dfb -76b33491596736ca804e3a29bd8398d7a1516ab7 -6e4e98ee8ce2da3cca2e2fd210e9e8dbc9b1c936 -c838283ecdfb9490425bb071b7c22e542de46c7c -5e3f5e4f25b65b583d3bfefac9e1148035781089 -f7388e93d3dd91a90239aedac4ec58404f103a2e -0a2f46b0158b6fc7244a585913b0925c0acf707f -dd561667cb7ccbbfed3134b05a565971ef6f5873 -6f01dcf63873a5e42798635ab4026c9a5f9fa213 -70fec9e36bcd1a3d93df019be084aaf89cecd7d7 -f9b74ef3fc74fd7d2aa94560820341f03cda8e12 -998c3046fab2b52bc9f141cfb588a18c05506a86 -89cc4f905e30b913ca20e4192d538cc5cbe2c38d -87d90efd69b64f769116956a5db89e536e9e3714 -5aeaa9ccd1568a77e075dbe2bd2435bd60c87c91 -bfb270acfa30713dc8c968bb9ee40cf5a2360359 -1b8c88451b0554502435d3883c528ad0aad1b09b -57ee73990f1ce29916adfd99f93eae1ccea1a43b -808c84f89d0edcef9ddaab0b849a382719f6ec9e -14b860bf64020451ced823b859da8cb912278ab9 -c63364610f4a041df1c1bd81d01b1f6856160749 -92eadc395071876d77f3babddc056b4325bdbabc -e93fff1463ae906fc986bf98c3b118c82f171546 -9ccafb1d7bdd172a9b963444072a844da379c4f7 -b4a509a3f817121c3df98ddfd96b2769e18a3e5a -dbc4ae03963014ab4b7957d62ba59dbd8f938c33 -8ddf60db7ad636b6a31b590251c671ded635fa1d -f199b8a33d9443a258a1f49a1a29674cd9ee9a20 -e542728cde676f218c552d841d0af29b92f9800b -763231051596b8e3455b839911ad6a3a1f1c3c74 -ff4cd6075b12fb32b9a906deea3ed033e3f9560a -9c3c9cdae3e20b5bdea91a0631edac5116bbc89f -93d20a734d2ee873832bed8ca5c05cf8e539c53c -ef8340d25f7c5dd5682bdecea97ce84cfce1493c -69c7ecef405d168f658a9cc7996da84c17f61e66 -4ce2f3d0d33346e9f0e96851689ee6550b2a72e3 -44e1fd926cfb0df0fbd8c41de8cd65ed8d5d6e18 -d6d2c8503c4039b682196d83a67dc28359c10c5c -ae233c4ec3d14a97c6195059f52873cdba2b4755 -0f399a9ff227896265cafab9b2e9fab6cdb9b5b9 -f4ed44ab4a8f9a87ba678d5fd1449fbf636103dc -7fcd61b2613c211bb042a82a889655178be6a212 -42973f834445d7735738bdba8847812ba3c34d95 -8df48b36ed3201d938b9974ecbee455d7dc2fb84 -96ac26e56627f0c24213fcd3a1cce9fc95f1f661 -cce94c518a46b7b0006f984bbe4d69e8749182d2 -801dd40666d1e6009920ad3ff755c7bb993b2a62 -ce829855cfca103dde55661fa1524e66b139d063 -b148803b181e30213e8a7f3bd89c8239e9dcb866 -c377feaad87f8109f85da6caf62602b30c20effc -b37cab65c63e051ebc5b491da9bd687581df94df -16e41844e7d6c5876d2caaeef6010656950c6ec5 -ee50c9e48786dea0d9df2e45805c25565c100fe3 -11dacc6154c42bc6fe3ba94c1823f8a46e4fe81a -791a0e6ddade27d1b69f4861a6640de60b9553cf -638e6c59da4fad987c437592174b188510193b2e -52f8877525d5238f3440e73710507be889d14127 -2a56baf395bf11835d784c4f8634f4525deed6a1 -bc561b4b7d6a3f71649d37d5eb9047c29efa2b13 -31809d6f8514c4a8d5677e947e3f1ebb0db210b9 -a31e9ad4f027955d43c04a05517244647e250161 -777519bd96f68c18150a0f5942f8f97a91937f5e -4eb1f39d421024d9666cec61deaf96715ffae4c6 -50fae68d416b4b8ec4ca192923dfd5ae9ea42773 -ce665863b137ac4a7470cf006a92aa7694faca71 -81f8c0378b2ab5ea0d7b65635cb529bd3c69127c -108222b9c323a05cc9339368f10ddd0859f62b43 -28f788e47e58f2b462351d6989348a4e1a241b2b -d81dccf191a48a6b59c3747d7b4ccbe3535dde40 -a90e6d2bffc422ddcdb771c53aac0bceb970a2c4 -91e49c51f1aecc9e1d75457f4920d52a4b0a133c -60dd9cc470584960431de425e2a9ffbed0e8034a -ede386c2193fc31351e193b3a8cf30030d6be62c -a084767b40c0d3ba8fa8f8d60f1e8d99a9dc3457 -3f726c99f819f97f2ab21b94d34c6b3129cd883a -77fc469fc78cdd87c29f398d46ac58dbb9ef62c0 -4ae6d0fbef60ccbecf8f23bb482e201b3678f7a3 -8858b6ddd3bce9daa08da6e05de3ca863a399c15 -22e301a3d56dc9e6878380ee92c7d19ca43119d2 -c484ec6c9b85ca4e331e395c564ae232fd0681dd -a46a671e253528e450bd57645c400bf761da07ab -655970d9c60ae6850daf452457e14e21047c0e1b -b6a48914c50631914192aa11b19205436a9c664d -7db65c363a0cc6ca7cdb04de9a973ab70013baad -6366941275344dac7e2130b0c972e90117d37ed0 -4fb2586661471a1572c2df2a5a091011d45eb7c4 -d7be7b39fa1021ec4518186afe145ee948e12a94 -85aec87b11ec41295558175c63f1f5a849460fdf -aeb31756276034dd506fdf97c8aaade0e7e584f5 -ac016e17d20253129a0287cee7e1d06b7ef15966 -bf74d377fb8e20140da6eac1407414928384bcea -2c811e08db651a4aed6ea0f7c1972d60de6de8ab -e5d26e47c7a482c072a7fe47bb84c56854734184 -96a63a3e0cefe920819bd42add0041837b1214a1 -e526ca6284b9e13be1b912b80dd73a34e739b539 -ecd21357f16106e541e9c2854ead2a906659b938 -4b5a7ce0c301ad971f383eb60f61bf9b4026efda -929fd7276c0f0c30b9416f61a6f5f35d763d81e4 -fa8a0639f7b0ce04030b72b4d5be4f0aa36fc5cb -f1f1605c22a6283bbfd757055fcf2b584a857709 -0c173a15ca1bf20999f74987988985508c9de463 -df0793f324e33066cc746c0cb1d053d35733d626 -2b0179d8a9b75397937126b36114df0dddeab40c -bf0a08be281dc42241e7f264c2a20515eb4781bb -3895e25a77363ae8b49358fb793f50fa8b271e2d -1fc783fc08bc078239537535f174ab8a489772c0 -1d4805ce04645f3203b0cfd3d66ea710e7433eb4 -d3b58704d1d325875fc605580c1c02b825c1bbcc -ed88e3194c4bc43aeafef929da7b419d03dea1ad -dd07f47b79628668e29cc0143b21e790100ee445 -65cc7aacfbfc7b747926375280a1d839e88d576b -080ec5209172ac9605f1434559dbb3c1e012b10a -416af3edf5b5ab265acf95568f2bc9eabd3d96de -e0a7801223fd573863939e76cb633f1dcc2d22c4 -4bc853b50fd9127687eb9e4f3b679dd261a4fa96 -c68a9a69278aa194fed96bd9733d32af3690a11e -c38f540298f0e188df5ed68fd56c623b9ac8331b -643fa0b22d70e459d7f7ec3d728ae4811dc5158f -e053e05c130549f43953f1d70e724dc9ce3e1b85 -75e898c094eea533d1dfaf141c6afccc3072c49f -2805d606bc46bf5589093a1b92d3542c13ce50c2 -32751807c9c06011eb689cba56b401a6302699c0 -30853e16d332816752dafcfca92147c7ffef5b54 -bea5b00cfe95cd37832305c0f93c339a22a7d79d -c871f323b418fac27bf834843ca26985010df53f -329fc1dce7a1c372c8b10c2f2f8732b2c60daff0 -1aefc94dd78d6e0c9209cb09fc16f53dedf42108 -8e5725666b519b61fcdc3141da5c6a57c1959909 -a4ca0b042365061020627a8c045cddacea3312ec -8bd16ee12fc8ef6723e0572c29b979c15b92b4f4 -87abe20fc118721cc5efdbd94a8462468cd1da2b -4b766fcdd4ca16399075d1e081a321b3b05ce516 -f6241b3e420e19f3f0507cbbc872fe9218916a02 -7ee523604851af62c0a47c07ee023a8710ef32f7 -776ba233e939fe41a74c6b2632b93a0679a32c71 -6a796b2b53fe542e0f340f250f4f20d69efed8d0 -23d78c4dd01bc74ba35db3e3df95280f6f1b2e22 -f4b15e2de97c4f8cdbb40bef4c9d0ab2807974d9 -fff72de5bf8ac7b70208e655f237b80e70e18851 -170bc2c381f86a523de2fc8b71d62ade66303c0d -314ebdfcb38d4b4c977579f787d5e1a20d068c94 -e9274839bf316b1972d80d28e45759f898edbf86 -75171f099e82e3527d7c3469b15891bd92227ec2 -3c5e6c94caf40395e031fbde44a0cca46fdd76ec -dc8fc0c73bebbc1c48ac5540026030c9cc00ec23 -492d22f92919d8d9d59568318c26c1e2ac4890cc -80c3a734298e824f9321c4efdd446086a3baad89 -47535d7c3ec79c5978cdcc03a5351ddbbb22538d -1b25b6df0f08f7474228c5b6ed13b58682e1e440 -c530c15180631cea95e9c292cf7fabde9dca9db3 -2723bcdce3248417e98e6c43207bef74d34076c1 -ed22eb4a62bd8d5369aaec87d4cbdc03c9f16368 -9111df9673beb6d6616d491a5478f09b5f14d040 -d86bb075bf6d1e78c1e4f3dd38b0ea828ef5ecfe -50a1cc0f0aef1514b917a5a3f4476967170b429d -6ce733747e160ca699711f2c47e686284ca9aa07 -b44adf92342ad4f9c343ba29c081a91687932936 -88799ea1b1c08f4bc1a487c9e3c2effd5e1650ae -080d7c700fc3291560d79fc590e05b8e2bad984f -12af74b289f8cdc6caf850dc6c802f9936b1e8b3 -8e4f7e72410df3ba430082c7cf385f26fd75b033 -8ac80412867118172dc4172494304e19969e9489 -f2734c2828f69d9cfd535e5eab0592a7674b2b61 -0b9fb682890b8fe10cec54072b809a5efe57d33d -5b029aaedb5fcf7cadd249607dd28eb3f233ab8c -79af9fbd8c3c0e54702a9c92b171f134bd4466c8 -c412fd805ddf3282dc2e1f28e30f51ffcb1f1da2 -111849345bb5140f86b48e730ceab4bff45fa2e9 -a0b1e57b20a17177ed5a9a54e4a8aab597a546b4 -ca209230c8e73745cf8cfc79f500c9c46e103306 -a230b0588788dbe1ac84622aea169c577b381241 -dfef6b6af08097f0676a2323085558fbbd3c48c6 -3192e5278abca7c1f3b4a2a7f77a0ce941c73985 -7c7ddd9ead99a8b5033a1a5d4698032c9e2b3a92 -10b930dde8f14e9cb661810e97a33bbf144fc55c -9225de2cf652fe2bf6e50636824cdb641546f57d -598ef9c44b3ea2cc142c175f077b493f39f5ba22 -c49355c7170a64bdd7864cc3ba9a64916b67fe7c -857d1e171e051b254a617f27b39f6a551054cee2 -21833f9456f6ad5bc06321ad6d9590f42ce0195c -8910b4717e5bb946ee6988f7fe9fd461f53a5935 -5703dff0939f05c7457cebd6fc61d88ab13afe41 -8bfa13b15b84cb372950fb7b25a1080173060b6a -ac23a7c1f19b3d8c326ffe75c8e13edf285f90fe -19be26afe3d04783a92d032b55bf3fb1e2ae63cc -f7ec7cfd38b543ba81ac7bed5b77f9a19739460b -36afd4db4442c45d4078b1a7ad16a1872b5bee0d -88c2ae3ed2bb5d367dd408c9255cd8f1e7a36c7d -a13a417cdcfdfd1f1b3bf997bb6ffe6e69b096b9 -d6064a89ac97dc0d2ce9da3982e1a4e25afaeda8 -7146d96de3e15a80cafbab2af48ff6f65d8e41bb -5628c70f2a44567695e5331fe2293c5b7f35b629 -7ff4a538a8682cdf02a4bcd6f15499c841001b73 -aa5fa642b0e7ce2ea55e2298886f212f11a8894e -8efd1c820b9a782d8608d54d924658536178295c -50a226563cd8d7c0a5e8448e87fede0eb72a8354 -b860915f8b0dae98e57a254d11575ea41f5c5a79 -d304fef3746039183f51b3ac8f4774dcf3a64f59 -53ab12d9318d5d195ccc77028b0e3ae66dc6e1fd -668de70be039a4f1ffcf20aeae2a22ee71fc55a8 -0fea960ca917b73aff853fe88476174c8a313863 -f89502306dcf6393a2c7b0efbb0fa728fc582137 -ff58b1c3bdff5e5f687f10f9e40ce495ca49674e -0b96abc35f1a9d46a27eeddd7df418d107c29c57 -b0b57a17306a7e963a4fe463f84e2b150a00a859 -4105cb6fd964ad13099ca83b1fdf3d35f3961f74 -23281a4dc3afc42a001346caec4dbb8193f0bb53 -8daf103fa138f9a184448ebf1c2e03b9dbd96f21 -02e5308c1b9f3771bbe49bc5036215fa2bd66aa9 -a65ced1a66575c652baf5084644b8647f531be8c -2456a835f0bc7796d9ff71f64837fa6790e2b7cc -9ec1330b455c1ab2eb6b89f8a2ab885677d4ae8a -0b738075bd43fbd4410e30a51e0498cbfd2b7513 -98c80e374b84e5a9c2d5c36889a0b1ebed5b814b -25720fc394e27a951bcad26095fb5a711bfacb8f -4cfd57d2e38207d78722ce8c9274ba8dd700d1cc -0fc1c31a878e93d938c67db3f958e82e3c39659f -df1ab5b4d67b46b5e9e840b1fbe0ff02520831f9 -5bc3b6cede8dabdf3f4f27ddb03723cbb7cde51a -c2ea1e6561caba3abffce361abc800822b9e0efe -caa2f106d704ec3ade63498031dd58d34510bc76 -dce853ef76ef90c46d84294225088d595467d08c -dbc8a8c86ae50059fddb2d6834fa5f0c9bbf9b71 -0f921e6a0492c4e9f037a9ed91f474885032d68c -041331e1da23e4136fd046ed870cdcc177464176 -e6ba5068f107ac234576e77cedbd748b665369c2 -76fcd9d5034143a5b041766552670d19f926097d -72bf1b3d0962304850a3ef5fe375db4bff1d0a39 -919db037f1f5cc73cdcaef92dd9cb0e7f5c8dec3 -c36229b0b2e9d4554053f5c9fc451ac29a493b1f -9e4bb312e6958d2baa309ba670e5eed1523c6f47 -d7ba4a233bd5a6f8fadee681c68a995e23fe36d7 -98514988a3d3e8b7dbf0463884a5c38f5ed5562d -5412c08c3cf13577566064edd04da021c37b7cbe -31bcc667863f368157efa1143a78623a5db8f0d1 -7bd1aa566fb4a4fe194f209085649f2c722b0cff -c4522e71c7e1d8ecfd70112e9375b9d00d6733a8 -e22f409f18881b63a8e747036584a71217f40e6e -97ec6e5c9098a1240655cfcab05b6cd5eedb6cd1 -bc121b0eb19713ec72002b5be03ba5ac35903a17 -c98f6b3d93a2cc1b49a6db425ea2b661089d0f9e -0de7fd36de57a68e543b4c1f184fba192c398c73 -e662d281b837c25b2b70525aa8fe8af894339823 -44adf683ad232db8ce0cb89b3e236a1f5944cfb0 -cb2ed300a89ebf9f0654da869ced665ed8b2abe7 -0a6d48d9ed60b0b02177059ab116f8f46d2cbed3 -b42291334651fff46dbfe5947a726f65cb9d7dfe -e5364991daecb73aca3bb5ac37f2619d7a89211b -4a2b170c075ce703cbdc82519a48016a9ee3f99c -924de0bd75a7f75df65d7d15f9d1587a2e794abf -1253f8692fc3a11be9430685cd405236a68df6c3 -2b799ae9e1e0a540f9a5971ddf27d83254668279 -c9bdf9a75f9fde8cd011e4aa94be4ed4347078a3 -3d69ecb4edeb80003a1a41442e320898a30dbd9c -f08222e882b18c1f279308636e03beceece2dbf1 -23e03f8d26d7bd03273a5dcbdcfe3905dfb49ffb -03dd707dc027fbf6f24120213f8eb66571600374 -d0754799698de2c032abcb8198ee5d5401063213 -072116fceb2294b97d1c40f79305f2e3ff71812b -e66cc1d58e16bf1650dd6479fed64ecaca8c6098 -f137753a2dcd8229f89d1d1ac28039364e5850b4 -61d191fbf953700ba8aeadc9c8cf4c195efbd10c -76f3c02fb01a6df98fbd8c16ac21d159d4649d37 -6013c73b3312e11b447ed387426749014716f820 -6faffb8a83db3f209a303a4464dbdd597faad5a4 -cc9e8aca5f950c78dcfeff63c441ba993c1fe12f -8ca69a2a88a77eb06149fa049ab1a7e6de38b321 -2f71490d21796594ca6f55e375558944de9db5a0 -08cc5fd666456cb476467473ed1880c90c92dedb -e31a43c725ebe641d7c219c3886eee18eebf0bb8 -52b5a8785de760a204b2b0aab19dfaf79c2c3ff0 -483e8e4f4875a1a621ec9e9df2880d3037d95ed7 -1e5799c52535a3fc20e885916f1e7ed33ecc7f46 -a82e5d8220bbc8b5d786bed99b0876f530b9b7cc -7fe6c5c993706e8395cdaf7977bee793c06f48f3 -2a0836f6d5e7c1d7e97bedb0e0ea33dcaf981f77 -ddc308068d69c6c9aa629ee3c4ce75e1d1cf08b5 -ec139a5621a9c9f03e1988391a3c7c6c5d849776 -c01a6c48b982d625fd9f4f69005878781d3d56fa -95a983d56dbda457e3bf8766d59bac74c7aa5699 -760741a00833876976389ed7a6b73f36ee5b4c13 -6e5e5abba6f8bbbe61c22795df440dfafcfdc378 -cf2cecb18779ce83de9adebf382dff1c19b12840 -af9b7a9f2f73b1a2f9728106774dd13e8d1cdd8d -115735d547fdeade822f547eb3e8c8f9961a9b07 -c2c69edf37b5c02aafa01d0407dadbf5ef8751b5 -a072d1a83787e786d074a4b5871b0b961781f7c6 -ed2cd59e258f756b2eaed7909a60956ade6ef7ee -ae5575ba41c8a782805afb1c08730343cfc22397 -6ff2c8d29f6b5a5c2ce63f0a16f3bb0dbd049451 -a80de15113166354cdf208e3d8b6e25f4511a591 -06bd4f637f15e769f088d9051a5af94bbb0217a3 -6700cc993cc07fb0f5b8b577ff8c4afcf0b18274 -37f9a1f627c0995d89b62923e75cd092600894f9 -8844ef15ded02d5ed86fb95aaf251235fcef2396 -1b87e5b5b184a0a6c683eda23b36393822b57f03 -e2bf830bb6c1bfa038c943dd6f5d92a406bd723f -423ca302a3ee87000530da3c105f269b8fabece7 -4e14afe42fdd468d5de11df8cc13defdcb8e83f8 -3e90fe6534206412ea22beaa445cf20d28fbe718 -88b77c7da0a672c89e24df37ea6e9085b4e2a05c -0ad104190465d8d65c2344bbe10dcf3df025d86c -5c7df7022bcd360e6af00b9458b1a3fd54e1cc9a -59ad56851a342d2c62f6b38bf15002b23ab439e1 -d8cd7b137fb075616f31d2b43b85fa2e27ea7477 -655937ebcbf681ededf86b1f0f60aac45c73393d -abdfd2d0e3ebec7dbead89317ee9192189a35809 -e439aeb30c0439001a781c5979aec41e1fc2aa50 -b9b26d9c3615d15669ae0a049c1dede39a9e59a9 -fdf146f3293c487afdc4d6d9f6b64099aa8bd28a -16e3b175781caacee403a2dc40cd6c70448e12ef -b30c62d4b954df05bf404cfbeb5b728282201496 -b3f377daaa86cd7755a552fa3adfeb195835f58e -0a8f519a0626d7cd385114ce610d23215c051b3f -544f3234384b2f6c290e987ad18576e1b50d7db9 -91482e5bf22d283d32c9f83c8057f10971848107 -e754c6e33194e9ed69ba5350c5139b0423b645fe -dc1e54206d76e5fa378d28a18ae1fb2bcf714485 -b2863c0685a5c12f829095cbabaf26ccc49e46ec -b14db5abab405a708f0166293f1ea12222a6bf03 -8010ded6da56842c09b14665343cd189d7e08401 -d387507aeca652a5569825af65243536f2ce26ea -27bf14f6f3e0fb1f348f13c1b54fc6b67b3bef6e -f8d470e24606297dab95e30b1d39ff664fbda31d -b25a4c2284babdf1e8cf0ec3b1402200dd25f33f -1329ef1f00e4fad83937ddd8721d0292ccfe7808 -9a1ad2c5cbdfa3114d05df57103c34f72e087f26 -1e90862f5d0b5f7dcc18fb018b2bbaa323dcca1d -ad552a54c56a420be84b47154882c3e4c76f9bdd -90b1c7e5c50551f39d4983008d1d5ab3b085803e -d6b2235ca45e072961e25a35e6a159e97c9e556b -2643fa50869f22672cbc72ac497d9c30234075b8 -01f909828d126d5443bc28758f51781eccdf5848 -f54f3738c8ce839c413d7b6b719be2ff341536ca -418ae49ee1eac2c9d6cd4ba83c036a41f1afe922 -5a666428b0f11d62af2002bd54a45ff2f79f30cd -a07e8caa5d5000286604458e6887f57fec7fdcbb -8b262eb2d80bfa27ae8501078ce47bc1407e9c55 -5df84de583c900e00fef63bedaef32786f205a33 -4ba6da55743a55189164e29e45ac9e73a074d808 -88430cbab4dca36b6a867364cab319cde6a9ebca -e0f7515f5500968c86e5a9f4912d83d4abc5b2b9 -9b8b1079ddab64ac955766536c38d23dc57bc499 -af20f9b1d485582b8c8aa8294bac4f2c540246d2 -7be9a9a570c1140048f8781ced1111e1d930e517 -2bac3e484114c30548e286972525dd799dbd0a5b -df529dcc65e8037c5a3a3ad74545be294a770f07 -6acd8700bc0ee1d10207a362c1e07372ba274041 -ffc6e48b2983189dc0ce7de0a038e5329bc07b1b -252ae7111cbff09a4cbc5caee9e02b6ed3580476 -b3ecb7bab6074377d87c700bf0c5d351e5d3174f -d9fdac130a5ed1d96fcac6bb87c10bec9d596b17 -a07e8caa5d5000286604458e6887f57fec7fdcbb -8b262eb2d80bfa27ae8501078ce47bc1407e9c55 -5df84de583c900e00fef63bedaef32786f205a33 -4ba6da55743a55189164e29e45ac9e73a074d808 -5bea05bc1d17aa43cbdf3a3413241f8132790d93 -c17f11f7b43ad3bd9e242c67db1f3679558a0581 -5ea932a51083837cdd27715e10a3a0d5d553af24 -033c78671b91b12d589ebff6c5ede8d94d7500f8 -ef8a634358848847e006c43ce621bc17a612fd1f -ba216b5fa63e7e6cae847d1e3621f5c54840f898 -26fee4f6bd9aec62c6caa60683ad66574cf16aa6 -6ab0e4cf49549640b903bf5fce0e6035b8116397 -326a5652e0d25fdb60c337ef4f1c98a63e0748f0 -424be03305143cbe5da5d5adb54d73d3dc3747b6 -38c201f47c0bc388a05cdb35d6137150fa90193e -12ed800ab870e0fc527a84d6e4584b10c8d239f5 -aeed345c9bade5d52a3fbf0a943203f6c82e6344 -c6223b3daab0328ca742b1cc3c15e89e698630bb -877678710800a4d78afc12519424f232f1a583d3 -6c4fecfaf7beefad0d1c3f8520bf50bb515a0716 -98212745c8acb5cc4e688bbb3979bfd46b25f98a -b9bceaf1c081a84d9fcc680372614e797b168a9e -1afc22a7667a7a5c66b4b5d7f50832356dd5ec12 -3255d6347b1f9eccbec3d6d93d4a424087a3b35b -ec20f01ba0945a3113797ac98a6b3500e24603d4 -75b5643c47c3b382ed97a9f5e2bdc883a0f98709 -fee0d803fb55c8d85b5cd1ff69d799c5ad522e18 -565494619d809655fa94d274bb2202d25553e485 -ad6fce67b9bb6eee864c8431ad3291aebaa2e5d2 -99c7db8731cc77f143b52f544b3fdd93033ed20d -b4d03be3cac04da8b5d5fa17e29c5220b75d970b -ef37f2033c4ae104585cd980141262f95d33166e -5cfdda2503c995cdd563b1a2a29162ac298d173d -c5904e871479514b2e2e18b4fdbbe468c4e5ec8e -10b22e3141a603ec891d2cfc7100c29c7409aabe -afd2fca911c4a5e3a4d1f0993a226d40f250aff4 -505955052e60e0681865f3064e005ca0d3aa90bf -8fdd23a224ba236874ef662c4ca311b002dbcab3 -1c011ff430106b5f727f2eaa0f7f4883cd2122a3 -ec8a50b8d786a8cd1192e692ab19b46979add582 -f90603ac6d24f5263649675d51233f1fce8b2ecd -b7d6623c76e1468f2a93db5a3120580e2784d74a -66270a416edb1610f276124483feceef9cba93ff -e4fcbf797ed3b472d352ac3794ec82f581209c50 -479afa0f8486146a35f1fb96be1826061ecbcf23 -2a09a3891fde052a585dc019eea9fba26d42445d -90a002ea647dcea57a2ed4294eab77897168ba1d -30c21306c17165c3925fea4ac9d1a4763c6d2a99 -b3eb0d6485510f2bdf36d256ab60ce29b8213744 -efbcf2b1d5ff4ee7132eae9c9e203d2b875c545b -b33ca14f594e2cf2a16ef27778169deb7cc9f4dc -d636f3943d39ec893dab2d2546f77f3f2607769d -cafe24f039e117d53288387c2720f44f27deecd0 -de8db47b7ff351f3287c5efb85102ba8836058d6 -d76e84a21416ef77e78138e326d4d249454e79dc -7a74f88a26cf251ba36b26f604f1ac9940fd9c92 -1ad3d4e1261f4a444d982a1470c257c78233bda3 -8d9f45ea6a5e4220e44d34139438eea75a07530b -c98ebf1bfb29a8203b5090412afeb333384213cd -f18bb49547095020a30e81b648075bc7e707515c -76f268b9bd1b69eb7784c5324abbb67f3e395b97 -e801084decf4542d57cf5ddb95820643766a172a -be3e042c20e2f3449b7b55d1cab0a80b0c6f00af -400fdd08cc95f1e85afafd07ddd9c0bed11483ea -098b01dc58ff555c473ae58c92c34b03a77eda5f -7cc2c670e3d7cf26454ac8547a94ec2c8ca90b34 -1088b02f0ccd7358d2b7076bb9e122d59d502d02 -f94b7d5bfa911ea7125920589723ee63a3eec9f0 -b4b057a3e0712dd16b50cbcfe7d613e4413ffa1c -b40ceed98a112f4f0d07351ce07270d9ff2bf796 -4cb8757aae1ae31e5519d81e854f44ed062d9836 -f2f7e97e8cc24cf7a2b7954cb74ecfd0f91a95ad -ae786098bc58b1ca92f596a698b23aeade9cd2cd -c33652576ce21694b33a94832378f737dd6959fb -e317c0d19201ff75fa7afedf93a9d1cd2c560af2 -bee35299716cc72cb7d5bd4daa9fddca05c58378 -318ea50a1c2f612e750a93e36620dd0c4531e9cf -b6ee855b411ee9bc39f935d0da3298a773a2ed37 -daf3e7def7b9e5db7a32f5a20b5c4e09e3f0dd18 -bc64b5aa0fc543fe8fd3dbaec275f89df44dc409 -3f57c55dba6ef1fda2bdf6fd9abd8ca7eb6828e4 -431a548faaf51c7a5fc89b6e479187a1c0e29805 -e4bbd3d230f22401ba0a0a72c8ed41ee1bd098a0 -c45da32047cac54afc99cf9b8a539389c577ded1 -ab1f1d32469180b3d011e9625d67c86a22b55903 -a550f6e415fd8aec8c45d4704712a408c37ecd18 -c73af5416b66f09cec0eb106f5a10f9bb6ef9cb1 -a077a90da88f12d9f10c8b85840bdb847a98b0a0 -c5e9e428a9198c8c4076f239b5eaa8dc95e7985b -b7365f0545b1a6862e3277b2b2139ee0d5aee1cf -4bd0e9b90a39c5c6a016b83882ae44cb4d28f1f8 -7438ceac716fdfe6621728c05e718eaa89dd89aa -4e3efd47e0d50c6cd1dc81ccc9669a5b2658f495 -5ab6a942764bf6577ae311f2551153dde3d4830c -b04f42efe31e23e15cc945efe0de906ed2eadb2b -ceae0eb7e31f9d3495a13a23df7372e5e870b572 -5bf65ec66e5986c9188e3f6234f1c5c0f8dc7f90 -55c9e2d790fa2e137ccd0d91e6cf3e2d0bff4813 -ba29911e21c88f49780c6c87f94ff8ed6e764a9d -fffff0abb9c71f0af83a7925db3c293b3bb12158 -aaeb315ff0f7956449a92736160795f0140369e3 -0dd34773334c7f4db7b05df30ee61b011795b46d -2598720d6c1ef15288045599de7e65f8707d09bc -bc83710fdcc09d8e427e77457df107acc9db1be5 -ddd7a39aa960ee3639ef1e59b2e53852e0862c52 -0808c88d7bd992d5c9ded0009c9563f6177b4035 -a085a554913ae8f4ed83afac830ce6dc39c9cc65 -b1a824dd06aa58618947783edee2dd891b5204dc -a4e066af8573dcefb11dff120e1c09e8cf7f40c2 -58b9d6cf9e9b801be9c677a3ae121e5d2950ce66 -7377ed778c6d832ecd291e65b2789af7bac2ae2b -c3a41ad980cc5149de3f9ec8414962c183b1fed9 -5884a47c367f6ff1aff3ae1ef6894881c5a5e0b7 -1d39c9ca0672e7ad4c1f0959f9d58d2fcc7dc46b -e16f6441044fc2123e0cbdcbd8a5842ec3aae7a0 -6c6cc7989cac79450bf83b932ca82d390a37e17b -bc28ca3afb7f6656a0bf50038a5e383ee7f9b219 -57a491bee17af88f75c2cea8c109d93b1cdbc9a8 -f8586b25f6a4f1e30a54e58f45dd28aaf580bbc6 -e5df0ba0d97e5f8cfd748f09d6ed77b7bfc45471 -1b0469199bdaedfd452eea718268be7fd50db3c0 -015717e2b873b7a2ce433bd3be2328a782aa5d91 -3b3c66f85959f3393a3a9e87a29004b526f91b93 -874529665c1c326fc86fc0d0d6c3512fab087ab8 -7f2c983e1cfdb58b6f84eabe5ff6a16f143f39aa -0ea92cad5274f3939f09d6890da31a21b8481282 -489b5876698f9bb2d93b1b1d62d514148b31effd -faf25b09d9e78f2ff129e25b90f67930d2fc1c4f -df933596e7e9aa17f7e5cd6e1c850520f5b56f1b -9e4fbebcc8e497016563e46de4c64fa094edab2d -1557014378cc5a6234a9244fa60132955206fd27 -c5fbcf5f8d7b36bee54ac80d1027d0dccea2aa75 -cccbc5fe3ea5ae52426203f4485b11071fbe4b6e -5174a139c92c1238f9700d06e362dc628d81a0a9 -9dae9f5f1e2bf29f58d3f49b0c612063d883b8b3 -e282764e049523439bc8adaadc002a1420122830 -d8ae5044488248d5eb134aa7c0a15c813a2f8715 -06ea2783a2c11e7b171e2809c3211bb3091d894d -00ce8543f16f4357926eb6dc701ac6229142be80 -1f63b460a8506675ccacb4647941f07d391735e3 -a100c42a136da5ddfd09aa442543ec2190f24faf -636991d0c0f969968c790d490c82c1d2fa4e8047 -dd52f79a73eca18301db1569d517197160018dbb -e157b98640c7cfb94cae7e0faca3bcffc2dde990 -ad9e5eaf77bf7e19a926a43407c88386a8a1e58f -c5e67be03bb06a5d7885c55db1f016fbf2333fe3 -48eec32347494da781f26478fa488b28336afbd2 -c324b07a541a04698954ece94e5879ae7131c1c7 -4901631dac6a883c6ddd0d4e5e3edd08b10d7609 -cacbdbaa95317b45cf2100702bca92410fb43b9a -b4f686952a60bbadc7ed2250651d0d6af0959f4d -90e49c1ececd6296c6ec6109cea525a208c0626e -700808754884919916a5518e7ecfdabadef956d8 -0cd1a2eff9e0020ec1052a931f3863794d1a95d9 -51527ec1ec4264f7e24ef548bb049db07a89fc7f -ed4eeafbb6e2e73ff9fb9c03bd66bbb049b8aacd -d4475ea7ae70ad1a1f9374b88c68f01590a88d54 -5e1aacab576b8d8918da129097a9ac0816b6ead2 -fe6a299fc0020cd62156d4b7dd9c8dac358c69c5 -0047d9b89b9fa6be660c363961cf0af72fa62ecf -037c5e511fe2185d244049cae25a98f99b878787 -8730bd3fc87c8a7d44347267e1ff3c7a8674201b -47b8256da872722953693c4037d1b9e07caadcb1 -85aea18ae660b5edf7b6c1415f033cfcb15307f9 -132d5f8c2f2397a4600a42203f413dafdb6bcc37 -23ebd7a8027f12e722834d214113892fe8561fe1 -a19f641a80cb2841ca9f593e9be589dbc4b4ae7c -1e7db37e76c510d373c4404eea2b97508b367aca -16fa967d3cca66eef0f17b41fd8aaee6a1420fbc -9eedbe98c86ff2a9214c24c37f6524ce67fd129b -0342ae1d395ca82614f6d3b8fabb6a44403baf2a -777b89b3008e53374eb13fdee70db315cd61a703 -8b686776ef5cbd6ef9d5281c3136eded25ea35a4 -c90b42bcdb594638c5759ef5ef0773314d0a1379 -7134327be5c1bdcef7919ed735049a6bbfc457ec -e88a52e9a2fda971d34425bb80e42ad2d6623d68 -173c79626867e9f89d49be7dcbb0c2042c480553 -2513499348fa955d0e4b0970b08ba9e715e6316e -43bb10661360d9f35d921d493a1f94ac95df00e2 -6f55ab57cbfa414d57a8e9fb9a47f9bcd8c836d7 -6300b9556ec927a61371053fafe1a4045f5afb00 -f8b2e9bcfc76fede05f5e12f7b15f0d9c9d0add5 -b297b945f7610772434817181ad12067b2832565 -57a73d71a36ce212977607d3e94de6ef55521bfc -5fdf37e14bb3b66264a7e6868250c2084ac39054 -3059d4dd72af73b654077d9f72019c47edd47674 -333a41882c5ccd5f0c7f884f97d25449bdeec07b -7da4f65a00a8d96da2119de613ed7fbee2a28a0d -e14f0fa6a346afecbb1d5470aef5226a8cc33e57 -cf0a8b9c4870cc88254a757286140d9632e7b70c -b69fd5eaa99f84b62a49d7c7f48d8cee1227592a -1e3ed01faa77215a7c36308237280aaa58895532 -6c9bc14a3f2cfa50144607c820ebab5288f9571c -8e3c266a4f02093d57d563f32ba73d3ab4b5f208 -decde9bba6f9d3671bdf0af4fe6ff4bf28992d1d -9b7eb584ade2ce73dbfcda080935172c3857b758 -3bbc46ddafb61f68785c7e581817db952f99d93a -bbb83f0b2b2671980c06453fd243b1f2801a1cc4 -6c9460edaeb6c89692b71f51be7b7ee386f4f5c4 -b3072799248fae8fc16f910b642edb9c5acf8bac -696d39410fc3372d120a6e89695c1543ac2fc052 -c5c4fb31828107a5ded88627632e19e05b2c7e83 -9ce1c506a3a5d20b1bf254235bfae48af592d86c -fe66dad8a779ed928b1c2fc0c3accf594b042877 -f421de5be611f874a027392d5fee7e113dce4f54 -d492dc1cdaabdc52b0766bf4cba4bd73178325d0 -6348bc61b533705a229f2c2ddcff2bdd98849d07 -83b26cb97cb46516aa4fdee3bcbfa751d28c1233 -afac75f140a3e7d89877f03420e1bc64a8d8c6cd -171f6f2699dc27e77843318be2fefdfcd9e589fb -50c806f001d66e20f314777b9fa7fefa01dc6893 -bdbabc50ba6c87ded97ea2bbacd3605c59cd12d0 -9e32adbb5c543885b2c01a984bf1e4b80e8cec16 -7c08d81e119570792648fe95bbacddbb1d5f9ae2 -65e9ca22785f4a799cbcff6d95cbe1ce4b4a6bd2 -2948d6dea098bf722828b969838668f833c2cb00 -deb847b75710d600e5b0d3d5c77fa5166d80808a -05e5af5a6c884d2ade3d7acc766ad5380cb85b64 -cba41db327a241f992f9329b214d9070888255b8 -f6d335e82822ed8f2da56c1bcaddf7f99acd4997 -30308cc380a8176a5ec0e0bd2beed8b9c482ccf7 -8b6cd42c6226dea28c182a48a214d1c091b9b5bb -267917f5632a99bb51fc3fe516d8308e79d31ed1 -ba11eb354b9f3420ebb8608227062fb639a07496 -848b11615b67a3c49f76ebbcaa241a322d8014d8 -25290071c434638e2719a99784572deef44542ad -159f89c118645c0f9e23e453f01cede84abac795 -37637bea3a9a48c0d52d68d3f78f154f8249a009 -0a76bf848c72211f986a6cc5b1c13de820b861dd -358fe779cbb2681666ae5ab23a19662db21a2c46 -c44e734dca64a15fae92255a5d848c04adaad2fa -8add59d77dd621be57059229f378822e4b707318 -922c49a1389531d9fba30168257c466bd413f625 -df0825046acc7cb496c47666e36af18118beb030 -c23bf06492dddacfc0eece3d4dd12cce81496dd0 -3eec29ed3aa1c8eb293a7a7a6be356fc014f8813 -a7e80449c0811b361cdaea39b6bab78ca5fbf668 -5e8e0b3d7f6055e326bda61e60712b530e8920f0 -a5edd191be93aff8f9c0f60f04e711e2e78ecc77 -515200298b555845696a07ae2bc0a84a5dd02ae4 -e8a3882e20f0ffeeb9b3964c2c09d8aa5eb53fd4 -c545a7aeb1d559377933c7b2e6edc2d4a37b33fb -df669230cf2001dd869e897bb4f2d9c46f9accd9 -56a0fbf8365343d73cdff2b0a0e16542294d7577 -196b4599201dbce3e0317e9b98753fa6a244b82d -cf5bb048e80d4cde8828787b266b7f5f2e3b6d7b -b94d0c7af11bd91dad4f180ce2a2ffa09e4b5668 -792d0d8d512cf8ddca200317b74ce550c1a1a428 -767ee2e3a1082468b4e2248bac3ef8bd54bb2ddb -31db3dd874dfbba88616c96a5767e2c9861d9a7a -018fd9620293582f0ce43d344ac3110e19c4dedc -801aaac2b39564aa14009785146ba26d2506fb53 -121d47afe3e67ff7f94d26e08a39573dccf652aa -af7fba3af788e91a460582351d40f8f5e2118760 -8f1c28a609b203e0d0a844d9cc5ada9eb9160a5e -8319c4e906e6df5f2048e7c048942fde285a93a2 -66be456d93a66526322b7f36fd734a8dbd5e5524 -c006ab29ceec9274dc85a0de7f7d0502021a4b87 -1220af5e6d1072ea306f6ecaaa7effe3d386c379 -14ba286556faad794f288ef38493c540382897cb -784a21d35466736a7a372364498ed94482a12a2a -4ad59042b359f473d5888ecee0c9288dcf98f1c9 -fee16b15fa3425871670239c25d4e61ae961e0c5 -216f4ca9e7ccb1f0fcb9bab0f9940992a87ae55f -2d0bdb2089644f5904629413423cdc897911b081 -50c502f54abd9eb15c8ddca013f0fdfae3d132a9 -c840ab0231bc29057172179f005001c9ab299554 -aab5e48d422d396aec09bd6389de68613b19def5 diff --git a/contrib/verify-commits/allow-unclean-merge-commits b/contrib/verify-commits/allow-unclean-merge-commits index 7aab274b9a..e69de29bb2 100644 --- a/contrib/verify-commits/allow-unclean-merge-commits +++ b/contrib/verify-commits/allow-unclean-merge-commits @@ -1,4 +0,0 @@ -6052d509105790a26b3ad5df43dd61e7f1b24a12 -3798e5de334c3deb5f71302b782f6b8fbd5087f1 -326ffed09bfcc209a2efd6a2ebc69edf6bd200b5 -97d83739db0631be5d4ba86af3616014652c00ec diff --git a/contrib/verify-commits/gpg.sh b/contrib/verify-commits/gpg.sh index db5bfce208..cfd68e45b8 100755 --- a/contrib/verify-commits/gpg.sh +++ b/contrib/verify-commits/gpg.sh @@ -5,12 +5,9 @@ export LC_ALL=C INPUT=$(cat /dev/stdin) -VALID=false -REVSIG=false -IFS=' -' if [ "$BITCOIN_VERIFY_COMMITS_ALLOW_SHA1" = 1 ]; then - GPG_RES="$(printf '%s\n' "$INPUT" | gpg --trust-model always "$@" 2>/dev/null)" + printf '%s\n' "$INPUT" | gpg --trust-model always "$@" 2>/dev/null + exit $? else # Note how we've disabled SHA1 with the --weak-digest option, disabling # signatures - including selfsigs - that use SHA1. While you might think that @@ -20,12 +17,12 @@ else # an attacker could construct a pull-req that results in a commit object that # they've created a collision for. Not the most likely attack, but preventing # it is pretty easy so we do so as a "belt-and-suspenders" measure. - GPG_RES="" for LINE in $(gpg --version); do case "$LINE" in "gpg (GnuPG) 1.4.1"*|"gpg (GnuPG) 2.0."*) echo "Please upgrade to at least gpg 2.1.10 to check for weak signatures" > /dev/stderr - GPG_RES="$(printf '%s\n' "$INPUT" | gpg --trust-model always "$@" 2>/dev/null)" + printf '%s\n' "$INPUT" | gpg --trust-model always "$@" 2>/dev/null + exit $? ;; # We assume if you're running 2.1+, you're probably running 2.1.10+ # gpg will fail otherwise @@ -33,33 +30,6 @@ else # gpg will fail otherwise esac done - [ "$GPG_RES" = "" ] && GPG_RES="$(printf '%s\n' "$INPUT" | gpg --trust-model always --weak-digest sha1 "$@" 2>/dev/null)" -fi -for LINE in $GPG_RES; do - case "$LINE" in - "[GNUPG:] VALIDSIG "*) - while read KEY; do - [ "${LINE#?GNUPG:? VALIDSIG * * * * * * * * * }" = "$KEY" ] && VALID=true - done < ./contrib/verify-commits/trusted-keys - ;; - "[GNUPG:] REVKEYSIG "*) - [ "$BITCOIN_VERIFY_COMMITS_ALLOW_REVSIG" != 1 ] && exit 1 - REVSIG=true - GOODREVSIG="[GNUPG:] GOODSIG ${LINE#* * *}" - ;; - "[GNUPG:] EXPKEYSIG "*) - [ "$BITCOIN_VERIFY_COMMITS_ALLOW_REVSIG" != 1 ] && exit 1 - REVSIG=true - GOODREVSIG="[GNUPG:] GOODSIG ${LINE#* * *}" - ;; - esac -done -if ! $VALID; then - exit 1 -fi -if $VALID && $REVSIG; then - printf '%s\n' "$INPUT" | gpg --trust-model always "$@" 2>/dev/null | grep "^\[GNUPG:\] \(NEWSIG\|SIG_ID\|VALIDSIG\)" - echo "$GOODREVSIG" -else - printf '%s\n' "$INPUT" | gpg --trust-model always "$@" 2>/dev/null + printf '%s\n' "$INPUT" | gpg --trust-model always --weak-digest sha1 "$@" 2>/dev/null + exit $? fi diff --git a/contrib/verify-commits/trusted-git-root b/contrib/verify-commits/trusted-git-root index efb6b9f7b4..7ec318e1ea 100644 --- a/contrib/verify-commits/trusted-git-root +++ b/contrib/verify-commits/trusted-git-root @@ -1 +1 @@ -8ef096d4f8e08ac691502e3fd34721a8bdfa9044 +437dfe1c26e752c280014a30f809e62c684ad99e diff --git a/contrib/verify-commits/trusted-keys b/contrib/verify-commits/trusted-keys index eeafcdf205..94daf28b15 100644 --- a/contrib/verify-commits/trusted-keys +++ b/contrib/verify-commits/trusted-keys @@ -1,4 +1,3 @@ -B8B3F1C0E58C15DB6A81D30C3648A882F4316B9B E777299FC265DD04793070EB944D35F9AC3DB76A D1DBF2C4B96F2DEBF4C16654410108112E7EA81F 152812300785C96444D3334D17565732E08E5E41 diff --git a/contrib/verify-commits/verify-commits.py b/contrib/verify-commits/verify-commits.py index 3825caf5de..f301964280 100755 --- a/contrib/verify-commits/verify-commits.py +++ b/contrib/verify-commits/verify-commits.py @@ -92,8 +92,10 @@ def main(): unclean_merge_allowed = f.read().splitlines() with open(dirname + "/allow-incorrect-sha512-commits", "r", encoding="utf8") as f: incorrect_sha512_allowed = f.read().splitlines() + with open(dirname + "/trusted-keys", "r", encoding="utf8") as f: + trusted_keys = f.read().splitlines() - # Set commit and branch and set variables + # Set commit and variables current_commit = args.commit if ' ' in current_commit: print("Commit must not contain spaces", file=sys.stderr) @@ -102,7 +104,6 @@ def main(): no_sha1 = True prev_commit = "" initial_commit = current_commit - branch = subprocess.check_output([GIT, 'show', '-s', '--format=%H', initial_commit]).decode('utf8').splitlines()[0] # Iterate through commits while True: @@ -113,17 +114,41 @@ def main(): if current_commit == verified_root: print('There is a valid path from "{}" to {} where all commits are signed!'.format(initial_commit, verified_root)) sys.exit(0) - if current_commit == verified_sha512_root: - if verify_tree: + else: + # Make sure this commit isn't older than trusted roots + check_root_older_res = subprocess.run([GIT, "merge-base", "--is-ancestor", verified_root, current_commit]) + if check_root_older_res.returncode != 0: + print(f"\"{current_commit}\" predates the trusted root, stopping!") + sys.exit(0) + + if verify_tree: + if current_commit == verified_sha512_root: print("All Tree-SHA512s matched up to {}".format(verified_sha512_root), file=sys.stderr) - verify_tree = False - no_sha1 = False + verify_tree = False + no_sha1 = False + else: + # Skip the tree check if we are older than the trusted root + check_root_older_res = subprocess.run([GIT, "merge-base", "--is-ancestor", verified_sha512_root, current_commit]) + if check_root_older_res.returncode != 0: + print(f"\"{current_commit}\" predates the trusted SHA512 root, disabling tree verification.") + verify_tree = False + no_sha1 = False + os.environ['BITCOIN_VERIFY_COMMITS_ALLOW_SHA1'] = "0" if no_sha1 else "1" - os.environ['BITCOIN_VERIFY_COMMITS_ALLOW_REVSIG'] = "1" if current_commit in revsig_allowed else "0" + allow_revsig = current_commit in revsig_allowed # Check that the commit (and parents) was signed with a trusted key - if subprocess.call([GIT, '-c', 'gpg.program={}/gpg.sh'.format(dirname), 'verify-commit', current_commit], stdout=subprocess.DEVNULL): + valid_sig = False + verify_res = subprocess.run([GIT, '-c', 'gpg.program={}/gpg.sh'.format(dirname), 'verify-commit', "--raw", current_commit], capture_output=True) + for line in verify_res.stderr.decode().splitlines(): + if line.startswith("[GNUPG:] VALIDSIG "): + key = line.split(" ")[-1] + valid_sig = key in trusted_keys + elif (line.startswith("[GNUPG:] REVKEYSIG ") or line.startswith("[GNUPG:] EXPKEYSIG ")) and not allow_revsig: + valid_sig = False + break + if not valid_sig: if prev_commit != "": print("No parent of {} was signed with a trusted key!".format(prev_commit), file=sys.stderr) print("Parents are:", file=sys.stderr) @@ -153,15 +178,11 @@ def main(): allow_unclean = current_commit in unclean_merge_allowed if len(parents) == 2 and check_merge and not allow_unclean: current_tree = subprocess.check_output([GIT, 'show', '--format=%T', current_commit]).decode('utf8').splitlines()[0] - subprocess.call([GIT, 'checkout', '--force', '--quiet', parents[0]]) - subprocess.call([GIT, 'merge', '--no-ff', '--quiet', '--no-gpg-sign', parents[1]], stdout=subprocess.DEVNULL) - recreated_tree = subprocess.check_output([GIT, 'show', '--format=format:%T', 'HEAD']).decode('utf8').splitlines()[0] + recreated_tree = subprocess.check_output([GIT, "merge-tree", parents[0], parents[1]]).decode('utf8').splitlines()[0] if current_tree != recreated_tree: print("Merge commit {} is not clean".format(current_commit), file=sys.stderr) - subprocess.call([GIT, 'diff', current_commit]) - subprocess.call([GIT, 'checkout', '--force', '--quiet', branch]) + subprocess.call([GIT, 'diff', recreated_tree, current_tree]) sys.exit(1) - subprocess.call([GIT, 'checkout', '--force', '--quiet', branch]) prev_commit = current_commit current_commit = parents[0] diff --git a/doc/build-freebsd.md b/doc/build-freebsd.md index d45e9c4d0d..aa10e4a891 100644 --- a/doc/build-freebsd.md +++ b/doc/build-freebsd.md @@ -36,13 +36,30 @@ pkg install sqlite3 ``` ###### Legacy Wallet Support -`db5` is only required to support legacy wallets. -Skip if you don't intend to use legacy wallets. +BerkeleyDB is only required if legacy wallet support is required. + +It is required to use Berkeley DB 4.8. You **cannot** use the BerkeleyDB library +from ports. However, you can build DB 4.8 yourself [using depends](/depends). -```bash -pkg install db5 ``` ---- +gmake -C depends NO_BOOST=1 NO_LIBEVENT=1 NO_QT=1 NO_SQLITE=1 NO_NATPMP=1 NO_UPNP=1 NO_ZMQ=1 NO_USDT=1 +``` + +When the build is complete, the Berkeley DB installation location will be displayed: + +``` +to: /path/to/bitcoin/depends/x86_64-unknown-freebsd[release-number] +``` + +Finally, set `BDB_PREFIX` to this path according to your shell: + +``` +csh: setenv BDB_PREFIX [path displayed above] +``` + +``` +sh/bash: export BDB_PREFIX=[path displayed above] +``` #### GUI Dependencies ###### Qt5 @@ -91,12 +108,12 @@ This explicitly enables the GUI and disables legacy wallet support, assuming `sq ##### Descriptor & Legacy Wallet. No GUI: This enables support for both wallet types and disables the GUI, assuming -`sqlite3` and `db5` are both installed. +`sqlite3` and `db4` are both installed. ```bash ./autogen.sh -./configure --with-gui=no --with-incompatible-bdb \ - BDB_LIBS="-ldb_cxx-5" \ - BDB_CFLAGS="-I/usr/local/include/db5" \ +./configure --with-gui=no \ + BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" \ + BDB_CFLAGS="-I${BDB_PREFIX}/include" \ MAKE=gmake ``` diff --git a/doc/dependencies.md b/doc/dependencies.md index ec205e4b51..a9ca5b3e7a 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -19,7 +19,7 @@ You can find installation instructions in the `build-*.md` file for your platfor | --- | --- | --- | --- | --- | | [Boost](../depends/packages/boost.mk) | [link](https://www.boost.org/users/download/) | [1.81.0](https://github.com/bitcoin/bitcoin/pull/26557) | [1.64.0](https://github.com/bitcoin/bitcoin/pull/22320) | No | | [libevent](../depends/packages/libevent.mk) | [link](https://github.com/libevent/libevent/releases) | [2.1.12-stable](https://github.com/bitcoin/bitcoin/pull/21991) | [2.1.8](https://github.com/bitcoin/bitcoin/pull/24681) | No | -| glibc | [link](https://www.gnu.org/software/libc/) | N/A | [2.18](https://github.com/bitcoin/bitcoin/pull/23511) | Yes | +| glibc | [link](https://www.gnu.org/software/libc/) | N/A | [2.27](https://github.com/bitcoin/bitcoin/pull/27029) | Yes | | Linux Kernel | [link](https://www.kernel.org/) | N/A | 3.2.0 | Yes | ## Optional diff --git a/doc/developer-notes.md b/doc/developer-notes.md index e2e54e13d3..f15df1bf73 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -560,8 +560,19 @@ address sanitizer, libtsan for the thread sanitizer, and libubsan for the undefined sanitizer. If you are missing required libraries, the configure script will fail with a linker error when testing the sanitizer flags. -The test suite should pass cleanly with the `thread` and `undefined` sanitizers, -but there are a number of known problems when using the `address` sanitizer. The +The test suite should pass cleanly with the `thread` and `undefined` sanitizers. You +may need to use a suppressions file, see `test/sanitizer_suppressions`. They may be +used as follows: +```bash +export LSAN_OPTIONS="suppressions=$(pwd)/test/sanitizer_suppressions/lsan" +export TSAN_OPTIONS="suppressions=$(pwd)/test/sanitizer_suppressions/tsan:halt_on_error=1:second_deadlock_stack=1" +export UBSAN_OPTIONS="suppressions=$(pwd)/test/sanitizer_suppressions/ubsan:print_stacktrace=1:halt_on_error=1:report_error_type=1" +``` + +See the CI config for more examples, and upstream documentation for more information +about any additional options. + +There are a number of known problems when using the `address` sanitizer. The address sanitizer is known to fail in [sha256_sse4::Transform](/src/crypto/sha256_sse4.cpp) which makes it unusable unless you also use `--disable-asm` when running configure. We would like to fix diff --git a/doc/release-notes-25574.md b/doc/release-notes-25574.md new file mode 100644 index 0000000000..312a99d95b --- /dev/null +++ b/doc/release-notes-25574.md @@ -0,0 +1,13 @@ +Updated settings +---------------- + +If the `-checkblocks` or `-checklevel` options are explicitly provided by the +user, but the verification checks cannot be completed due to an insufficient +dbcache, Bitcoin Core will now return an error at startup. (#25574) + +RPC +--- +The `-verifychain` RPC will now return `false` if the checks didn't fail, +but couldn't be completed at the desired depth and level. This could be due +to missing data while pruning, due to an insufficient dbcache or due to +the node being shutdown before the call could finish. (#25574) diff --git a/doc/release-notes-25943.md b/doc/release-notes-25943.md new file mode 100644 index 0000000000..81b0a48b5d --- /dev/null +++ b/doc/release-notes-25943.md @@ -0,0 +1,4 @@ +New RPC Argument +-------- +- `sendrawtransaction` has a new, optional argument, `maxburnamount` with a default value of `0`. Any transaction containing an unspendable output with a value greater than `maxburnamount` will not be submitted. At present, the outputs deemed unspendable are those with scripts that begin with an `OP_RETURN` code (known as 'datacarriers'), scripts that exceed the maximum script size, and scripts that contain invalid opcodes. + diff --git a/doc/release-notes-27068.md b/doc/release-notes-27068.md new file mode 100644 index 0000000000..3f5c5dba37 --- /dev/null +++ b/doc/release-notes-27068.md @@ -0,0 +1,6 @@ +Wallet +------ + +- Wallet passphrases may now contain null characters. + Prior to this change, only characters up to the first + null character were recognized and accepted. (#27068)
\ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am index 5830090ada..7b9ffe427d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -204,8 +204,10 @@ BITCOIN_CORE_H = \ node/chainstate.h \ node/chainstatemanager_args.h \ node/coin.h \ + node/coins_view_args.h \ node/connection_types.h \ node/context.h \ + node/database_args.h \ node/eviction.h \ node/interface_ui.h \ node/mempool_args.h \ @@ -388,8 +390,10 @@ libbitcoin_node_a_SOURCES = \ node/chainstate.cpp \ node/chainstatemanager_args.cpp \ node/coin.cpp \ + node/coins_view_args.cpp \ node/connection_types.cpp \ node/context.cpp \ + node/database_args.cpp \ node/eviction.cpp \ node/interface_ui.cpp \ node/interfaces.cpp \ diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include index ae77b79b8b..aefefe789a 100644 --- a/src/Makefile.test_util.include +++ b/src/Makefile.test_util.include @@ -10,10 +10,12 @@ EXTRA_LIBRARIES += \ TEST_UTIL_H = \ test/util/blockfilter.h \ test/util/chainstate.h \ + test/util/coins.h \ test/util/json.h \ test/util/logging.h \ test/util/mining.h \ test/util/net.h \ + test/util/random.h \ test/util/script.h \ test/util/setup_common.h \ test/util/str.h \ @@ -30,6 +32,7 @@ libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) libtest_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libtest_util_a_SOURCES = \ test/util/blockfilter.cpp \ + test/util/coins.cpp \ test/util/json.cpp \ test/util/logging.cpp \ test/util/mining.cpp \ diff --git a/src/addrman.cpp b/src/addrman.cpp index a740760faf..f5ca9a5c34 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -486,7 +486,7 @@ void AddrManImpl::ClearNew(int nUBucket, int nUBucketPos) assert(infoDelete.nRefCount > 0); infoDelete.nRefCount--; vvNew[nUBucket][nUBucketPos] = -1; - LogPrint(BCLog::ADDRMAN, "Removed %s from new[%i][%i]\n", infoDelete.ToString(), nUBucket, nUBucketPos); + LogPrint(BCLog::ADDRMAN, "Removed %s from new[%i][%i]\n", infoDelete.ToStringAddrPort(), nUBucket, nUBucketPos); if (infoDelete.nRefCount == 0) { Delete(nIdDelete); } @@ -542,7 +542,7 @@ void AddrManImpl::MakeTried(AddrInfo& info, int nId) nNew++; m_network_counts[infoOld.GetNetwork()].n_new++; LogPrint(BCLog::ADDRMAN, "Moved %s from tried[%i][%i] to new[%i][%i] to make space\n", - infoOld.ToString(), nKBucket, nKBucketPos, nUBucket, nUBucketPos); + infoOld.ToStringAddrPort(), nKBucket, nKBucketPos, nUBucket, nUBucketPos); } assert(vvTried[nKBucket][nKBucketPos] == -1); @@ -618,7 +618,7 @@ bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, std::c pinfo->nRefCount++; vvNew[nUBucket][nUBucketPos] = nId; LogPrint(BCLog::ADDRMAN, "Added %s mapped to AS%i to new[%i][%i]\n", - addr.ToString(), m_netgroupman.GetMappedAS(addr), nUBucket, nUBucketPos); + addr.ToStringAddrPort(), m_netgroupman.GetMappedAS(addr), nUBucket, nUBucketPos); } else { if (pinfo->nRefCount == 0) { Delete(nId); @@ -669,15 +669,15 @@ bool AddrManImpl::Good_(const CService& addr, bool test_before_evict, NodeSecond // Output the entry we'd be colliding with, for debugging purposes auto colliding_entry = mapInfo.find(vvTried[tried_bucket][tried_bucket_pos]); LogPrint(BCLog::ADDRMAN, "Collision with %s while attempting to move %s to tried table. Collisions=%d\n", - colliding_entry != mapInfo.end() ? colliding_entry->second.ToString() : "", - addr.ToString(), + colliding_entry != mapInfo.end() ? colliding_entry->second.ToStringAddrPort() : "", + addr.ToStringAddrPort(), m_tried_collisions.size()); return false; } else { // move nId to the tried tables MakeTried(info, nId); LogPrint(BCLog::ADDRMAN, "Moved %s mapped to AS%i to tried[%i][%i]\n", - addr.ToString(), m_netgroupman.GetMappedAS(addr), tried_bucket, tried_bucket_pos); + addr.ToStringAddrPort(), m_netgroupman.GetMappedAS(addr), tried_bucket, tried_bucket_pos); return true; } } @@ -689,7 +689,7 @@ bool AddrManImpl::Add_(const std::vector<CAddress>& vAddr, const CNetAddr& sourc added += AddSingle(*it, source, time_penalty) ? 1 : 0; } if (added > 0) { - LogPrint(BCLog::ADDRMAN, "Added %i addresses (of %i) from %s: %i tried, %i new\n", added, vAddr.size(), source.ToString(), nTried, nNew); + LogPrint(BCLog::ADDRMAN, "Added %i addresses (of %i) from %s: %i tried, %i new\n", added, vAddr.size(), source.ToStringAddr(), nTried, nNew); } return added > 0; } @@ -746,7 +746,7 @@ std::pair<CAddress, NodeSeconds> AddrManImpl::Select_(bool newOnly) const const AddrInfo& info{it_found->second}; // With probability GetChance() * fChanceFactor, return the entry. if (insecure_rand.randbits(30) < fChanceFactor * info.GetChance() * (1 << 30)) { - LogPrint(BCLog::ADDRMAN, "Selected %s from tried\n", info.ToString()); + LogPrint(BCLog::ADDRMAN, "Selected %s from tried\n", info.ToStringAddrPort()); return {info, info.m_last_try}; } // Otherwise start over with a (likely) different bucket, and increased chance factor. @@ -774,7 +774,7 @@ std::pair<CAddress, NodeSeconds> AddrManImpl::Select_(bool newOnly) const const AddrInfo& info{it_found->second}; // With probability GetChance() * fChanceFactor, return the entry. if (insecure_rand.randbits(30) < fChanceFactor * info.GetChance() * (1 << 30)) { - LogPrint(BCLog::ADDRMAN, "Selected %s from new\n", info.ToString()); + LogPrint(BCLog::ADDRMAN, "Selected %s from new\n", info.ToStringAddrPort()); return {info, info.m_last_try}; } // Otherwise start over with a (likely) different bucket, and increased chance factor. @@ -891,7 +891,7 @@ void AddrManImpl::ResolveCollisions_() // Give address at least 60 seconds to successfully connect if (current_time - info_old.m_last_try > 60s) { - LogPrint(BCLog::ADDRMAN, "Replacing %s with %s in tried table\n", info_old.ToString(), info_new.ToString()); + LogPrint(BCLog::ADDRMAN, "Replacing %s with %s in tried table\n", info_old.ToStringAddrPort(), info_new.ToStringAddrPort()); // Replaces an existing address already in the tried table with the new address Good_(info_new, false, current_time); @@ -901,7 +901,7 @@ void AddrManImpl::ResolveCollisions_() // If the collision hasn't resolved in some reasonable amount of time, // just evict the old entry -- we must not be able to // connect to it for some reason. - LogPrint(BCLog::ADDRMAN, "Unable to test; replacing %s with %s in tried table anyway\n", info_old.ToString(), info_new.ToString()); + LogPrint(BCLog::ADDRMAN, "Unable to test; replacing %s with %s in tried table anyway\n", info_old.ToStringAddrPort(), info_new.ToStringAddrPort()); Good_(info_new, false, current_time); erase_collision = true; } diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 087e1442fe..11b0e0dee2 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -45,7 +45,7 @@ static void CoinSelection(benchmark::Bench& bench) { NodeContext node; auto chain = interfaces::MakeChain(node); - CWallet wallet(chain.get(), "", gArgs, CreateDummyWalletDatabase()); + CWallet wallet(chain.get(), "", CreateDummyWalletDatabase()); std::vector<std::unique_ptr<CWalletTx>> wtxs; LOCK(wallet.cs_wallet); diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp index ea272b2120..d5d057e96d 100644 --- a/src/bench/wallet_balance.cpp +++ b/src/bench/wallet_balance.cpp @@ -28,7 +28,7 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b const auto& ADDRESS_WATCHONLY = ADDRESS_BCRT1_UNSPENDABLE; - CWallet wallet{test_setup->m_node.chain.get(), "", gArgs, CreateMockWalletDatabase()}; + CWallet wallet{test_setup->m_node.chain.get(), "", CreateMockWalletDatabase()}; { LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); diff --git a/src/bench/wallet_create_tx.cpp b/src/bench/wallet_create_tx.cpp index 820c9d5d50..bd32a5abdc 100644 --- a/src/bench/wallet_create_tx.cpp +++ b/src/bench/wallet_create_tx.cpp @@ -83,7 +83,7 @@ static void WalletCreateTx(benchmark::Bench& bench, const OutputType output_type { const auto test_setup = MakeNoLogFileContext<const TestingSetup>(); - CWallet wallet{test_setup->m_node.chain.get(), "", gArgs, CreateMockWalletDatabase()}; + CWallet wallet{test_setup->m_node.chain.get(), "", CreateMockWalletDatabase()}; { LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -136,7 +136,7 @@ static void WalletCreateTx(benchmark::Bench& bench, const OutputType output_type static void AvailableCoins(benchmark::Bench& bench, const std::vector<OutputType>& output_type) { const auto test_setup = MakeNoLogFileContext<const TestingSetup>(); - CWallet wallet{test_setup->m_node.chain.get(), "", gArgs, CreateMockWalletDatabase()}; + CWallet wallet{test_setup->m_node.chain.get(), "", CreateMockWalletDatabase()}; { LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp index d972b71a65..423fa79c6f 100644 --- a/src/bitcoin-chainstate.cpp +++ b/src/bitcoin-chainstate.cpp @@ -82,6 +82,7 @@ int main(int argc, char* argv[]) // SETUP: Chainstate const ChainstateManager::Options chainman_opts{ .chainparams = chainparams, + .datadir = gArgs.GetDataDirNet(), .adjusted_time_callback = NodeClock::now, }; ChainstateManager chainman{chainman_opts}; diff --git a/src/blockencodings.h b/src/blockencodings.h index 7207ff1ae2..afdfa426f1 100644 --- a/src/blockencodings.h +++ b/src/blockencodings.h @@ -135,7 +135,7 @@ protected: public: CBlockHeader header; - // Can be overriden for testing + // Can be overridden for testing using CheckBlockFn = std::function<bool(const CBlock&, BlockValidationState&, const Consensus::Params&, bool, bool)>; CheckBlockFn m_check_block_mock{nullptr}; diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp index 6efaf2ec19..0c6debfa80 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -127,40 +127,40 @@ static leveldb::Options GetOptions(size_t nCacheSize) return options; } -CDBWrapper::CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory, bool fWipe, bool obfuscate) - : m_name{fs::PathToString(path.stem())}, m_path{path}, m_is_memory{fMemory} +CDBWrapper::CDBWrapper(const DBParams& params) + : m_name{fs::PathToString(params.path.stem())}, m_path{params.path}, m_is_memory{params.memory_only} { penv = nullptr; readoptions.verify_checksums = true; iteroptions.verify_checksums = true; iteroptions.fill_cache = false; syncoptions.sync = true; - options = GetOptions(nCacheSize); + options = GetOptions(params.cache_bytes); options.create_if_missing = true; - if (fMemory) { + if (params.memory_only) { penv = leveldb::NewMemEnv(leveldb::Env::Default()); options.env = penv; } else { - if (fWipe) { - LogPrintf("Wiping LevelDB in %s\n", fs::PathToString(path)); - leveldb::Status result = leveldb::DestroyDB(fs::PathToString(path), options); + if (params.wipe_data) { + LogPrintf("Wiping LevelDB in %s\n", fs::PathToString(params.path)); + leveldb::Status result = leveldb::DestroyDB(fs::PathToString(params.path), options); dbwrapper_private::HandleError(result); } - TryCreateDirectories(path); - LogPrintf("Opening LevelDB in %s\n", fs::PathToString(path)); + TryCreateDirectories(params.path); + LogPrintf("Opening LevelDB in %s\n", fs::PathToString(params.path)); } // PathToString() return value is safe to pass to leveldb open function, // because on POSIX leveldb passes the byte string directly to ::open(), and // on Windows it converts from UTF-8 to UTF-16 before calling ::CreateFileW // (see env_posix.cc and env_windows.cc). - leveldb::Status status = leveldb::DB::Open(options, fs::PathToString(path), &pdb); + leveldb::Status status = leveldb::DB::Open(options, fs::PathToString(params.path), &pdb); dbwrapper_private::HandleError(status); LogPrintf("Opened LevelDB successfully\n"); - if (gArgs.GetBoolArg("-forcecompactdb", false)) { - LogPrintf("Starting database compaction of %s\n", fs::PathToString(path)); + if (params.options.force_compact) { + LogPrintf("Starting database compaction of %s\n", fs::PathToString(params.path)); pdb->CompactRange(nullptr, nullptr); - LogPrintf("Finished database compaction of %s\n", fs::PathToString(path)); + LogPrintf("Finished database compaction of %s\n", fs::PathToString(params.path)); } // The base-case obfuscation key, which is a noop. @@ -168,7 +168,7 @@ CDBWrapper::CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory, bo bool key_exists = Read(OBFUSCATE_KEY_KEY, obfuscate_key); - if (!key_exists && obfuscate && IsEmpty()) { + if (!key_exists && params.obfuscate && IsEmpty()) { // Initialize non-degenerate obfuscation if it won't upset // existing, non-obfuscated data. std::vector<unsigned char> new_key = CreateObfuscateKey(); @@ -177,10 +177,10 @@ CDBWrapper::CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory, bo Write(OBFUSCATE_KEY_KEY, new_key); obfuscate_key = new_key; - LogPrintf("Wrote new obfuscate key for %s: %s\n", fs::PathToString(path), HexStr(obfuscate_key)); + LogPrintf("Wrote new obfuscate key for %s: %s\n", fs::PathToString(params.path), HexStr(obfuscate_key)); } - LogPrintf("Using obfuscation key for %s: %s\n", fs::PathToString(path), HexStr(obfuscate_key)); + LogPrintf("Using obfuscation key for %s: %s\n", fs::PathToString(params.path), HexStr(obfuscate_key)); } CDBWrapper::~CDBWrapper() diff --git a/src/dbwrapper.h b/src/dbwrapper.h index b389d039fb..578d9880ac 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -31,6 +31,29 @@ class Env; static const size_t DBWRAPPER_PREALLOC_KEY_SIZE = 64; static const size_t DBWRAPPER_PREALLOC_VALUE_SIZE = 1024; +//! User-controlled performance and debug options. +struct DBOptions { + //! Compact database on startup. + bool force_compact = false; +}; + +//! Application-specific storage settings. +struct DBParams { + //! Location in the filesystem where leveldb data will be stored. + fs::path path; + //! Configures various leveldb cache settings. + size_t cache_bytes; + //! If true, use leveldb's memory environment. + bool memory_only = false; + //! If true, remove all existing data. + bool wipe_data = false; + //! If true, store data obfuscated via simple XOR. If false, XOR with a + //! zero'd byte array. + bool obfuscate = false; + //! Passed-through options. + DBOptions options{}; +}; + class dbwrapper_error : public std::runtime_error { public: @@ -230,15 +253,7 @@ private: bool m_is_memory; public: - /** - * @param[in] path Location in the filesystem where leveldb data will be stored. - * @param[in] nCacheSize Configures various leveldb cache settings. - * @param[in] fMemory If true, use leveldb's memory environment. - * @param[in] fWipe If true, remove all existing data. - * @param[in] obfuscate If true, store data obfuscated via simple XOR. If false, XOR - * with a zero'd byte array. - */ - CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory = false, bool fWipe = false, bool obfuscate = false); + CDBWrapper(const DBParams& params); ~CDBWrapper(); CDBWrapper(const CDBWrapper&) = delete; diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 33a75df68e..86166a5ca4 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -160,7 +160,7 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) JSONRPCRequest jreq; jreq.context = context; - jreq.peerAddr = req->GetPeer().ToString(); + jreq.peerAddr = req->GetPeer().ToStringAddrPort(); if (!RPCAuthorized(authHeader.second, jreq.authUser)) { LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", jreq.peerAddr); diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 720f5c9353..4e4f27f1be 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -222,7 +222,7 @@ static void http_request_cb(struct evhttp_request* req, void* arg) // Early address-based allow check if (!ClientAllowed(hreq->GetPeer())) { LogPrint(BCLog::HTTP, "HTTP request from %s rejected: Client network is not allowed RPC access\n", - hreq->GetPeer().ToString()); + hreq->GetPeer().ToStringAddrPort()); hreq->WriteReply(HTTP_FORBIDDEN); return; } @@ -230,13 +230,13 @@ static void http_request_cb(struct evhttp_request* req, void* arg) // Early reject unknown HTTP methods if (hreq->GetRequestMethod() == HTTPRequest::UNKNOWN) { LogPrint(BCLog::HTTP, "HTTP request from %s rejected: Unknown HTTP request method\n", - hreq->GetPeer().ToString()); + hreq->GetPeer().ToStringAddrPort()); hreq->WriteReply(HTTP_BAD_METHOD); return; } LogPrint(BCLog::HTTP, "Received a %s request for %s from %s\n", - RequestMethodString(hreq->GetRequestMethod()), SanitizeString(hreq->GetURI(), SAFE_CHARS_URI).substr(0, 100), hreq->GetPeer().ToString()); + RequestMethodString(hreq->GetRequestMethod()), SanitizeString(hreq->GetURI(), SAFE_CHARS_URI).substr(0, 100), hreq->GetPeer().ToStringAddrPort()); // Find registered handler for prefix std::string strURI = hreq->GetURI(); diff --git a/src/i2p.cpp b/src/i2p.cpp index 8e98440747..a3bfc23a65 100644 --- a/src/i2p.cpp +++ b/src/i2p.cpp @@ -206,7 +206,7 @@ bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error) } const Reply& lookup_reply = - SendRequestAndGetReply(*sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringIP())); + SendRequestAndGetReply(*sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringAddr())); const std::string& dest = lookup_reply.Get("VALUE"); @@ -233,7 +233,7 @@ bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error) throw std::runtime_error(strprintf("\"%s\"", connect_reply.full)); } catch (const std::runtime_error& e) { - Log("Error connecting to %s: %s", to.ToString(), e.what()); + Log("Error connecting to %s: %s", to.ToStringAddrPort(), e.what()); CheckControlSock(); return false; } @@ -302,7 +302,7 @@ std::unique_ptr<Sock> Session::Hello() const } if (!ConnectSocketDirectly(m_control_host, *sock, nConnectTimeout, true)) { - throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString())); + throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToStringAddrPort())); } SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1"); @@ -371,7 +371,7 @@ void Session::CreateIfNotCreatedAlready() const auto session_type = m_transient ? "transient" : "persistent"; const auto session_id = GetRandHash().GetHex().substr(0, 10); // full is overkill, too verbose in the logs - Log("Creating %s SAM session %s with %s", session_type, session_id, m_control_host.ToString()); + Log("Creating %s SAM session %s with %s", session_type, session_id, m_control_host.ToStringAddrPort()); auto sock = Hello(); @@ -380,7 +380,9 @@ void Session::CreateIfNotCreatedAlready() // in the reply in DESTINATION=. const Reply& reply = SendRequestAndGetReply( *sock, - strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT SIGNATURE_TYPE=7", session_id)); + strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT SIGNATURE_TYPE=7 " + "inbound.quantity=1 outbound.quantity=1", + session_id)); m_private_key = DecodeI2PBase64(reply.Get("DESTINATION")); } else { @@ -396,7 +398,8 @@ void Session::CreateIfNotCreatedAlready() const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key)); SendRequestAndGetReply(*sock, - strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s", + strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s " + "inbound.quantity=3 outbound.quantity=3", session_id, private_key_b64)); } @@ -408,7 +411,7 @@ void Session::CreateIfNotCreatedAlready() Log("%s SAM session %s created, my address=%s", Capitalize(session_type), m_session_id, - m_my_addr.ToString()); + m_my_addr.ToStringAddrPort()); } std::unique_ptr<Sock> Session::StreamAccept() diff --git a/src/index/base.cpp b/src/index/base.cpp index 1d5c0dbe24..6f2ce2efe4 100644 --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -8,6 +8,7 @@ #include <kernel/chain.h> #include <node/blockstorage.h> #include <node/context.h> +#include <node/database_args.h> #include <node/interface_ui.h> #include <shutdown.h> #include <tinyformat.h> @@ -48,7 +49,13 @@ CBlockLocator GetLocator(interfaces::Chain& chain, const uint256& block_hash) } BaseIndex::DB::DB(const fs::path& path, size_t n_cache_size, bool f_memory, bool f_wipe, bool f_obfuscate) : - CDBWrapper(path, n_cache_size, f_memory, f_wipe, f_obfuscate) + CDBWrapper{DBParams{ + .path = path, + .cache_bytes = n_cache_size, + .memory_only = f_memory, + .wipe_data = f_wipe, + .obfuscate = f_obfuscate, + .options = [] { DBOptions options; node::ReadDatabaseArgs(gArgs, options); return options; }()}} {} bool BaseIndex::DB::ReadBestBlock(CBlockLocator& locator) const diff --git a/src/init.cpp b/src/init.cpp index 73ae36e4f7..4e06d44cb0 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1046,6 +1046,7 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb { ChainstateManager::Options chainman_opts_dummy{ .chainparams = chainparams, + .datadir = args.GetDataDirNet(), }; if (const auto error{ApplyArgsManOptions(args, chainman_opts_dummy)}) { return InitError(*error); @@ -1444,6 +1445,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) bool fReindexChainState = args.GetBoolArg("-reindex-chainstate", false); ChainstateManager::Options chainman_opts{ .chainparams = chainparams, + .datadir = args.GetDataDirNet(), .adjusted_time_callback = GetAdjustedTime, }; Assert(!ApplyArgsManOptions(args, chainman_opts)); // no error can happen, already checked in AppInitParameterInteraction @@ -1493,6 +1495,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) options.prune = chainman.m_blockman.IsPruneMode(); options.check_blocks = args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS); options.check_level = args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL); + options.require_full_verification = args.IsArgSet("-checkblocks") || args.IsArgSet("-checklevel"); options.check_interrupt = ShutdownRequested; options.coins_error_cb = [] { uiInterface.ThreadSafeMessageBox( @@ -1524,7 +1527,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } } - if (status == node::ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB) { + if (status == node::ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB || status == node::ChainstateLoadStatus::FAILURE_INSUFFICIENT_DBCACHE) { return InitError(error); } @@ -1798,7 +1801,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) if (connOptions.onion_binds.size() > 1) { InitWarning(strprintf(_("More than one onion bind address is provided. Using %s " "for the automatically created Tor onion service."), - onion_service_target.ToStringIPPort())); + onion_service_target.ToStringAddrPort())); } StartTorControl(onion_service_target); } @@ -1825,6 +1828,13 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) if (connect.size() != 1 || connect[0] != "0") { connOptions.m_specified_outgoing = connect; } + if (!connOptions.m_specified_outgoing.empty() && !connOptions.vSeedNodes.empty()) { + LogPrintf("-seednode is ignored when -connect is used\n"); + } + + if (args.IsArgSet("-dnsseed") && args.GetBoolArg("-dnsseed", DEFAULT_DNSSEED) && args.IsArgSet("-proxy")) { + LogPrintf("-dnsseed is ignored when -connect is used and -proxy is specified\n"); + } } const std::string& i2psam_arg = args.GetArg("-i2psam", ""); diff --git a/src/kernel/chainstatemanager_opts.h b/src/kernel/chainstatemanager_opts.h index 226bb6031e..2395f60164 100644 --- a/src/kernel/chainstatemanager_opts.h +++ b/src/kernel/chainstatemanager_opts.h @@ -6,6 +6,8 @@ #define BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H #include <arith_uint256.h> +#include <dbwrapper.h> +#include <txdb.h> #include <uint256.h> #include <util/time.h> @@ -27,6 +29,7 @@ namespace kernel { */ struct ChainstateManagerOpts { const CChainParams& chainparams; + fs::path datadir; const std::function<NodeClock::time_point()> adjusted_time_callback{nullptr}; std::optional<bool> check_block_index{}; bool checkpoints_enabled{DEFAULT_CHECKPOINTS_ENABLED}; @@ -36,6 +39,9 @@ struct ChainstateManagerOpts { std::optional<uint256> assumed_valid_block{}; //! If the tip is older than this, the node is considered to be in initial block download. std::chrono::seconds max_tip_age{DEFAULT_MAX_TIP_AGE}; + DBOptions block_tree_db{}; + DBOptions coins_db{}; + CoinsViewOptions coins_view{}; }; } // namespace kernel diff --git a/src/kernel/cs_main.cpp b/src/kernel/cs_main.cpp index c3a08c9695..d27cb7caf3 100644 --- a/src/kernel/cs_main.cpp +++ b/src/kernel/cs_main.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <kernel/cs_main.h> #include <sync.h> RecursiveMutex cs_main; diff --git a/src/mapport.cpp b/src/mapport.cpp index 84b889f22d..994fd12cf5 100644 --- a/src/mapport.cpp +++ b/src/mapport.cpp @@ -104,7 +104,7 @@ static bool NatpmpMapping(natpmp_t* natpmp, const struct in_addr& external_ipv4_ AddLocal(external, LOCAL_MAPPED); external_ip_discovered = true; } - LogPrintf("natpmp: Port mapping successful. External address = %s\n", external.ToString()); + LogPrintf("natpmp: Port mapping successful. External address = %s\n", external.ToStringAddrPort()); return true; } else { LogPrintf("natpmp: Port mapping failed.\n"); @@ -177,7 +177,7 @@ static bool ProcessUpnp() if (externalIPAddress[0]) { CNetAddr resolved; if (LookupHost(externalIPAddress, resolved, false)) { - LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToString()); + LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToStringAddr()); AddLocal(resolved, LOCAL_MAPPED); } } else { diff --git a/src/net.cpp b/src/net.cpp index 4f4e443976..4e4f2f78be 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -196,7 +196,7 @@ static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t> &vSeedsIn) s >> endpoint; CAddress addr{endpoint, GetDesirableServiceFlags(NODE_NONE)}; addr.nTime = rng.rand_uniform_delay(Now<NodeSeconds>() - one_week, -one_week); - LogPrint(BCLog::NET, "Added hardcoded seed: %s\n", addr.ToString()); + LogPrint(BCLog::NET, "Added hardcoded seed: %s\n", addr.ToStringAddrPort()); vSeedsOut.push_back(addr); } return vSeedsOut; @@ -258,7 +258,7 @@ std::optional<CService> GetLocalAddrForPeer(CNode& node) } if (addrLocal.IsRoutable() || gArgs.GetBoolArg("-addrmantest", false)) { - LogPrint(BCLog::NET, "Advertising address %s to peer=%d\n", addrLocal.ToString(), node.GetId()); + LogPrint(BCLog::NET, "Advertising address %s to peer=%d\n", addrLocal.ToStringAddrPort(), node.GetId()); return addrLocal; } // Address is unroutable. Don't advertise. @@ -295,7 +295,7 @@ bool AddLocal(const CService& addr_, int nScore) if (!IsReachable(addr)) return false; - LogPrintf("AddLocal(%s,%i)\n", addr.ToString(), nScore); + LogPrintf("AddLocal(%s,%i)\n", addr.ToStringAddrPort(), nScore); { LOCK(g_maplocalhost_mutex); @@ -318,7 +318,7 @@ bool AddLocal(const CNetAddr &addr, int nScore) void RemoveLocal(const CService& addr) { LOCK(g_maplocalhost_mutex); - LogPrintf("RemoveLocal(%s)\n", addr.ToString()); + LogPrintf("RemoveLocal(%s)\n", addr.ToStringAddrPort()); mapLocalHost.erase(addr); } @@ -405,7 +405,7 @@ CNode* CConnman::FindNode(const CService& addr) bool CConnman::AlreadyConnectedToAddress(const CAddress& addr) { - return FindNode(static_cast<CNetAddr>(addr)) || FindNode(addr.ToStringIPPort()); + return FindNode(static_cast<CNetAddr>(addr)) || FindNode(addr.ToStringAddrPort()); } bool CConnman::CheckIncomingNonce(uint64_t nonce) @@ -436,6 +436,7 @@ static CAddress GetBindAddress(const Sock& sock) CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type) { + AssertLockNotHeld(m_unused_i2p_sessions_mutex); assert(conn_type != ConnectionType::INBOUND); if (pszDest == nullptr) { @@ -452,7 +453,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo } LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "trying connection %s lastseen=%.1fhrs\n", - pszDest ? pszDest : addrConnect.ToString(), + pszDest ? pszDest : addrConnect.ToStringAddrPort(), Ticks<HoursDouble>(pszDest ? 0h : Now<NodeSeconds>() - addrConnect.nTime)); // Resolve @@ -464,7 +465,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo const CService rnd{resolved[GetRand(resolved.size())]}; addrConnect = CAddress{MaybeFlipIPv6toCJDNS(rnd), NODE_NONE}; if (!addrConnect.IsValid()) { - LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToString(), pszDest); + LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToStringAddrPort(), pszDest); return nullptr; } // It is possible that we already have a connection to the IP/port pszDest resolved to. @@ -496,8 +497,23 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo if (m_i2p_sam_session) { connected = m_i2p_sam_session->Connect(addrConnect, conn, proxyConnectionFailed); } else { - i2p_transient_session = std::make_unique<i2p::sam::Session>(proxy.proxy, &interruptNet); + { + LOCK(m_unused_i2p_sessions_mutex); + if (m_unused_i2p_sessions.empty()) { + i2p_transient_session = + std::make_unique<i2p::sam::Session>(proxy.proxy, &interruptNet); + } else { + i2p_transient_session.swap(m_unused_i2p_sessions.front()); + m_unused_i2p_sessions.pop(); + } + } connected = i2p_transient_session->Connect(addrConnect, conn, proxyConnectionFailed); + if (!connected) { + LOCK(m_unused_i2p_sessions_mutex); + if (m_unused_i2p_sessions.size() < MAX_UNUSED_I2P_SESSIONS_SIZE) { + m_unused_i2p_sessions.emplace(i2p_transient_session.release()); + } + } } if (connected) { @@ -509,7 +525,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo if (!sock) { return nullptr; } - connected = ConnectThroughProxy(proxy, addrConnect.ToStringIP(), addrConnect.GetPort(), + connected = ConnectThroughProxy(proxy, addrConnect.ToStringAddr(), addrConnect.GetPort(), *sock, nConnectTimeout, proxyConnectionFailed); } else { // no proxy needed (none set for target network) @@ -593,7 +609,7 @@ void CNode::SetAddrLocal(const CService& addrLocalIn) { AssertLockNotHeld(m_addr_local_mutex); LOCK(m_addr_local_mutex); if (addrLocal.IsValid()) { - error("Addr local already set for node: %i. Refusing to change from %s to %s", id, addrLocal.ToString(), addrLocalIn.ToString()); + error("Addr local already set for node: %i. Refusing to change from %s to %s", id, addrLocal.ToStringAddrPort(), addrLocalIn.ToStringAddrPort()); } else { addrLocal = addrLocalIn; } @@ -644,7 +660,7 @@ void CNode::CopyStats(CNodeStats& stats) // Leave string empty if addrLocal invalid (not filled in yet) CService addrLocalUnlocked = GetAddrLocal(); - stats.addrLocal = addrLocalUnlocked.IsValid() ? addrLocalUnlocked.ToString() : ""; + stats.addrLocal = addrLocalUnlocked.IsValid() ? addrLocalUnlocked.ToStringAddrPort() : ""; X(m_conn_type); } @@ -973,12 +989,12 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, } if (!fNetworkActive) { - LogPrint(BCLog::NET, "connection from %s dropped: not accepting new connections\n", addr.ToString()); + LogPrint(BCLog::NET, "connection from %s dropped: not accepting new connections\n", addr.ToStringAddrPort()); return; } if (!sock->IsSelectable()) { - LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToString()); + LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToStringAddrPort()); return; } @@ -987,14 +1003,14 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, const int on{1}; if (sock->SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == SOCKET_ERROR) { LogPrint(BCLog::NET, "connection from %s: unable to set TCP_NODELAY, continuing anyway\n", - addr.ToString()); + addr.ToStringAddrPort()); } // Don't accept connections from banned peers. bool banned = m_banman && m_banman->IsBanned(addr); if (!NetPermissions::HasFlag(permission_flags, NetPermissionFlags::NoBan) && banned) { - LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToString()); + LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToStringAddrPort()); return; } @@ -1002,7 +1018,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, bool discouraged = m_banman && m_banman->IsDiscouraged(addr); if (!NetPermissions::HasFlag(permission_flags, NetPermissionFlags::NoBan) && nInbound + 1 >= nMaxInbound && discouraged) { - LogPrint(BCLog::NET, "connection from %s dropped (discouraged)\n", addr.ToString()); + LogPrint(BCLog::NET, "connection from %s dropped (discouraged)\n", addr.ToStringAddrPort()); return; } @@ -1040,7 +1056,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, pnode->AddRef(); m_msgproc->InitializeNode(*pnode, nodeServices); - LogPrint(BCLog::NET, "connection from %s accepted\n", addr.ToString()); + LogPrint(BCLog::NET, "connection from %s accepted\n", addr.ToStringAddrPort()); { LOCK(m_nodes_mutex); @@ -1053,6 +1069,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, bool CConnman::AddConnection(const std::string& address, ConnectionType conn_type) { + AssertLockNotHeld(m_unused_i2p_sessions_mutex); std::optional<int> max_connections; switch (conn_type) { case ConnectionType::INBOUND: @@ -1471,6 +1488,8 @@ void CConnman::ThreadDNSAddressSeed() } LogPrintf("Loading addresses from DNS seed %s\n", seed); + // If -proxy is in use, we make an ADDR_FETCH connection to the DNS resolved peer address + // for the base dns seed domain in chainparams if (HaveNameProxy()) { AddAddrFetch(seed); } else { @@ -1492,8 +1511,9 @@ void CConnman::ThreadDNSAddressSeed() } addrman.Add(vAdd, resolveSource); } else { - // We now avoid directly using results from DNS Seeds which do not support service bit filtering, - // instead using them as a addrfetch to get nodes with our desired service bits. + // If the seed does not support a subdomain with our desired service bits, + // we make an ADDR_FETCH connection to the DNS resolved peer address for the + // base dns seed domain in chainparams AddAddrFetch(seed); } } @@ -1514,6 +1534,7 @@ void CConnman::DumpAddresses() void CConnman::ProcessAddrFetch() { + AssertLockNotHeld(m_unused_i2p_sessions_mutex); std::string strDest; { LOCK(m_addr_fetches_mutex); @@ -1595,6 +1616,7 @@ std::unordered_set<Network> CConnman::GetReachableEmptyNetworks() const void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) { + AssertLockNotHeld(m_unused_i2p_sessions_mutex); SetSyscallSandboxPolicy(SyscallSandboxPolicy::NET_OPEN_CONNECTION); FastRandomContext rng; // Connect to specific addresses @@ -1602,7 +1624,6 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) { for (int64_t nLoop = 0;; nLoop++) { - ProcessAddrFetch(); for (const std::string& strAddr : connect) { CAddress addr(CService(), NODE_NONE); @@ -1671,7 +1692,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // Therefore, we do not add them to addrman in the first place. // In case previously unreachable networks become reachable // (e.g. in case of -onlynet changes by the user), fixed seeds will - // be loaded only for networks for which we have no addressses. + // be loaded only for networks for which we have no addresses. seed_addrs.erase(std::remove_if(seed_addrs.begin(), seed_addrs.end(), [&fixed_seed_networks](const CAddress& addr) { return fixed_seed_networks.count(addr.GetNetwork()) == 0; }), seed_addrs.end()); @@ -1788,7 +1809,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) !HasAllDesirableServiceFlags(addr.nServices) || setConnected.count(m_netgroupman.GetGroup(addr))) continue; addrConnect = addr; - LogPrint(BCLog::NET, "Trying to make an anchor connection to %s\n", addrConnect.ToString()); + LogPrint(BCLog::NET, "Trying to make an anchor connection to %s\n", addrConnect.ToStringAddrPort()); break; } @@ -1868,7 +1889,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) if (!interruptNet.sleep_for(rng.rand_uniform_duration<CThreadInterrupt::Clock>(FEELER_SLEEP_WINDOW))) { return; } - LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToString()); + LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToStringAddrPort()); } OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, conn_type); @@ -1945,6 +1966,7 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const void CConnman::ThreadOpenAddedConnections() { + AssertLockNotHeld(m_unused_i2p_sessions_mutex); SetSyscallSandboxPolicy(SyscallSandboxPolicy::NET_ADD_CONNECTION); while (true) { @@ -1974,6 +1996,7 @@ void CConnman::ThreadOpenAddedConnections() // if successful, this moves the passed grant to the constructed node void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, ConnectionType conn_type) { + AssertLockNotHeld(m_unused_i2p_sessions_mutex); assert(conn_type != ConnectionType::INBOUND); // @@ -2097,7 +2120,7 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError, socklen_t len = sizeof(sockaddr); if (!addrBind.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { - strError = strprintf(Untranslated("Bind address family for %s not supported"), addrBind.ToString()); + strError = strprintf(Untranslated("Bind address family for %s not supported"), addrBind.ToStringAddrPort()); LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); return false; } @@ -2137,13 +2160,13 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError, 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); + strError = strprintf(_("Unable to bind to %s on this computer. %s is probably already running."), addrBind.ToStringAddrPort(), PACKAGE_NAME); else - strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %s)"), addrBind.ToString(), NetworkErrorString(nErr)); + strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %s)"), addrBind.ToStringAddrPort(), NetworkErrorString(nErr)); LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); return false; } - LogPrintf("Bound to %s\n", addrBind.ToString()); + LogPrintf("Bound to %s\n", addrBind.ToStringAddrPort()); // Listen for incoming connections if (sock->Listen(SOMAXCONN) == SOCKET_ERROR) @@ -2173,7 +2196,7 @@ void Discover() for (const CNetAddr &addr : vaddr) { if (AddLocal(addr, LOCAL_IF)) - LogPrintf("%s: %s - %s\n", __func__, pszHostName, addr.ToString()); + LogPrintf("%s: %s - %s\n", __func__, pszHostName, addr.ToStringAddr()); } } } @@ -2193,14 +2216,14 @@ void Discover() struct sockaddr_in* s4 = (struct sockaddr_in*)(ifa->ifa_addr); CNetAddr addr(s4->sin_addr); if (AddLocal(addr, LOCAL_IF)) - LogPrintf("%s: IPv4 %s: %s\n", __func__, ifa->ifa_name, addr.ToString()); + LogPrintf("%s: IPv4 %s: %s\n", __func__, ifa->ifa_name, addr.ToStringAddr()); } else if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6* s6 = (struct sockaddr_in6*)(ifa->ifa_addr); CNetAddr addr(s6->sin6_addr); if (AddLocal(addr, LOCAL_IF)) - LogPrintf("%s: IPv6 %s: %s\n", __func__, ifa->ifa_name, addr.ToString()); + LogPrintf("%s: IPv6 %s: %s\n", __func__, ifa->ifa_name, addr.ToStringAddr()); } } freeifaddrs(myaddrs); @@ -2759,7 +2782,7 @@ CNode::CNode(NodeId idIn, m_connected{GetTime<std::chrono::seconds>()}, addr{addrIn}, addrBind{addrBindIn}, - m_addr_name{addrNameIn.empty() ? addr.ToStringIPPort() : addrNameIn}, + m_addr_name{addrNameIn.empty() ? addr.ToStringAddrPort() : addrNameIn}, m_inbound_onion{inbound_onion}, m_prefer_evict{node_opts.prefer_evict}, nKeyedNetGroup{nKeyedNetGroupIn}, @@ -2865,7 +2888,7 @@ void CaptureMessageToFile(const CAddress& addr, auto now = GetTime<std::chrono::microseconds>(); // Windows folder names cannot include a colon - std::string clean_addr = addr.ToString(); + std::string clean_addr = addr.ToStringAddrPort(); std::replace(clean_addr.begin(), clean_addr.end(), ':', '_'); fs::path base_path = gArgs.GetDataDirNet() / "message_capture" / fs::u8path(clean_addr); @@ -38,6 +38,7 @@ #include <map> #include <memory> #include <optional> +#include <queue> #include <thread> #include <unordered_set> #include <vector> @@ -744,7 +745,7 @@ public: bool GetNetworkActive() const { return fNetworkActive; }; bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; }; void SetNetworkActive(bool active); - void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant* grantOutbound, const char* strDest, ConnectionType conn_type); + void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant* grantOutbound, const char* strDest, ConnectionType conn_type) EXCLUSIVE_LOCKS_REQUIRED(!m_unused_i2p_sessions_mutex); bool CheckIncomingNonce(uint64_t nonce); bool ForNode(NodeId id, std::function<bool(CNode* pnode)> func); @@ -820,7 +821,7 @@ public: * - Max total outbound connection capacity filled * - Max connection capacity for type is filled */ - bool AddConnection(const std::string& address, ConnectionType conn_type); + bool AddConnection(const std::string& address, ConnectionType conn_type) EXCLUSIVE_LOCKS_REQUIRED(!m_unused_i2p_sessions_mutex); size_t GetNodeCount(ConnectionDirection) const; void GetNodeStats(std::vector<CNodeStats>& vstats) const; @@ -886,10 +887,10 @@ private: bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions); bool InitBinds(const Options& options); - void ThreadOpenAddedConnections() EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); + void ThreadOpenAddedConnections() EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex, !m_unused_i2p_sessions_mutex); void AddAddrFetch(const std::string& strDest) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex); - void ProcessAddrFetch() EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex); - void ThreadOpenConnections(std::vector<std::string> connect) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_added_nodes_mutex, !m_nodes_mutex); + void ProcessAddrFetch() EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_unused_i2p_sessions_mutex); + void ThreadOpenConnections(std::vector<std::string> connect) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_added_nodes_mutex, !m_nodes_mutex, !m_unused_i2p_sessions_mutex); void ThreadMessageHandler() EXCLUSIVE_LOCKS_REQUIRED(!mutexMsgProc); void ThreadI2PAcceptIncoming(); void AcceptConnection(const ListenSocket& hListenSocket); @@ -956,7 +957,7 @@ private: bool AlreadyConnectedToAddress(const CAddress& addr); bool AttemptToEvictConnection(); - CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type); + CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type) EXCLUSIVE_LOCKS_REQUIRED(!m_unused_i2p_sessions_mutex); void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const; void DeleteNode(CNode* pnode); @@ -1134,6 +1135,26 @@ private: std::vector<CService> m_onion_binds; /** + * Mutex protecting m_i2p_sam_sessions. + */ + Mutex m_unused_i2p_sessions_mutex; + + /** + * A pool of created I2P SAM transient sessions that should be used instead + * of creating new ones in order to reduce the load on the I2P network. + * Creating a session in I2P is not cheap, thus if this is not empty, then + * pick an entry from it instead of creating a new session. If connecting to + * a host fails, then the created session is put to this pool for reuse. + */ + std::queue<std::unique_ptr<i2p::sam::Session>> m_unused_i2p_sessions GUARDED_BY(m_unused_i2p_sessions_mutex); + + /** + * Cap on the size of `m_unused_i2p_sessions`, to ensure it does not + * unexpectedly use too much memory. + */ + static constexpr size_t MAX_UNUSED_I2P_SESSIONS_SIZE{10}; + + /** * RAII helper to atomically create a copy of `m_nodes` and add a reference * to each of the nodes. The nodes are released when this object is destroyed. */ diff --git a/src/net_processing.cpp b/src/net_processing.cpp index a659300a0d..25c65c7090 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1397,7 +1397,7 @@ void PeerManagerImpl::PushNodeVersion(CNode& pnode, const Peer& peer) nonce, strSubVersion, nNodeStartingHeight, tx_relay)); if (fLogIPs) { - LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, them=%s, txrelay=%d, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addr_you.ToString(), tx_relay, nodeid); + LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, them=%s, txrelay=%d, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addr_you.ToStringAddrPort(), tx_relay, nodeid); } else { LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, txrelay=%d, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, tx_relay, nodeid); } @@ -3231,7 +3231,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // Disconnect if we connected to ourself if (pfrom.IsInboundConn() && !m_connman.CheckIncomingNonce(nNonce)) { - LogPrintf("connected to self at %s, disconnecting\n", pfrom.addr.ToString()); + LogPrintf("connected to self at %s, disconnecting\n", pfrom.addr.ToStringAddrPort()); pfrom.fDisconnect = true; return; } @@ -3358,11 +3358,11 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, std::string remoteAddr; if (fLogIPs) - remoteAddr = ", peeraddr=" + pfrom.addr.ToString(); + remoteAddr = ", peeraddr=" + pfrom.addr.ToStringAddrPort(); LogPrint(BCLog::NET, "receive version message: %s: version %d, blocks=%d, us=%s, txrelay=%d, peer=%d%s\n", cleanSubVer, pfrom.nVersion, - peer->m_starting_height, addrMe.ToString(), fRelay, pfrom.GetId(), + peer->m_starting_height, addrMe.ToStringAddrPort(), fRelay, pfrom.GetId(), remoteAddr); int64_t nTimeOffset = nTime - GetTime(); @@ -3405,7 +3405,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (!pfrom.IsInboundConn()) { LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s (%s)\n", pfrom.nVersion.load(), peer->m_starting_height, - pfrom.GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom.addr.ToString()) : ""), + pfrom.GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom.addr.ToStringAddrPort()) : ""), pfrom.ConnectionTypeAsString()); } diff --git a/src/netaddress.cpp b/src/netaddress.cpp index 782b692d30..85ae8fab36 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -599,7 +599,7 @@ std::string OnionToString(Span<const uint8_t> addr) return EncodeBase32(address) + ".onion"; } -std::string CNetAddr::ToStringIP() const +std::string CNetAddr::ToStringAddr() const { switch (m_net) { case NET_IPV4: @@ -622,11 +622,6 @@ std::string CNetAddr::ToStringIP() const assert(false); } -std::string CNetAddr::ToString() const -{ - return ToStringIP(); -} - bool operator==(const CNetAddr& a, const CNetAddr& b) { return a.m_net == b.m_net && a.m_addr == b.m_addr; @@ -916,25 +911,17 @@ std::vector<unsigned char> CService::GetKey() const return key; } -std::string CService::ToStringPort() const +std::string CService::ToStringAddrPort() const { - return strprintf("%u", port); -} + const auto port_str = strprintf("%u", port); -std::string CService::ToStringIPPort() const -{ if (IsIPv4() || IsTor() || IsI2P() || IsInternal()) { - return ToStringIP() + ":" + ToStringPort(); + return ToStringAddr() + ":" + port_str; } else { - return "[" + ToStringIP() + "]:" + ToStringPort(); + return "[" + ToStringAddr() + "]:" + port_str; } } -std::string CService::ToString() const -{ - return ToStringIPPort(); -} - CSubNet::CSubNet(): valid(false) { @@ -1098,7 +1085,7 @@ std::string CSubNet::ToString() const break; } - return network.ToString() + suffix; + return network.ToStringAddr() + suffix; } bool CSubNet::IsValid() const @@ -1106,29 +1093,6 @@ bool CSubNet::IsValid() const return valid; } -bool CSubNet::SanityCheck() const -{ - switch (network.m_net) { - case NET_IPV4: - case NET_IPV6: - break; - case NET_ONION: - case NET_I2P: - case NET_CJDNS: - return true; - case NET_INTERNAL: - case NET_UNROUTABLE: - case NET_MAX: - return false; - } - - for (size_t x = 0; x < network.m_addr.size(); ++x) { - if (network.m_addr[x] & ~netmask[x]) return false; - } - - return true; -} - bool operator==(const CSubNet& a, const CSubNet& b) { return a.valid == b.valid && a.network == b.network && !memcmp(a.netmask, b.netmask, 16); diff --git a/src/netaddress.h b/src/netaddress.h index 7f782674d3..3d15b0b123 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -193,8 +193,7 @@ public: bool IsAddrV1Compatible() const; enum Network GetNetwork() const; - std::string ToString() const; - std::string ToStringIP() const; + std::string ToStringAddr() const; bool GetInAddr(struct in_addr* pipv4Addr) const; Network GetNetClass() const; @@ -476,8 +475,6 @@ protected: /// Is this value valid? (only used to signal parse errors) bool valid; - bool SanityCheck() const; - public: /** * Construct an invalid subnet (empty, `Match()` always returns false). @@ -536,9 +533,7 @@ public: friend bool operator!=(const CService& a, const CService& b) { return !(a == b); } friend bool operator<(const CService& a, const CService& b); std::vector<unsigned char> GetKey() const; - std::string ToString() const; - std::string ToStringPort() const; - std::string ToStringIPPort() const; + std::string ToStringAddrPort() const; CService(const struct in6_addr& ipv6Addr, uint16_t port); explicit CService(const struct sockaddr_in6& addr); diff --git a/src/netbase.cpp b/src/netbase.cpp index fac4b3b5d5..797f1e17f2 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -488,7 +488,7 @@ std::unique_ptr<Sock> CreateSockTCP(const CService& address_family) struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); if (!address_family.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { - LogPrintf("Cannot create socket for %s: unsupported network\n", address_family.ToString()); + LogPrintf("Cannot create socket for %s: unsupported network\n", address_family.ToStringAddrPort()); return nullptr; } @@ -549,11 +549,11 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); if (sock.Get() == INVALID_SOCKET) { - LogPrintf("Cannot connect to %s: invalid socket\n", addrConnect.ToString()); + LogPrintf("Cannot connect to %s: invalid socket\n", addrConnect.ToStringAddrPort()); return false; } if (!addrConnect.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { - LogPrintf("Cannot connect to %s: unsupported network\n", addrConnect.ToString()); + LogPrintf("Cannot connect to %s: unsupported network\n", addrConnect.ToStringAddrPort()); return false; } @@ -570,11 +570,11 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT Sock::Event occurred; if (!sock.Wait(std::chrono::milliseconds{nTimeout}, requested, &occurred)) { LogPrintf("wait for connect to %s failed: %s\n", - addrConnect.ToString(), + addrConnect.ToStringAddrPort(), NetworkErrorString(WSAGetLastError())); return false; } else if (occurred == 0) { - LogPrint(BCLog::NET, "connection attempt to %s timed out\n", addrConnect.ToString()); + LogPrint(BCLog::NET, "connection attempt to %s timed out\n", addrConnect.ToStringAddrPort()); return false; } @@ -586,13 +586,13 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT socklen_t sockerr_len = sizeof(sockerr); if (sock.GetSockOpt(SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&sockerr, &sockerr_len) == SOCKET_ERROR) { - LogPrintf("getsockopt() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); + LogPrintf("getsockopt() for %s failed: %s\n", addrConnect.ToStringAddrPort(), NetworkErrorString(WSAGetLastError())); return false; } if (sockerr != 0) { LogConnectFailure(manual_connection, "connect() to %s failed after wait: %s", - addrConnect.ToString(), + addrConnect.ToStringAddrPort(), NetworkErrorString(sockerr)); return false; } @@ -603,7 +603,7 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT else #endif { - LogConnectFailure(manual_connection, "connect() to %s failed: %s", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); + LogConnectFailure(manual_connection, "connect() to %s failed: %s", addrConnect.ToStringAddrPort(), NetworkErrorString(WSAGetLastError())); return false; } } diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index ba1024d22e..41c0ff2118 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -64,7 +64,12 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize // new CBlockTreeDB tries to delete the existing file, which // fails if it's still open from the previous loop. Close it first: pblocktree.reset(); - pblocktree.reset(new CBlockTreeDB(cache_sizes.block_tree_db, options.block_tree_db_in_memory, options.reindex)); + pblocktree = std::make_unique<CBlockTreeDB>(DBParams{ + .path = chainman.m_options.datadir / "blocks" / "index", + .cache_bytes = static_cast<size_t>(cache_sizes.block_tree_db), + .memory_only = options.block_tree_db_in_memory, + .wipe_data = options.reindex, + .options = chainman.m_options.block_tree_db}); if (options.reindex) { pblocktree->WriteReindexing(true); @@ -187,12 +192,23 @@ ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const C "Only rebuild the block database if you are sure that your computer's date and time are correct")}; } - if (!CVerifyDB().VerifyDB( - *chainstate, chainman.GetConsensus(), chainstate->CoinsDB(), - options.check_level, - options.check_blocks)) { + VerifyDBResult result = CVerifyDB().VerifyDB( + *chainstate, chainman.GetConsensus(), chainstate->CoinsDB(), + options.check_level, + options.check_blocks); + switch (result) { + case VerifyDBResult::SUCCESS: + case VerifyDBResult::INTERRUPTED: + case VerifyDBResult::SKIPPED_MISSING_BLOCKS: + break; + case VerifyDBResult::CORRUPTED_BLOCK_DB: return {ChainstateLoadStatus::FAILURE, _("Corrupted block database detected")}; - } + case VerifyDBResult::SKIPPED_L3_CHECKS: + if (options.require_full_verification) { + return {ChainstateLoadStatus::FAILURE_INSUFFICIENT_DBCACHE, _("Insufficient dbcache for block verification")}; + } + break; + } // no default case, so the compiler can warn about missing cases } } diff --git a/src/node/chainstate.h b/src/node/chainstate.h index d3c7656bf2..7838a62d0c 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -25,6 +25,7 @@ struct ChainstateLoadOptions { bool reindex{false}; bool reindex_chainstate{false}; bool prune{false}; + bool require_full_verification{true}; int64_t check_blocks{DEFAULT_CHECKBLOCKS}; int64_t check_level{DEFAULT_CHECKLEVEL}; std::function<bool()> check_interrupt; @@ -35,7 +36,13 @@ struct ChainstateLoadOptions { //! case, and treat other cases as errors. More complex applications may want to //! try reindexing in the generic failure case, and pass an interrupt callback //! and exit cleanly in the interrupted case. -enum class ChainstateLoadStatus { SUCCESS, FAILURE, FAILURE_INCOMPATIBLE_DB, INTERRUPTED }; +enum class ChainstateLoadStatus { + SUCCESS, + FAILURE, + FAILURE_INCOMPATIBLE_DB, + FAILURE_INSUFFICIENT_DBCACHE, + INTERRUPTED, +}; //! Chainstate load status code and optional error string. using ChainstateLoadResult = std::tuple<ChainstateLoadStatus, bilingual_str>; diff --git a/src/node/chainstatemanager_args.cpp b/src/node/chainstatemanager_args.cpp index b0d929626b..9801e6e959 100644 --- a/src/node/chainstatemanager_args.cpp +++ b/src/node/chainstatemanager_args.cpp @@ -5,6 +5,9 @@ #include <node/chainstatemanager_args.h> #include <arith_uint256.h> +#include <kernel/chainstatemanager_opts.h> +#include <node/coins_view_args.h> +#include <node/database_args.h> #include <tinyformat.h> #include <uint256.h> #include <util/strencodings.h> @@ -34,6 +37,10 @@ std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& args, Chains if (auto value{args.GetIntArg("-maxtipage")}) opts.max_tip_age = std::chrono::seconds{*value}; + ReadDatabaseArgs(args, opts.block_tree_db); + ReadDatabaseArgs(args, opts.coins_db); + ReadCoinsViewArgs(args, opts.coins_view); + return std::nullopt; } } // namespace node diff --git a/src/node/coins_view_args.cpp b/src/node/coins_view_args.cpp new file mode 100644 index 0000000000..67c9b8dbac --- /dev/null +++ b/src/node/coins_view_args.cpp @@ -0,0 +1,16 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <node/coins_view_args.h> + +#include <txdb.h> +#include <util/system.h> + +namespace node { +void ReadCoinsViewArgs(const ArgsManager& args, CoinsViewOptions& options) +{ + if (auto value = args.GetIntArg("-dbbatchsize")) options.batch_write_bytes = *value; + if (auto value = args.GetIntArg("-dbcrashratio")) options.simulate_crash_ratio = *value; +} +} // namespace node diff --git a/src/node/coins_view_args.h b/src/node/coins_view_args.h new file mode 100644 index 0000000000..71a7a671fd --- /dev/null +++ b/src/node/coins_view_args.h @@ -0,0 +1,15 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NODE_COINS_VIEW_ARGS_H +#define BITCOIN_NODE_COINS_VIEW_ARGS_H + +class ArgsManager; +struct CoinsViewOptions; + +namespace node { +void ReadCoinsViewArgs(const ArgsManager& args, CoinsViewOptions& options); +} // namespace node + +#endif // BITCOIN_NODE_COINS_VIEW_ARGS_H diff --git a/src/node/database_args.cpp b/src/node/database_args.cpp new file mode 100644 index 0000000000..2c53b4b47e --- /dev/null +++ b/src/node/database_args.cpp @@ -0,0 +1,18 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <node/database_args.h> + +#include <dbwrapper.h> +#include <util/system.h> + +namespace node { +void ReadDatabaseArgs(const ArgsManager& args, DBOptions& options) +{ + // Settings here apply to all databases (chainstate, blocks, and index + // databases), but it'd be easy to parse database-specific options by adding + // a database_type string or enum parameter to this function. + if (auto value = args.GetBoolArg("-forcecompactdb")) options.force_compact = *value; +} +} // namespace node diff --git a/src/node/database_args.h b/src/node/database_args.h new file mode 100644 index 0000000000..001976f219 --- /dev/null +++ b/src/node/database_args.h @@ -0,0 +1,15 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NODE_DATABASE_ARGS_H +#define BITCOIN_NODE_DATABASE_ARGS_H + +class ArgsManager; +struct DBOptions; + +namespace node { +void ReadDatabaseArgs(const ArgsManager& args, DBOptions& options); +} // namespace node + +#endif // BITCOIN_NODE_DATABASE_ARGS_H diff --git a/src/node/miner.cpp b/src/node/miner.cpp index c2b6fd1dc3..c7bc9a9a3d 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -56,34 +56,27 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman) block.hashMerkleRoot = BlockMerkleRoot(block); } -BlockAssembler::Options::Options() +static BlockAssembler::Options ClampOptions(BlockAssembler::Options options) { - blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE); - nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT; - test_block_validity = true; + // Limit weight to between 4K and DEFAULT_BLOCK_MAX_WEIGHT for sanity: + options.nBlockMaxWeight = std::clamp<size_t>(options.nBlockMaxWeight, 4000, DEFAULT_BLOCK_MAX_WEIGHT); + return options; } BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options) - : test_block_validity{options.test_block_validity}, - chainparams{chainstate.m_chainman.GetParams()}, - m_mempool(mempool), - m_chainstate(chainstate) + : chainparams{chainstate.m_chainman.GetParams()}, + m_mempool{mempool}, + m_chainstate{chainstate}, + m_options{ClampOptions(options)} { - blockMinFeeRate = options.blockMinFeeRate; - // Limit weight to between 4K and MAX_BLOCK_WEIGHT-4K for sanity: - nBlockMaxWeight = std::max<size_t>(4000, std::min<size_t>(MAX_BLOCK_WEIGHT - 4000, options.nBlockMaxWeight)); } -void ApplyArgsManOptions(const ArgsManager& gArgs, BlockAssembler::Options& options) +void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& options) { // Block resource limits - // If -blockmaxweight is not given, limit to DEFAULT_BLOCK_MAX_WEIGHT - options.nBlockMaxWeight = gArgs.GetIntArg("-blockmaxweight", DEFAULT_BLOCK_MAX_WEIGHT); - if (gArgs.IsArgSet("-blockmintxfee")) { - std::optional<CAmount> parsed = ParseMoney(gArgs.GetArg("-blockmintxfee", "")); - options.blockMinFeeRate = CFeeRate{parsed.value_or(DEFAULT_BLOCK_MIN_TX_FEE)}; - } else { - options.blockMinFeeRate = CFeeRate{DEFAULT_BLOCK_MIN_TX_FEE}; + options.nBlockMaxWeight = args.GetIntArg("-blockmaxweight", options.nBlockMaxWeight); + if (const auto blockmintxfee{args.GetArg("-blockmintxfee")}) { + if (const auto parsed{ParseMoney(*blockmintxfee)}) options.blockMinFeeRate = CFeeRate{*parsed}; } } static BlockAssembler::Options ConfiguredOptions() @@ -176,7 +169,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]); BlockValidationState state; - if (test_block_validity && !TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev, + if (m_options.test_block_validity && !TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev, GetAdjustedTime, /*fCheckPOW=*/false, /*fCheckMerkleRoot=*/false)) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString())); } @@ -205,7 +198,7 @@ void BlockAssembler::onlyUnconfirmed(CTxMemPool::setEntries& testSet) bool BlockAssembler::TestPackage(uint64_t packageSize, int64_t packageSigOpsCost) const { // TODO: switch to weight-based accounting for packages instead of vsize-based accounting. - if (nBlockWeight + WITNESS_SCALE_FACTOR * packageSize >= nBlockMaxWeight) { + if (nBlockWeight + WITNESS_SCALE_FACTOR * packageSize >= m_options.nBlockMaxWeight) { return false; } if (nBlockSigOpsCost + packageSigOpsCost >= MAX_BLOCK_SIGOPS_COST) { @@ -377,7 +370,7 @@ void BlockAssembler::addPackageTxs(const CTxMemPool& mempool, int& nPackagesSele packageSigOpsCost = modit->nSigOpCostWithAncestors; } - if (packageFees < blockMinFeeRate.GetFee(packageSize)) { + if (packageFees < m_options.blockMinFeeRate.GetFee(packageSize)) { // Everything else we might consider has a lower fee rate return; } @@ -394,7 +387,7 @@ void BlockAssembler::addPackageTxs(const CTxMemPool& mempool, int& nPackagesSele ++nConsecutiveFailed; if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight > - nBlockMaxWeight - 4000) { + m_options.nBlockMaxWeight - 4000) { // Give up if we're close to full and haven't succeeded in a while break; } diff --git a/src/node/miner.h b/src/node/miner.h index ea9e470a64..f1ccffff55 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_NODE_MINER_H #define BITCOIN_NODE_MINER_H +#include <policy/policy.h> #include <primitives/block.h> #include <txmempool.h> @@ -132,13 +133,6 @@ private: // The constructed block template std::unique_ptr<CBlockTemplate> pblocktemplate; - // Configuration parameters for the block size - unsigned int nBlockMaxWeight; - CFeeRate blockMinFeeRate; - - // Whether to call TestBlockValidity() at the end of CreateNewBlock(). - const bool test_block_validity; - // Information on the current status of the block uint64_t nBlockWeight; uint64_t nBlockTx; @@ -156,10 +150,11 @@ private: public: struct Options { - Options(); - size_t nBlockMaxWeight; - CFeeRate blockMinFeeRate; - bool test_block_validity; + // Configuration parameters for the block size + size_t nBlockMaxWeight{DEFAULT_BLOCK_MAX_WEIGHT}; + CFeeRate blockMinFeeRate{DEFAULT_BLOCK_MIN_TX_FEE}; + // Whether to call TestBlockValidity() at the end of CreateNewBlock(). + bool test_block_validity{true}; }; explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool); @@ -172,6 +167,8 @@ public: inline static std::optional<int64_t> m_last_block_weight{}; private: + const Options m_options; + // utility functions /** Clear the block's state and prepare for assembling a new block */ void resetBlock(); diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index d15aba5cdd..0a96be038b 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -89,11 +89,10 @@ void AskPassphraseDialog::accept() oldpass.reserve(MAX_PASSPHRASE_SIZE); newpass1.reserve(MAX_PASSPHRASE_SIZE); newpass2.reserve(MAX_PASSPHRASE_SIZE); - // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) - // Alternately, find a way to make this input mlock()'d to begin with. - oldpass.assign(ui->passEdit1->text().toStdString().c_str()); - newpass1.assign(ui->passEdit2->text().toStdString().c_str()); - newpass2.assign(ui->passEdit3->text().toStdString().c_str()); + + oldpass.assign(std::string_view{ui->passEdit1->text().toStdString()}); + newpass1.assign(std::string_view{ui->passEdit2->text().toStdString()}); + newpass2.assign(std::string_view{ui->passEdit3->text().toStdString()}); secureClearPassFields(); @@ -154,8 +153,19 @@ void AskPassphraseDialog::accept() case Unlock: try { if (!model->setWalletLocked(false, oldpass)) { - QMessageBox::critical(this, tr("Wallet unlock failed"), - tr("The passphrase entered for the wallet decryption was incorrect.")); + // Check if the passphrase has a null character (see #27067 for details) + if (oldpass.find('\0') == std::string::npos) { + QMessageBox::critical(this, tr("Wallet unlock failed"), + tr("The passphrase entered for the wallet decryption was incorrect.")); + } else { + QMessageBox::critical(this, tr("Wallet unlock failed"), + tr("The passphrase entered for the wallet decryption is incorrect. " + "It contains a null character (ie - a zero byte). " + "If the passphrase was set with a version of this software prior to 25.0, " + "please try again with only the characters up to — but not including — " + "the first null character. If this is successful, please set a new " + "passphrase to avoid this issue in the future.")); + } } else { QDialog::accept(); // Success } @@ -174,8 +184,18 @@ void AskPassphraseDialog::accept() } else { - QMessageBox::critical(this, tr("Wallet encryption failed"), - tr("The passphrase entered for the wallet decryption was incorrect.")); + // Check if the old passphrase had a null character (see #27067 for details) + if (oldpass.find('\0') == std::string::npos) { + QMessageBox::critical(this, tr("Passphrase change failed"), + tr("The passphrase entered for the wallet decryption was incorrect.")); + } else { + QMessageBox::critical(this, tr("Passphrase change failed"), + tr("The old passphrase entered for the wallet decryption is incorrect. " + "It contains a null character (ie - a zero byte). " + "If the passphrase was set with a version of this software prior to 25.0, " + "please try again with only the characters up to — but not including — " + "the first null character.")); + } } } else diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 59f433749d..c383c8bd58 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -167,6 +167,7 @@ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTrans static bool InitSettings() { + gArgs.EnsureDataDir(); if (!gArgs.GetSettingsPath()) { return true; // Do nothing if settings file disabled. } diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 69ed5d3a90..8411ec4696 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -280,7 +280,7 @@ bool ClientModel::getProxyInfo(std::string& ip_port) const { Proxy ipv4, ipv6; if (m_node.getProxy((Network) 1, ipv4) && m_node.getProxy((Network) 2, ipv6)) { - ip_port = ipv4.proxy.ToStringIPPort(); + ip_port = ipv4.proxy.ToStringAddrPort(); return true; } return false; diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 53b0c3832b..6dec4b2e42 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -406,24 +406,21 @@ void OptionsDialog::updateProxyValidationState() void OptionsDialog::updateDefaultProxyNets() { + CNetAddr ui_proxy_netaddr; + LookupHost(ui->proxyIp->text().toStdString(), ui_proxy_netaddr, /*fAllowLookup=*/false); + const CService ui_proxy{ui_proxy_netaddr, ui->proxyPort->text().toUShort()}; + Proxy proxy; - std::string strProxy; - QString strDefaultProxyGUI; - - model->node().getProxy(NET_IPV4, proxy); - strProxy = proxy.proxy.ToStringIP() + ":" + proxy.proxy.ToStringPort(); - strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text(); - (strProxy == strDefaultProxyGUI.toStdString()) ? ui->proxyReachIPv4->setChecked(true) : ui->proxyReachIPv4->setChecked(false); - - model->node().getProxy(NET_IPV6, proxy); - strProxy = proxy.proxy.ToStringIP() + ":" + proxy.proxy.ToStringPort(); - strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text(); - (strProxy == strDefaultProxyGUI.toStdString()) ? ui->proxyReachIPv6->setChecked(true) : ui->proxyReachIPv6->setChecked(false); - - model->node().getProxy(NET_ONION, proxy); - strProxy = proxy.proxy.ToStringIP() + ":" + proxy.proxy.ToStringPort(); - strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text(); - (strProxy == strDefaultProxyGUI.toStdString()) ? ui->proxyReachTor->setChecked(true) : ui->proxyReachTor->setChecked(false); + bool has_proxy; + + has_proxy = model->node().getProxy(NET_IPV4, proxy); + ui->proxyReachIPv4->setChecked(has_proxy && proxy.proxy == ui_proxy); + + has_proxy = model->node().getProxy(NET_IPV6, proxy); + ui->proxyReachIPv6->setChecked(has_proxy && proxy.proxy == ui_proxy); + + has_proxy = model->node().getProxy(NET_ONION, proxy); + ui->proxyReachTor->setChecked(has_proxy && proxy.proxy == ui_proxy); } ProxyAddressValidator::ProxyAddressValidator(QObject *parent) : diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 7f95d527f0..bee8fafddc 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -18,6 +18,7 @@ #include <netbase.h> #include <txdb.h> // for -dbcache defaults #include <util/string.h> +#include <util/system.h> #include <validation.h> // For DEFAULT_SCRIPTCHECK_THREADS #include <wallet/wallet.h> // For DEFAULT_SPEND_ZEROCONF_CHANGE @@ -668,6 +669,11 @@ bool OptionsModel::isRestartRequired() const return settings.value("fRestartRequired", false).toBool(); } +bool OptionsModel::hasSigner() +{ + return gArgs.GetArg("-signer", "") != ""; +} + void OptionsModel::checkAndMigrate() { // Migration of default values diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index a5da4dfaeb..f28a1087ba 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -99,6 +99,9 @@ public: bool getEnablePSBTControls() const { return m_enable_psbt_controls; } const QString& getOverriddenByCommandLine() { return strOverriddenByCommandLine; } + /** Whether -signer was set or not */ + bool hasSigner(); + /* Explicit setters */ void SetPruneTargetGB(int prune_target_gb); diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 33145cc48d..89dd0ada62 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -203,7 +203,7 @@ void SendCoinsDialog::setModel(WalletModel *_model) if (model->wallet().hasExternalSigner()) { //: "device" usually means a hardware wallet. ui->sendButton->setText(tr("Sign on device")); - if (gArgs.GetArg("-signer", "") != "") { + if (model->getOptionsModel()->hasSigner()) { ui->sendButton->setEnabled(true); ui->sendButton->setToolTip(tr("Connect your hardware wallet first.")); } else { diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 049326070e..d005e08d14 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -75,7 +75,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node) auto wallet_loader = interfaces::MakeWalletLoader(*test.m_node.chain, *Assert(test.m_node.args)); test.m_node.wallet_loader = wallet_loader.get(); node.setContext(&test.m_node); - const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", gArgs, CreateMockWalletDatabase()); + const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", CreateMockWalletDatabase()); wallet->LoadWallet(); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); { diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 15fe37c164..62f2019438 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -159,7 +159,7 @@ void TestGUI(interfaces::Node& node) auto wallet_loader = interfaces::MakeWalletLoader(*test.m_node.chain, *Assert(test.m_node.args)); test.m_node.wallet_loader = wallet_loader.get(); node.setContext(&test.m_node); - const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", gArgs, CreateMockWalletDatabase()); + const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", CreateMockWalletDatabase()); wallet->LoadWallet(); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); { diff --git a/src/random.cpp b/src/random.cpp index 5f50c001cd..432592589a 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -8,23 +8,22 @@ #include <compat/cpuid.h> #include <crypto/sha256.h> #include <crypto/sha512.h> -#include <support/cleanse.h> -#ifdef WIN32 -#include <compat/compat.h> -#include <wincrypt.h> -#endif #include <logging.h> #include <randomenv.h> -#include <support/allocators/secure.h> #include <span.h> -#include <sync.h> // for Mutex -#include <util/time.h> // for GetTimeMicros() +#include <support/allocators/secure.h> +#include <support/cleanse.h> +#include <sync.h> +#include <util/time.h> #include <cmath> #include <cstdlib> #include <thread> -#ifndef WIN32 +#ifdef WIN32 +#include <windows.h> +#include <wincrypt.h> +#else #include <fcntl.h> #include <sys/time.h> #endif @@ -634,7 +633,7 @@ bool Random_SanityCheck() * GetOSRand() overwrites all 32 bytes of the output given a maximum * number of tries. */ - static const ssize_t MAX_TRIES = 1024; + static constexpr int MAX_TRIES{1024}; uint8_t data[NUM_OS_RANDOM_BYTES]; bool overwritten[NUM_OS_RANDOM_BYTES] = {}; /* Tracks which bytes have been overwritten at least once */ int num_overwritten; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 8bee066ab8..28a619fe54 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1110,7 +1110,7 @@ static RPCHelpMan verifychain() {"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS)}, "The number of blocks to check."}, }, RPCResult{ - RPCResult::Type::BOOL, "", "Verified or not"}, + RPCResult::Type::BOOL, "", "Verification finished successfully. If false, check debug.log for reason."}, RPCExamples{ HelpExampleCli("verifychain", "") + HelpExampleRpc("verifychain", "") @@ -1125,7 +1125,7 @@ static RPCHelpMan verifychain() Chainstate& active_chainstate = chainman.ActiveChainstate(); return CVerifyDB().VerifyDB( - active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth); + active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth) == VerifyDBResult::SUCCESS; }, }; } diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 5fe914f0a1..eb91a151b5 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -114,6 +114,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "signrawtransactionwithkey", 2, "prevtxs" }, { "signrawtransactionwithwallet", 1, "prevtxs" }, { "sendrawtransaction", 1, "maxfeerate" }, + { "sendrawtransaction", 2, "maxburnamount" }, { "testmempoolaccept", 0, "rawtxs" }, { "testmempoolaccept", 1, "maxfeerate" }, { "submitpackage", 0, "package" }, diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 44f7435a26..3a69e2d8a2 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -18,6 +18,7 @@ #include <rpc/server.h> #include <rpc/server_util.h> #include <rpc/util.h> +#include <script/standard.h> #include <txmempool.h> #include <univalue.h> #include <util/moneystr.h> @@ -44,7 +45,11 @@ static RPCHelpMan sendrawtransaction() {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"}, {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())}, "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + - "/kvB.\nSet to 0 to accept any fee rate.\n"}, + "/kvB.\nSet to 0 to accept any fee rate."}, + {"maxburnamount", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(0)}, + "Reject transactions with provably unspendable outputs (e.g. 'datacarrier' outputs that use the OP_RETURN opcode) greater than the specified value, expressed in " + CURRENCY_UNIT + ".\n" + "If burning funds through unspendable outputs is desired, increase this value.\n" + "This check is based on heuristics and does not guarantee spendability of outputs.\n"}, }, RPCResult{ RPCResult::Type::STR_HEX, "", "The transaction hash in hex" @@ -61,10 +66,19 @@ static RPCHelpMan sendrawtransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + const CAmount max_burn_amount = request.params[2].isNull() ? 0 : AmountFromValue(request.params[2]); + CMutableTransaction mtx; if (!DecodeHexTx(mtx, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input."); } + + for (const auto& out : mtx.vout) { + if((out.scriptPubKey.IsUnspendable() || !out.scriptPubKey.HasValidOps()) && out.nValue > max_burn_amount) { + throw JSONRPCTransactionError(TransactionError::MAX_BURN_EXCEEDED); + } + } + CTransactionRef tx(MakeTransactionRef(std::move(mtx))); const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ? @@ -839,15 +853,16 @@ static RPCHelpMan submitpackage() NONFATAL_UNREACHABLE(); } } + size_t num_broadcast{0}; for (const auto& tx : txns) { - size_t num_submitted{0}; std::string err_string; - const auto err = BroadcastTransaction(node, tx, err_string, 0, true, true); + const auto err = BroadcastTransaction(node, tx, err_string, /*max_tx_fee=*/0, /*relay=*/true, /*wait_callback=*/true); if (err != TransactionError::OK) { throw JSONRPCTransactionError(err, strprintf("transaction broadcast failed: %s (all transactions were submitted, %d transactions were broadcast successfully)", - err_string, num_submitted)); + err_string, num_broadcast)); } + num_broadcast++; } UniValue rpc_result{UniValue::VOBJ}; UniValue tx_result_map{UniValue::VOBJ}; diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 618a5d0dd4..7ffa777ef4 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -196,7 +196,7 @@ static RPCHelpMan getpeerinfo() obj.pushKV("id", stats.nodeid); obj.pushKV("addr", stats.m_addr_name); if (stats.addrBind.IsValid()) { - obj.pushKV("addrbind", stats.addrBind.ToString()); + obj.pushKV("addrbind", stats.addrBind.ToStringAddrPort()); } if (!(stats.addrLocal.empty())) { obj.pushKV("addrlocal", stats.addrLocal); @@ -496,7 +496,7 @@ static RPCHelpMan getaddednodeinfo() UniValue addresses(UniValue::VARR); if (info.fConnected) { UniValue address(UniValue::VOBJ); - address.pushKV("address", info.resolvedAddress.ToString()); + address.pushKV("address", info.resolvedAddress.ToStringAddrPort()); address.pushKV("connected", info.fInbound ? "inbound" : "outbound"); addresses.push_back(address); } @@ -571,7 +571,7 @@ static UniValue GetNetworksInfo() obj.pushKV("name", GetNetworkName(network)); obj.pushKV("limited", !IsReachable(network)); obj.pushKV("reachable", IsReachable(network)); - obj.pushKV("proxy", proxy.IsValid() ? proxy.proxy.ToStringIPPort() : std::string()); + obj.pushKV("proxy", proxy.IsValid() ? proxy.proxy.ToStringAddrPort() : std::string()); obj.pushKV("proxy_randomize_credentials", proxy.randomize_credentials); networks.push_back(obj); } @@ -664,7 +664,7 @@ static RPCHelpMan getnetworkinfo() for (const std::pair<const CNetAddr, LocalServiceInfo> &item : mapLocalHost) { UniValue rec(UniValue::VOBJ); - rec.pushKV("address", item.first.ToString()); + rec.pushKV("address", item.first.ToStringAddr()); rec.pushKV("port", item.second.nPort); rec.pushKV("score", item.second.nScore); localAddresses.push_back(rec); @@ -901,7 +901,7 @@ static RPCHelpMan getnodeaddresses() UniValue obj(UniValue::VOBJ); obj.pushKV("time", int64_t{TicksSinceEpoch<std::chrono::seconds>(addr.nTime)}); obj.pushKV("services", (uint64_t)addr.nServices); - obj.pushKV("address", addr.ToStringIP()); + obj.pushKV("address", addr.ToStringAddr()); obj.pushKV("port", addr.GetPort()); obj.pushKV("network", GetNetworkName(addr.GetNetClass())); ret.push_back(obj); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 503099d8a9..5ed8aee9ea 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -522,14 +522,14 @@ static RPCHelpMan decodescript() if (can_wrap_P2WSH) { UniValue sr(UniValue::VOBJ); CScript segwitScr; - FillableSigningProvider provider; + FlatSigningProvider provider; if (which_type == TxoutType::PUBKEY) { segwitScr = GetScriptForDestination(WitnessV0KeyHash(Hash160(solutions_data[0]))); } else if (which_type == TxoutType::PUBKEYHASH) { segwitScr = GetScriptForDestination(WitnessV0KeyHash(uint160{solutions_data[0]})); } else { // Scripts that are not fit for P2WPKH are encoded as P2WSH. - provider.AddCScript(script); + provider.scripts[CScriptID(script)] = script; segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script)); } ScriptToUniv(segwitScr, /*out=*/sr, /*include_hex=*/true, /*include_address=*/true, /*provider=*/&provider); diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 15b8e1dcd0..3ba930f84f 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -21,12 +21,8 @@ #include <util/strencodings.h> #include <util/translation.h> -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf) +void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, std::optional<bool> rbf) { - if (outputs_in.isNull()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null"); - } - UniValue inputs; if (inputs_in.isNull()) { inputs = UniValue::VARR; @@ -34,18 +30,6 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal inputs = inputs_in.get_array(); } - const bool outputs_is_obj = outputs_in.isObject(); - UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array(); - - CMutableTransaction rawTx; - - if (!locktime.isNull()) { - int64_t nLockTime = locktime.getInt<int64_t>(); - if (nLockTime < 0 || nLockTime > LOCKTIME_MAX) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); - rawTx.nLockTime = nLockTime; - } - for (unsigned int idx = 0; idx < inputs.size(); idx++) { const UniValue& input = inputs[idx]; const UniValue& o = input.get_obj(); @@ -84,6 +68,16 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal rawTx.vin.push_back(in); } +} + +void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in) +{ + if (outputs_in.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null"); + } + + const bool outputs_is_obj = outputs_in.isObject(); + UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array(); if (!outputs_is_obj) { // Translate array of key-value pairs into dict @@ -132,6 +126,21 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal rawTx.vout.push_back(out); } } +} + +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf) +{ + CMutableTransaction rawTx; + + if (!locktime.isNull()) { + int64_t nLockTime = locktime.getInt<int64_t>(); + if (nLockTime < 0 || nLockTime > LOCKTIME_MAX) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); + rawTx.nLockTime = nLockTime; + } + + AddInputs(rawTx, inputs_in, rbf); + AddOutputs(rawTx, outputs_in); if (rbf.has_value() && rbf.value() && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index 0c3823bc1e..a863432b7a 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -38,6 +38,13 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const */ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins); + +/** Normalize univalue-represented inputs and add them to the transaction */ +void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, bool rbf); + +/** Normalize univalue-represented outputs and add them to the transaction */ +void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in); + /** Create a transaction from univalue parameters */ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf); diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 03b157a847..5f4a1aceb2 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1439,7 +1439,7 @@ void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent hashOutputs = SHA256Uint256(m_outputs_single_hash); m_bip143_segwit_ready = true; } - if (uses_bip341_taproot) { + if (uses_bip341_taproot && m_spent_outputs_ready) { m_spent_amounts_single_hash = GetSpentAmountsSHA256(m_spent_outputs); m_spent_scripts_single_hash = GetSpentScriptsSHA256(m_spent_outputs); m_bip341_taproot_ready = true; diff --git a/src/support/allocators/secure.h b/src/support/allocators/secure.h index c6bd685189..a0918bf463 100644 --- a/src/support/allocators/secure.h +++ b/src/support/allocators/secure.h @@ -56,6 +56,7 @@ struct secure_allocator : public std::allocator<T> { }; // This is exactly like std::string, but with a custom allocator. +// TODO: Consider finding a way to make incoming RPC request.params[i] mlock()ed as well typedef std::basic_string<char, std::char_traits<char>, secure_allocator<char> > SecureString; #endif // BITCOIN_SUPPORT_ALLOCATORS_SECURE_H diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp index 24ae4bdd1e..f92d1d8fb7 100644 --- a/src/support/lockedpool.cpp +++ b/src/support/lockedpool.cpp @@ -42,12 +42,12 @@ static inline size_t align_up(size_t x, size_t align) // Implementation: Arena Arena::Arena(void *base_in, size_t size_in, size_t alignment_in): - base(static_cast<char*>(base_in)), end(static_cast<char*>(base_in) + size_in), alignment(alignment_in) + base(base_in), end(static_cast<char*>(base_in) + size_in), alignment(alignment_in) { // Start with one free chunk that covers the entire arena auto it = size_to_free_chunk.emplace(size_in, base); chunks_free.emplace(base, it); - chunks_free_end.emplace(base + size_in, it); + chunks_free_end.emplace(static_cast<char*>(base) + size_in, it); } Arena::~Arena() @@ -73,8 +73,9 @@ void* Arena::alloc(size_t size) // Create the used-chunk, taking its space from the end of the free-chunk const size_t size_remaining = size_ptr_it->first - size; - auto allocated = chunks_used.emplace(size_ptr_it->second + size_remaining, size).first; - chunks_free_end.erase(size_ptr_it->second + size_ptr_it->first); + char* const free_chunk = static_cast<char*>(size_ptr_it->second); + auto allocated = chunks_used.emplace(free_chunk + size_remaining, size).first; + chunks_free_end.erase(free_chunk + size_ptr_it->first); if (size_ptr_it->first == size) { // whole chunk is used up chunks_free.erase(size_ptr_it->second); @@ -82,11 +83,11 @@ void* Arena::alloc(size_t size) // still some memory left in the chunk auto it_remaining = size_to_free_chunk.emplace(size_remaining, size_ptr_it->second); chunks_free[size_ptr_it->second] = it_remaining; - chunks_free_end.emplace(size_ptr_it->second + size_remaining, it_remaining); + chunks_free_end.emplace(free_chunk + size_remaining, it_remaining); } size_to_free_chunk.erase(size_ptr_it); - return reinterpret_cast<void*>(allocated->first); + return allocated->first; } void Arena::free(void *ptr) @@ -97,11 +98,11 @@ void Arena::free(void *ptr) } // Remove chunk from used map - auto i = chunks_used.find(static_cast<char*>(ptr)); + auto i = chunks_used.find(ptr); if (i == chunks_used.end()) { throw std::runtime_error("Arena: invalid or double free"); } - std::pair<char*, size_t> freed = *i; + auto freed = std::make_pair(static_cast<char*>(i->first), i->second); chunks_used.erase(i); // coalesce freed with previous chunk diff --git a/src/support/lockedpool.h b/src/support/lockedpool.h index 1bba459377..81e0df513a 100644 --- a/src/support/lockedpool.h +++ b/src/support/lockedpool.h @@ -89,23 +89,23 @@ public: */ bool addressInArena(void *ptr) const { return ptr >= base && ptr < end; } private: - typedef std::multimap<size_t, char*> SizeToChunkSortedMap; + typedef std::multimap<size_t, void*> SizeToChunkSortedMap; /** Map to enable O(log(n)) best-fit allocation, as it's sorted by size */ SizeToChunkSortedMap size_to_free_chunk; - typedef std::unordered_map<char*, SizeToChunkSortedMap::const_iterator> ChunkToSizeMap; + typedef std::unordered_map<void*, SizeToChunkSortedMap::const_iterator> ChunkToSizeMap; /** Map from begin of free chunk to its node in size_to_free_chunk */ ChunkToSizeMap chunks_free; /** Map from end of free chunk to its node in size_to_free_chunk */ ChunkToSizeMap chunks_free_end; /** Map from begin of used chunk to its size */ - std::unordered_map<char*, size_t> chunks_used; + std::unordered_map<void*, size_t> chunks_used; /** Base address of arena */ - char* base; + void* base; /** End address of arena */ - char* end; + void* end; /** Minimum chunk alignment */ size_t alignment; }; diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index 586cec4081..758691cfde 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -69,14 +69,14 @@ BOOST_AUTO_TEST_CASE(addrman_simple) // Test: Does Addrman respond correctly when empty. BOOST_CHECK_EQUAL(addrman->Size(), 0U); auto addr_null = addrman->Select().first; - BOOST_CHECK_EQUAL(addr_null.ToString(), "[::]:0"); + BOOST_CHECK_EQUAL(addr_null.ToStringAddrPort(), "[::]:0"); // Test: Does Addrman::Add work as expected. CService addr1 = ResolveService("250.1.1.1", 8333); BOOST_CHECK(addrman->Add({CAddress(addr1, NODE_NONE)}, source)); BOOST_CHECK_EQUAL(addrman->Size(), 1U); auto addr_ret1 = addrman->Select().first; - BOOST_CHECK_EQUAL(addr_ret1.ToString(), "250.1.1.1:8333"); + BOOST_CHECK_EQUAL(addr_ret1.ToStringAddrPort(), "250.1.1.1:8333"); // Test: Does IP address deduplication work correctly. // Expected dup IP should not be added. @@ -121,7 +121,7 @@ BOOST_AUTO_TEST_CASE(addrman_ports) BOOST_CHECK(addrman->Add({CAddress(addr1_port, NODE_NONE)}, source)); BOOST_CHECK_EQUAL(addrman->Size(), 2U); auto addr_ret2 = addrman->Select().first; - BOOST_CHECK(addr_ret2.ToString() == "250.1.1.1:8333" || addr_ret2.ToString() == "250.1.1.1:8334"); + BOOST_CHECK(addr_ret2.ToStringAddrPort() == "250.1.1.1:8333" || addr_ret2.ToStringAddrPort() == "250.1.1.1:8334"); // Test: Add same IP but diff port to tried table; this converts the entry with // the specified port to tried, but not the other. @@ -129,7 +129,7 @@ BOOST_AUTO_TEST_CASE(addrman_ports) BOOST_CHECK_EQUAL(addrman->Size(), 2U); bool newOnly = true; auto addr_ret3 = addrman->Select(newOnly).first; - BOOST_CHECK_EQUAL(addr_ret3.ToString(), "250.1.1.1:8333"); + BOOST_CHECK_EQUAL(addr_ret3.ToStringAddrPort(), "250.1.1.1:8333"); } @@ -146,16 +146,16 @@ BOOST_AUTO_TEST_CASE(addrman_select) bool newOnly = true; auto addr_ret1 = addrman->Select(newOnly).first; - BOOST_CHECK_EQUAL(addr_ret1.ToString(), "250.1.1.1:8333"); + BOOST_CHECK_EQUAL(addr_ret1.ToStringAddrPort(), "250.1.1.1:8333"); // Test: move addr to tried, select from new expected nothing returned. BOOST_CHECK(addrman->Good(CAddress(addr1, NODE_NONE))); BOOST_CHECK_EQUAL(addrman->Size(), 1U); auto addr_ret2 = addrman->Select(newOnly).first; - BOOST_CHECK_EQUAL(addr_ret2.ToString(), "[::]:0"); + BOOST_CHECK_EQUAL(addr_ret2.ToStringAddrPort(), "[::]:0"); auto addr_ret3 = addrman->Select().first; - BOOST_CHECK_EQUAL(addr_ret3.ToString(), "250.1.1.1:8333"); + BOOST_CHECK_EQUAL(addr_ret3.ToStringAddrPort(), "250.1.1.1:8333"); BOOST_CHECK_EQUAL(addrman->Size(), 1U); @@ -714,7 +714,7 @@ BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision) BOOST_CHECK(addrman->Size() == 0); // Empty addrman should return blank addrman info. - BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToStringAddrPort() == "[::]:0"); // Add twenty two addresses. CNetAddr source = ResolveIP("252.2.2.2"); @@ -724,7 +724,7 @@ BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision) // No collisions in tried. BOOST_CHECK(addrman->Good(addr)); - BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToStringAddrPort() == "[::]:0"); } // Ensure Good handles duplicates well. @@ -736,7 +736,7 @@ BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision) BOOST_CHECK(!addrman->Good(addr)); // Verify duplicate address not marked as a collision. - BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToStringAddrPort() == "[::]:0"); } } @@ -758,13 +758,13 @@ BOOST_AUTO_TEST_CASE(addrman_noevict) CService addr36 = ResolveService("250.1.1.36"); BOOST_CHECK(addrman->Add({CAddress(addr36, NODE_NONE)}, source)); BOOST_CHECK(!addrman->Good(addr36)); - BOOST_CHECK_EQUAL(addrman->SelectTriedCollision().first.ToString(), "250.1.1.19:0"); + BOOST_CHECK_EQUAL(addrman->SelectTriedCollision().first.ToStringAddrPort(), "250.1.1.19:0"); // 36 should be discarded and 19 not evicted. // This means we keep 19 in the tried table and // 36 stays in the new table. addrman->ResolveCollisions(); - BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToStringAddrPort() == "[::]:0"); // Lets create two collisions. for (unsigned int i = 37; i < 59; i++) { @@ -778,18 +778,18 @@ BOOST_AUTO_TEST_CASE(addrman_noevict) BOOST_CHECK(addrman->Add({CAddress(addr59, NODE_NONE)}, source)); BOOST_CHECK(!addrman->Good(addr59)); - BOOST_CHECK_EQUAL(addrman->SelectTriedCollision().first.ToString(), "250.1.1.10:0"); + BOOST_CHECK_EQUAL(addrman->SelectTriedCollision().first.ToStringAddrPort(), "250.1.1.10:0"); // Cause a second collision in the new table. BOOST_CHECK(!addrman->Add({CAddress(addr36, NODE_NONE)}, source)); // 36 still cannot be moved from new to tried due to colliding with 19 BOOST_CHECK(!addrman->Good(addr36)); - BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() != "[::]:0"); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToStringAddrPort() != "[::]:0"); // Resolve all collisions. addrman->ResolveCollisions(); - BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToStringAddrPort() == "[::]:0"); } BOOST_AUTO_TEST_CASE(addrman_evictionworks) @@ -799,7 +799,7 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) BOOST_CHECK(addrman->Size() == 0); // Empty addrman should return blank addrman info. - BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToStringAddrPort() == "[::]:0"); // Add 35 addresses CNetAddr source = ResolveIP("252.2.2.2"); @@ -817,7 +817,7 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) BOOST_CHECK(!addrman->Good(addr)); auto info = addrman->SelectTriedCollision().first; - BOOST_CHECK_EQUAL(info.ToString(), "250.1.1.19:0"); + BOOST_CHECK_EQUAL(info.ToStringAddrPort(), "250.1.1.19:0"); // Ensure test of address fails, so that it is evicted. // Update entry in tried by setting last good connection in the deep past. @@ -826,7 +826,7 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) // Should swap 36 for 19. addrman->ResolveCollisions(); - BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToStringAddrPort() == "[::]:0"); AddressPosition addr_pos{addrman->FindAddressEntry(CAddress(addr, NODE_NONE)).value()}; BOOST_CHECK(addr_pos.tried); @@ -835,18 +835,18 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) // We check this by verifying Good() returns false and also verifying that // we have no collisions. BOOST_CHECK(!addrman->Good(addr)); - BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToStringAddrPort() == "[::]:0"); // 19 should fail as a collision (not a duplicate) if we now attempt to move // it to the tried table. CService addr19 = ResolveService("250.1.1.19"); BOOST_CHECK(!addrman->Good(addr19)); - BOOST_CHECK_EQUAL(addrman->SelectTriedCollision().first.ToString(), "250.1.1.36:0"); + BOOST_CHECK_EQUAL(addrman->SelectTriedCollision().first.ToStringAddrPort(), "250.1.1.36:0"); // Eviction is also successful if too much time has passed since last try SetMockTime(GetTime() + 4 * 60 *60); addrman->ResolveCollisions(); - BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToStringAddrPort() == "[::]:0"); //Now 19 is in tried again, and 36 back to new AddressPosition addr_pos19{addrman->FindAddressEntry(CAddress(addr19, NODE_NONE)).value()}; BOOST_CHECK(addr_pos19.tried); diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index 601caf8102..7f3ca6bf93 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -6,6 +6,7 @@ #include <base58.h> #include <test/util/json.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <util/strencodings.h> #include <util/vector.h> diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index e23b7228e7..4348a20886 100644 --- a/src/test/blockencodings_tests.cpp +++ b/src/test/blockencodings_tests.cpp @@ -7,6 +7,7 @@ #include <consensus/merkle.h> #include <pow.h> #include <streams.h> +#include <test/util/random.h> #include <test/util/txmempool.h> #include <test/util/setup_common.h> diff --git a/src/test/bloom_tests.cpp b/src/test/bloom_tests.cpp index 4888041204..5d4c5eea0e 100644 --- a/src/test/bloom_tests.cpp +++ b/src/test/bloom_tests.cpp @@ -12,6 +12,7 @@ #include <random.h> #include <serialize.h> #include <streams.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <uint256.h> #include <util/strencodings.h> diff --git a/src/test/checkqueue_tests.cpp b/src/test/checkqueue_tests.cpp index 53fbc26e15..135f107159 100644 --- a/src/test/checkqueue_tests.cpp +++ b/src/test/checkqueue_tests.cpp @@ -4,6 +4,7 @@ #include <checkqueue.h> #include <sync.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <util/system.h> #include <util/time.h> diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 55ecd41af1..e082800fc3 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -6,6 +6,7 @@ #include <coins.h> #include <script/standard.h> #include <streams.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <txdb.h> #include <uint256.h> @@ -172,7 +173,7 @@ void SimulationTest(CCoinsView* base, bool fake_best_block) if (InsecureRandRange(5) == 0 || coin.IsSpent()) { Coin newcoin; - newcoin.out.nValue = InsecureRand32(); + newcoin.out.nValue = InsecureRandMoneyAmount(); newcoin.nHeight = 1; // Infrequently test adding unspendable coins. @@ -278,7 +279,7 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) CCoinsViewTest base; SimulationTest(&base, false); - CCoinsViewDB db_base{"test", /*nCacheSize=*/1 << 23, /*fMemory=*/true, /*fWipe=*/false}; + CCoinsViewDB db_base{{.path = "test", .cache_bytes = 1 << 23, .memory_only = true}, {}}; SimulationTest(&db_base, true); } @@ -1064,7 +1065,7 @@ void TestFlushBehavior( BOOST_AUTO_TEST_CASE(ccoins_flush_behavior) { // Create two in-memory caches atop a leveldb view. - CCoinsViewDB base{"test", /*nCacheSize=*/ 1 << 23, /*fMemory=*/ true, /*fWipe=*/ false}; + CCoinsViewDB base{{.path = "test", .cache_bytes = 1 << 23, .memory_only = true}, {}}; std::vector<std::unique_ptr<CCoinsViewCacheTest>> caches; caches.push_back(std::make_unique<CCoinsViewCacheTest>(&base)); caches.push_back(std::make_unique<CCoinsViewCacheTest>(caches.back().get())); diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index ed851b5266..e4e8596a5d 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -17,6 +17,7 @@ #include <crypto/muhash.h> #include <random.h> #include <streams.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <util/strencodings.h> diff --git a/src/test/cuckoocache_tests.cpp b/src/test/cuckoocache_tests.cpp index c7c34cc8c9..eafbcf5681 100644 --- a/src/test/cuckoocache_tests.cpp +++ b/src/test/cuckoocache_tests.cpp @@ -1,9 +1,11 @@ // Copyright (c) 2012-2021 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. + #include <cuckoocache.h> #include <random.h> #include <script/sigcache.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/dbwrapper_tests.cpp b/src/test/dbwrapper_tests.cpp index 7ad123754b..723a1ceee3 100644 --- a/src/test/dbwrapper_tests.cpp +++ b/src/test/dbwrapper_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <dbwrapper.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <uint256.h> #include <util/string.h> @@ -28,7 +29,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper) // Perform tests both obfuscated and non-obfuscated. for (const bool obfuscate : {false, true}) { fs::path ph = m_args.GetDataDirBase() / (obfuscate ? "dbwrapper_obfuscate_true" : "dbwrapper_obfuscate_false"); - CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate); + CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20, .memory_only = true, .wipe_data = false, .obfuscate = obfuscate}); uint8_t key{'k'}; uint256 in = InsecureRand256(); uint256 res; @@ -47,7 +48,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper_basic_data) // Perform tests both obfuscated and non-obfuscated. for (bool obfuscate : {false, true}) { fs::path ph = m_args.GetDataDirBase() / (obfuscate ? "dbwrapper_1_obfuscate_true" : "dbwrapper_1_obfuscate_false"); - CDBWrapper dbw(ph, (1 << 20), false, true, obfuscate); + CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20, .memory_only = false, .wipe_data = true, .obfuscate = obfuscate}); uint256 res; uint32_t res_uint_32; @@ -128,7 +129,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper_batch) // Perform tests both obfuscated and non-obfuscated. for (const bool obfuscate : {false, true}) { fs::path ph = m_args.GetDataDirBase() / (obfuscate ? "dbwrapper_batch_obfuscate_true" : "dbwrapper_batch_obfuscate_false"); - CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate); + CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20, .memory_only = true, .wipe_data = false, .obfuscate = obfuscate}); uint8_t key{'i'}; uint256 in = InsecureRand256(); @@ -164,7 +165,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper_iterator) // Perform tests both obfuscated and non-obfuscated. for (const bool obfuscate : {false, true}) { fs::path ph = m_args.GetDataDirBase() / (obfuscate ? "dbwrapper_iterator_obfuscate_true" : "dbwrapper_iterator_obfuscate_false"); - CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate); + CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20, .memory_only = true, .wipe_data = false, .obfuscate = obfuscate}); // The two keys are intentionally chosen for ordering uint8_t key{'j'}; @@ -207,7 +208,7 @@ BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate) fs::create_directories(ph); // Set up a non-obfuscated wrapper to write some initial data. - std::unique_ptr<CDBWrapper> dbw = std::make_unique<CDBWrapper>(ph, (1 << 10), false, false, false); + std::unique_ptr<CDBWrapper> dbw = std::make_unique<CDBWrapper>(DBParams{.path = ph, .cache_bytes = 1 << 10, .memory_only = false, .wipe_data = false, .obfuscate = false}); uint8_t key{'k'}; uint256 in = InsecureRand256(); uint256 res; @@ -220,7 +221,7 @@ BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate) dbw.reset(); // Now, set up another wrapper that wants to obfuscate the same directory - CDBWrapper odbw(ph, (1 << 10), false, false, true); + CDBWrapper odbw({.path = ph, .cache_bytes = 1 << 10, .memory_only = false, .wipe_data = false, .obfuscate = true}); // Check that the key/val we wrote with unobfuscated wrapper exists and // is readable. @@ -248,7 +249,7 @@ BOOST_AUTO_TEST_CASE(existing_data_reindex) fs::create_directories(ph); // Set up a non-obfuscated wrapper to write some initial data. - std::unique_ptr<CDBWrapper> dbw = std::make_unique<CDBWrapper>(ph, (1 << 10), false, false, false); + std::unique_ptr<CDBWrapper> dbw = std::make_unique<CDBWrapper>(DBParams{.path = ph, .cache_bytes = 1 << 10, .memory_only = false, .wipe_data = false, .obfuscate = false}); uint8_t key{'k'}; uint256 in = InsecureRand256(); uint256 res; @@ -261,7 +262,7 @@ BOOST_AUTO_TEST_CASE(existing_data_reindex) dbw.reset(); // Simulate a -reindex by wiping the existing data store - CDBWrapper odbw(ph, (1 << 10), false, true, true); + CDBWrapper odbw({.path = ph, .cache_bytes = 1 << 10, .memory_only = false, .wipe_data = true, .obfuscate = true}); // Check that the key/val we wrote with unobfuscated wrapper doesn't exist uint256 res2; @@ -280,7 +281,7 @@ BOOST_AUTO_TEST_CASE(existing_data_reindex) BOOST_AUTO_TEST_CASE(iterator_ordering) { fs::path ph = m_args.GetDataDirBase() / "iterator_ordering"; - CDBWrapper dbw(ph, (1 << 20), true, false, false); + CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20, .memory_only = true, .wipe_data = false, .obfuscate = false}); for (int x=0x00; x<256; ++x) { uint8_t key = x; uint32_t value = x*x; @@ -348,7 +349,7 @@ struct StringContentsSerializer { BOOST_AUTO_TEST_CASE(iterator_string_ordering) { fs::path ph = m_args.GetDataDirBase() / "iterator_string_ordering"; - CDBWrapper dbw(ph, (1 << 20), true, false, false); + CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20, .memory_only = true, .wipe_data = false, .obfuscate = false}); for (int x = 0; x < 10; ++x) { for (int y = 0; y < 10; ++y) { std::string key{ToString(x)}; @@ -390,7 +391,7 @@ BOOST_AUTO_TEST_CASE(unicodepath) // the ANSI CreateDirectoryA call and the code page isn't UTF8. // It will succeed if created with CreateDirectoryW. fs::path ph = m_args.GetDataDirBase() / "test_runner_₿_🏃_20191128_104644"; - CDBWrapper dbw(ph, (1 << 20)); + CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20}); fs::path lockPath = ph / "LOCK"; BOOST_CHECK(fs::exists(lockPath)); diff --git a/src/test/fuzz/http_request.cpp b/src/test/fuzz/http_request.cpp index 66a1ff945f..9928c4a1ab 100644 --- a/src/test/fuzz/http_request.cpp +++ b/src/test/fuzz/http_request.cpp @@ -59,7 +59,7 @@ FUZZ_TARGET(http_request) const std::string body = http_request.ReadBody(); assert(body.empty()); const CService service = http_request.GetPeer(); - assert(service.ToString() == "[::]:0"); + assert(service.ToStringAddrPort() == "[::]:0"); evbuffer_free(evbuf); evhttp_request_free(evreq); diff --git a/src/test/fuzz/miniscript.cpp b/src/test/fuzz/miniscript.cpp index 1b791fc19c..73096cd5ca 100644 --- a/src/test/fuzz/miniscript.cpp +++ b/src/test/fuzz/miniscript.cpp @@ -253,7 +253,9 @@ using Type = miniscript::Type; using miniscript::operator"" _mst; //! Construct a miniscript node as a shared_ptr. -template<typename... Args> NodeRef MakeNodeRef(Args&&... args) { return miniscript::MakeNodeRef<CPubKey>(KEY_COMP, std::forward<Args>(args)...); } +template<typename... Args> NodeRef MakeNodeRef(Args&&... args) { + return miniscript::MakeNodeRef<CPubKey>(miniscript::internal::NoDupCheck{}, std::forward<Args>(args)...); +} /** Information about a yet to be constructed Miniscript node. */ struct NodeInfo { @@ -762,6 +764,7 @@ NodeRef GenNode(F ConsumeNode, Type root_type = ""_mst, bool strict_valid = fals } } assert(stack.size() == 1); + stack[0]->DuplicateKeyCheck(KEY_COMP); return std::move(stack[0]); } diff --git a/src/test/fuzz/netaddress.cpp b/src/test/fuzz/netaddress.cpp index d61aef6d81..049ae02f4d 100644 --- a/src/test/fuzz/netaddress.cpp +++ b/src/test/fuzz/netaddress.cpp @@ -70,8 +70,7 @@ FUZZ_TARGET(netaddress) assert(net_addr.GetNetwork() == Network::NET_ONION); } (void)net_addr.IsValid(); - (void)net_addr.ToString(); - (void)net_addr.ToStringIP(); + (void)net_addr.ToStringAddr(); const CSubNet sub_net{net_addr, fuzzed_data_provider.ConsumeIntegral<uint8_t>()}; (void)sub_net.IsValid(); @@ -80,9 +79,7 @@ FUZZ_TARGET(netaddress) const CService service{net_addr, fuzzed_data_provider.ConsumeIntegral<uint16_t>()}; (void)service.GetKey(); (void)service.GetPort(); - (void)service.ToString(); - (void)service.ToStringIPPort(); - (void)service.ToStringPort(); + (void)service.ToStringAddrPort(); (void)CServiceHash()(service); (void)CServiceHash(0, 0)(service); diff --git a/src/test/hash_tests.cpp b/src/test/hash_tests.cpp index 5b5158884a..f1f435591b 100644 --- a/src/test/hash_tests.cpp +++ b/src/test/hash_tests.cpp @@ -5,6 +5,7 @@ #include <clientversion.h> #include <crypto/siphash.h> #include <hash.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <util/strencodings.h> diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index edf28cfbfc..ea5b94f3a5 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -6,6 +6,7 @@ #include <key_io.h> #include <streams.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <uint256.h> #include <util/strencodings.h> diff --git a/src/test/merkle_tests.cpp b/src/test/merkle_tests.cpp index 74e01fc2a5..66f7be3c4e 100644 --- a/src/test/merkle_tests.cpp +++ b/src/test/merkle_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <consensus/merkle.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index e766a55673..9e484f919e 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -9,6 +9,7 @@ #include <node/miner.h> #include <policy/policy.h> #include <script/standard.h> +#include <test/util/random.h> #include <test/util/txmempool.h> #include <timedata.h> #include <txmempool.h> diff --git a/src/test/minisketch_tests.cpp b/src/test/minisketch_tests.cpp index 59c0aab053..10506da783 100644 --- a/src/test/minisketch_tests.cpp +++ b/src/test/minisketch_tests.cpp @@ -5,6 +5,7 @@ #include <minisketch.h> #include <node/minisketchwrapper.h> #include <random.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 5a97e9429a..4fbd9b3a6e 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -141,7 +141,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_CHECK(addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), "0.0.0.0"); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), "0.0.0.0"); // IPv4, INADDR_NONE BOOST_REQUIRE(LookupHost("255.255.255.255", addr, false)); @@ -150,7 +150,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), "255.255.255.255"); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), "255.255.255.255"); // IPv4, casual BOOST_REQUIRE(LookupHost("12.34.56.78", addr, false)); @@ -159,7 +159,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), "12.34.56.78"); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), "12.34.56.78"); // IPv6, in6addr_any BOOST_REQUIRE(LookupHost("::", addr, false)); @@ -168,7 +168,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_CHECK(addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), "::"); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), "::"); // IPv6, casual BOOST_REQUIRE(LookupHost("1122:3344:5566:7788:9900:aabb:ccdd:eeff", addr, false)); @@ -177,7 +177,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), "1122:3344:5566:7788:9900:aabb:ccdd:eeff"); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), "1122:3344:5566:7788:9900:aabb:ccdd:eeff"); // IPv6, scoped/link-local. See https://tools.ietf.org/html/rfc4007 // We support non-negative decimal integers (uint32_t) as zone id indices. @@ -190,14 +190,14 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsIPv6()); BOOST_CHECK(!addr.IsBindAny()); - BOOST_CHECK_EQUAL(addr.ToString(), scoped_addr); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), scoped_addr); // Test that the delimiter "%" and default zone id of 0 can be omitted for the default scope. BOOST_REQUIRE(LookupHost(link_local + "%0", addr, false)); BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsIPv6()); BOOST_CHECK(!addr.IsBindAny()); - BOOST_CHECK_EQUAL(addr.ToString(), link_local); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), link_local); // TORv2, no longer supported BOOST_CHECK(!addr.SetSpecial("6hzph5hv6337r6p2.onion")); @@ -211,7 +211,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_CHECK(!addr.IsI2P()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(!addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), torv3_addr); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), torv3_addr); // TORv3, broken, with wrong checksum BOOST_CHECK(!addr.SetSpecial("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscsad.onion")); @@ -238,7 +238,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_CHECK(!addr.IsTor()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(!addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), ToLower(i2p_addr)); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), ToLower(i2p_addr)); // I2P, correct length, but decodes to less than the expected number of bytes. BOOST_CHECK(!addr.SetSpecial("udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jn=.b32.i2p")); @@ -265,7 +265,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), "esffpvrt3wpeaygy.internal"); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), "esffpvrt3wpeaygy.internal"); // Totally bogus BOOST_CHECK(!addr.SetSpecial("totally bogus")); @@ -321,7 +321,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_tostring_canonical_ipv6) CNetAddr net_addr; BOOST_REQUIRE(LookupHost(input_address, net_addr, false)); BOOST_REQUIRE(net_addr.IsIPv6()); - BOOST_CHECK_EQUAL(net_addr.ToString(), expected_canonical_representation_output); + BOOST_CHECK_EQUAL(net_addr.ToStringAddr(), expected_canonical_representation_output); } } @@ -410,7 +410,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2) BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsIPv4()); BOOST_CHECK(addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), "1.2.3.4"); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), "1.2.3.4"); BOOST_REQUIRE(s.empty()); // Invalid IPv4, valid length but address itself is shorter. @@ -447,7 +447,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2) BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsIPv6()); BOOST_CHECK(addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), "102:304:506:708:90a:b0c:d0e:f10"); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), "102:304:506:708:90a:b0c:d0e:f10"); BOOST_REQUIRE(s.empty()); // Valid IPv6, contains embedded "internal". @@ -459,7 +459,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2) s >> addr; BOOST_CHECK(addr.IsInternal()); BOOST_CHECK(addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), "zklycewkdo64v6wc.internal"); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), "zklycewkdo64v6wc.internal"); BOOST_REQUIRE(s.empty()); // Invalid IPv6, with bogus length. @@ -505,7 +505,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2) BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsTor()); BOOST_CHECK(!addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), + BOOST_CHECK_EQUAL(addr.ToStringAddr(), "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"); BOOST_REQUIRE(s.empty()); @@ -528,7 +528,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2) BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsI2P()); BOOST_CHECK(!addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), + BOOST_CHECK_EQUAL(addr.ToStringAddr(), "ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq.b32.i2p"); BOOST_REQUIRE(s.empty()); @@ -551,7 +551,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2) BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsCJDNS()); BOOST_CHECK(!addr.IsAddrV1Compatible()); - BOOST_CHECK_EQUAL(addr.ToString(), "fc00:1:2:3:4:5:6:7"); + BOOST_CHECK_EQUAL(addr.ToStringAddr(), "fc00:1:2:3:4:5:6:7"); BOOST_REQUIRE(s.empty()); // Invalid CJDNS, wrong prefix. diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index cef42b7dd8..7e91819ddc 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -131,7 +131,7 @@ BOOST_AUTO_TEST_CASE(netbase_splithost) bool static TestParse(std::string src, std::string canon) { CService addr(LookupNumeric(src, 65535)); - return canon == addr.ToString(); + return canon == addr.ToStringAddrPort(); } BOOST_AUTO_TEST_CASE(netbase_lookupnumeric) @@ -155,7 +155,7 @@ BOOST_AUTO_TEST_CASE(embedded_test) CNetAddr addr1(ResolveIP("1.2.3.4")); CNetAddr addr2(ResolveIP("::FFFF:0102:0304")); BOOST_CHECK(addr2.IsIPv4()); - BOOST_CHECK_EQUAL(addr1.ToString(), addr2.ToString()); + BOOST_CHECK_EQUAL(addr1.ToStringAddr(), addr2.ToStringAddr()); } BOOST_AUTO_TEST_CASE(subnet_test) @@ -240,7 +240,7 @@ BOOST_AUTO_TEST_CASE(subnet_test) subnet = CSubNet(tor_addr); BOOST_CHECK(subnet.IsValid()); - BOOST_CHECK_EQUAL(subnet.ToString(), tor_addr.ToString()); + BOOST_CHECK_EQUAL(subnet.ToString(), tor_addr.ToStringAddr()); BOOST_CHECK(subnet.Match(tor_addr)); BOOST_CHECK( !subnet.Match(ResolveIP("kpgvmscirrdqpekbqjsvw5teanhatztpp2gl6eee4zkowvwfxwenqaid.onion"))); diff --git a/src/test/orphanage_tests.cpp b/src/test/orphanage_tests.cpp index d95b9711d0..a2c4774338 100644 --- a/src/test/orphanage_tests.cpp +++ b/src/test/orphanage_tests.cpp @@ -7,6 +7,7 @@ #include <script/sign.h> #include <script/signingprovider.h> #include <script/standard.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <txorphanage.h> diff --git a/src/test/pmt_tests.cpp b/src/test/pmt_tests.cpp index 21e0dd2fc5..a1e672d174 100644 --- a/src/test/pmt_tests.cpp +++ b/src/test/pmt_tests.cpp @@ -6,6 +6,7 @@ #include <merkleblock.h> #include <serialize.h> #include <streams.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <uint256.h> #include <version.h> diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index 7cd12ede0a..addc925bab 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -5,6 +5,7 @@ #include <chain.h> #include <chainparams.h> #include <pow.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/prevector_tests.cpp b/src/test/prevector_tests.cpp index 5f4d307048..1559011fcd 100644 --- a/src/test/prevector_tests.cpp +++ b/src/test/prevector_tests.cpp @@ -9,6 +9,7 @@ #include <serialize.h> #include <streams.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index b16f63d685..45d9f2cf29 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -16,6 +16,7 @@ #include <script/signingprovider.h> #include <streams.h> #include <test/util/json.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <test/util/transaction_utils.h> #include <util/strencodings.h> diff --git a/src/test/serfloat_tests.cpp b/src/test/serfloat_tests.cpp index f6af32cf6c..b36bdc02ca 100644 --- a/src/test/serfloat_tests.cpp +++ b/src/test/serfloat_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <hash.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <util/serfloat.h> #include <serialize.h> diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index 368f9e6047..e2d11afa6a 100644 --- a/src/test/sighash_tests.cpp +++ b/src/test/sighash_tests.cpp @@ -11,6 +11,7 @@ #include <streams.h> #include <test/data/sighash.json.h> #include <test/util/json.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <util/strencodings.h> #include <util/system.h> @@ -109,7 +110,7 @@ void static RandomTransaction(CMutableTransaction& tx, bool fSingle) for (int out = 0; out < outs; out++) { tx.vout.push_back(CTxOut()); CTxOut &txout = tx.vout.back(); - txout.nValue = InsecureRandRange(100000000); + txout.nValue = InsecureRandMoneyAmount(); RandomScript(txout.scriptPubKey); } } diff --git a/src/test/skiplist_tests.cpp b/src/test/skiplist_tests.cpp index ae9021df58..050033e43a 100644 --- a/src/test/skiplist_tests.cpp +++ b/src/test/skiplist_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chain.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <vector> diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index b7c1ce5066..a9b5251ad3 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -4,6 +4,7 @@ #include <fs.h> #include <streams.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 507284a566..11efb6a5c3 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -22,6 +22,7 @@ #include <script/standard.h> #include <streams.h> #include <test/util/json.h> +#include <test/util/random.h> #include <test/util/script.h> #include <test/util/transaction_utils.h> #include <util/strencodings.h> diff --git a/src/test/txpackage_tests.cpp b/src/test/txpackage_tests.cpp index e438867d15..024526497c 100644 --- a/src/test/txpackage_tests.cpp +++ b/src/test/txpackage_tests.cpp @@ -9,6 +9,7 @@ #include <primitives/transaction.h> #include <script/script.h> #include <script/standard.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <validation.h> diff --git a/src/test/txrequest_tests.cpp b/src/test/txrequest_tests.cpp index a4ed1e8b3a..17a55d5ab5 100644 --- a/src/test/txrequest_tests.cpp +++ b/src/test/txrequest_tests.cpp @@ -6,6 +6,7 @@ #include <txrequest.h> #include <uint256.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <algorithm> diff --git a/src/test/util/blockfilter.cpp b/src/test/util/blockfilter.cpp index 3ae22921b9..ec703c6a7b 100644 --- a/src/test/util/blockfilter.cpp +++ b/src/test/util/blockfilter.cpp @@ -28,4 +28,3 @@ bool ComputeFilter(BlockFilterType filter_type, const CBlockIndex* block_index, filter = BlockFilter(filter_type, block, block_undo); return true; } - diff --git a/src/test/util/coins.cpp b/src/test/util/coins.cpp new file mode 100644 index 0000000000..9b6c5535c5 --- /dev/null +++ b/src/test/util/coins.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2023 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <test/util/coins.h> + +#include <coins.h> +#include <primitives/transaction.h> +#include <script/script.h> +#include <test/util/random.h> +#include <uint256.h> + +#include <stdint.h> +#include <utility> + +COutPoint AddTestCoin(CCoinsViewCache& coins_view) +{ + Coin new_coin; + const uint256 txid{InsecureRand256()}; + COutPoint outpoint{txid, /*nIn=*/0}; + new_coin.nHeight = 1; + new_coin.out.nValue = InsecureRandMoneyAmount(); + new_coin.out.scriptPubKey.assign(uint32_t{56}, 1); + coins_view.AddCoin(outpoint, std::move(new_coin), /*possible_overwrite=*/false); + + return outpoint; +}; diff --git a/src/test/util/coins.h b/src/test/util/coins.h new file mode 100644 index 0000000000..5e6f4293ae --- /dev/null +++ b/src/test/util/coins.h @@ -0,0 +1,19 @@ +// Copyright (c) 2023 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_TEST_UTIL_COINS_H +#define BITCOIN_TEST_UTIL_COINS_H + +#include <primitives/transaction.h> + +class CCoinsViewCache; + +/** + * Create a Coin with DynamicMemoryUsage of 80 bytes and add it to the given view. + * @param[in,out] coins_view The coins view cache to add the new coin to. + * @returns the COutPoint of the created coin. + */ +COutPoint AddTestCoin(CCoinsViewCache& coins_view); + +#endif // BITCOIN_TEST_UTIL_COINS_H diff --git a/src/test/util/random.h b/src/test/util/random.h new file mode 100644 index 0000000000..7997e8a346 --- /dev/null +++ b/src/test/util/random.h @@ -0,0 +1,45 @@ +// Copyright (c) 2023 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_TEST_UTIL_RANDOM_H +#define BITCOIN_TEST_UTIL_RANDOM_H + +#include <consensus/amount.h> +#include <random.h> +#include <test/util/setup_common.h> +#include <uint256.h> + +#include <cstdint> + +static inline uint32_t InsecureRand32() +{ + return g_insecure_rand_ctx.rand32(); +} + +static inline uint256 InsecureRand256() +{ + return g_insecure_rand_ctx.rand256(); +} + +static inline uint64_t InsecureRandBits(int bits) +{ + return g_insecure_rand_ctx.randbits(bits); +} + +static inline uint64_t InsecureRandRange(uint64_t range) +{ + return g_insecure_rand_ctx.randrange(range); +} + +static inline bool InsecureRandBool() +{ + return g_insecure_rand_ctx.randbool(); +} + +static inline CAmount InsecureRandMoneyAmount() +{ + return static_cast<CAmount>(InsecureRandRange(MAX_MONEY + 1)); +} + +#endif // BITCOIN_TEST_UTIL_RANDOM_H diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 6e72f69968..4e0000cb3d 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -180,11 +180,15 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve const ChainstateManager::Options chainman_opts{ .chainparams = chainparams, + .datadir = m_args.GetDataDirNet(), .adjusted_time_callback = GetAdjustedTime, .check_block_index = true, }; m_node.chainman = std::make_unique<ChainstateManager>(chainman_opts); - m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<CBlockTreeDB>(m_cache_sizes.block_tree_db, true); + m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<CBlockTreeDB>(DBParams{ + .path = m_args.GetDataDirNet() / "blocks" / "index", + .cache_bytes = static_cast<size_t>(m_cache_sizes.block_tree_db), + .memory_only = true}); constexpr int script_check_threads = 2; StartScriptCheckWorkerThreads(script_check_threads); @@ -218,6 +222,7 @@ void TestingSetup::LoadVerifyActivateChainstate() options.prune = chainman.m_blockman.IsPruneMode(); options.check_blocks = m_args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS); options.check_level = m_args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL); + options.require_full_verification = m_args.IsArgSet("-checkblocks") || m_args.IsArgSet("-checklevel"); auto [status, error] = LoadChainstate(chainman, m_cache_sizes, options); assert(status == node::ChainstateLoadStatus::SUCCESS); diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 5f653d83ae..8874db7e75 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -71,12 +71,6 @@ static inline void SeedInsecureRand(SeedRand seed = SeedRand::SEED) } } -static inline uint32_t InsecureRand32() { return g_insecure_rand_ctx.rand32(); } -static inline uint256 InsecureRand256() { return g_insecure_rand_ctx.rand256(); } -static inline uint64_t InsecureRandBits(int bits) { return g_insecure_rand_ctx.randbits(bits); } -static inline uint64_t InsecureRandRange(uint64_t range) { return g_insecure_rand_ctx.randrange(range); } -static inline bool InsecureRandBool() { return g_insecure_rand_ctx.randbool(); } - static constexpr CAmount CENT{1000000}; /** Basic testing setup. diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 01e3cc8aed..a13552653e 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -9,6 +9,7 @@ #include <hash.h> // For Hash() #include <key.h> // For CKey #include <sync.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <uint256.h> #include <util/getuniquepath.h> diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 823c9877ac..4c8687ce69 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -11,6 +11,7 @@ #include <pow.h> #include <random.h> #include <script/standard.h> +#include <test/util/random.h> #include <test/util/script.h> #include <test/util/setup_common.h> #include <util/time.h> diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp index c40481a95c..2078fcd8f8 100644 --- a/src/test/validation_chainstate_tests.cpp +++ b/src/test/validation_chainstate_tests.cpp @@ -8,6 +8,8 @@ #include <rpc/blockchain.h> #include <sync.h> #include <test/util/chainstate.h> +#include <test/util/coins.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <uint256.h> #include <validation.h> @@ -24,20 +26,6 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) { ChainstateManager& manager = *Assert(m_node.chainman); CTxMemPool& mempool = *Assert(m_node.mempool); - - //! Create and add a Coin with DynamicMemoryUsage of 80 bytes to the given view. - auto add_coin = [](CCoinsViewCache& coins_view) -> COutPoint { - Coin newcoin; - uint256 txid = InsecureRand256(); - COutPoint outp{txid, 0}; - newcoin.nHeight = 1; - newcoin.out.nValue = InsecureRand32(); - newcoin.out.scriptPubKey.assign(uint32_t{56}, 1); - coins_view.AddCoin(outp, std::move(newcoin), false); - - return outp; - }; - Chainstate& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool)); c1.InitCoinsDB( /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); @@ -47,7 +35,7 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) // Add a coin to the in-memory cache, upsize once, then downsize. { LOCK(::cs_main); - auto outpoint = add_coin(c1.CoinsTip()); + const auto outpoint = AddTestCoin(c1.CoinsTip()); // Set a meaningless bestblock value in the coinsview cache - otherwise we won't // flush during ResizecoinsCaches() and will subsequently hit an assertion. diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index 56867a584b..78301c7c14 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -9,6 +9,7 @@ #include <rpc/blockchain.h> #include <sync.h> #include <test/util/chainstate.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <timedata.h> #include <uint256.h> @@ -374,6 +375,7 @@ struct SnapshotTestSetup : TestChain100Setup { BOOST_CHECK_EQUAL(chainman.GetAll().size(), 0); const ChainstateManager::Options chainman_opts{ .chainparams = ::Params(), + .datadir = m_args.GetDataDirNet(), .adjusted_time_callback = GetAdjustedTime, }; // For robustness, ensure the old manager is destroyed before creating a diff --git a/src/test/validation_flush_tests.cpp b/src/test/validation_flush_tests.cpp index f2ff570ca6..26c48eb0e0 100644 --- a/src/test/validation_flush_tests.cpp +++ b/src/test/validation_flush_tests.cpp @@ -3,6 +3,8 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. // #include <sync.h> +#include <test/util/coins.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <validation.h> @@ -24,19 +26,6 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) LOCK(::cs_main); auto& view = chainstate.CoinsTip(); - //! Create and add a Coin with DynamicMemoryUsage of 80 bytes to the given view. - auto add_coin = [](CCoinsViewCache& coins_view) -> COutPoint { - Coin newcoin; - uint256 txid = InsecureRand256(); - COutPoint outp{txid, 0}; - newcoin.nHeight = 1; - newcoin.out.nValue = InsecureRand32(); - newcoin.out.scriptPubKey.assign(uint32_t{56}, 1); - coins_view.AddCoin(outp, std::move(newcoin), false); - - return outp; - }; - // The number of bytes consumed by coin's heap data, i.e. CScript // (prevector<28, unsigned char>) when assigned 56 bytes of data per above. // @@ -61,7 +50,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) // Add a bunch of coins to see that we at least flip over to CRITICAL. for (int i{0}; i < 1000; ++i) { - COutPoint res = add_coin(view); + const COutPoint res = AddTestCoin(view); BOOST_CHECK_EQUAL(view.AccessCoin(res).DynamicMemoryUsage(), COIN_SIZE); } @@ -83,7 +72,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) constexpr int COINS_UNTIL_CRITICAL{3}; for (int i{0}; i < COINS_UNTIL_CRITICAL; ++i) { - COutPoint res = add_coin(view); + const COutPoint res = AddTestCoin(view); print_view_mem_usage(view); BOOST_CHECK_EQUAL(view.AccessCoin(res).DynamicMemoryUsage(), COIN_SIZE); BOOST_CHECK_EQUAL( @@ -93,7 +82,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) // Adding some additional coins will push us over the edge to CRITICAL. for (int i{0}; i < 4; ++i) { - add_coin(view); + AddTestCoin(view); print_view_mem_usage(view); if (chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes=*/0) == CoinsCacheSizeState::CRITICAL) { @@ -111,7 +100,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) CoinsCacheSizeState::OK); for (int i{0}; i < 3; ++i) { - add_coin(view); + AddTestCoin(view); print_view_mem_usage(view); BOOST_CHECK_EQUAL( chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes=*/1 << 10), @@ -120,7 +109,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) // Adding another coin with the additional mempool room will put us >90% // but not yet critical. - add_coin(view); + AddTestCoin(view); print_view_mem_usage(view); // Only perform these checks on 64 bit hosts; I haven't done the math for 32. @@ -136,7 +125,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) // Using the default max_* values permits way more coins to be added. for (int i{0}; i < 1000; ++i) { - add_coin(view); + AddTestCoin(view); BOOST_CHECK_EQUAL( chainstate.GetCoinsCacheSizeState(), CoinsCacheSizeState::OK); diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index 91383ee4a5..80c00036e7 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -5,6 +5,7 @@ #include <chain.h> #include <chainparams.h> #include <consensus/params.h> +#include <test/util/random.h> #include <test/util/setup_common.h> #include <versionbits.h> diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index ece77f9023..d4daeacd3e 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -380,7 +380,7 @@ void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlRe } Assume(resolved.IsValid()); - LogPrint(BCLog::TOR, "Configuring onion proxy for %s\n", resolved.ToStringIPPort()); + LogPrint(BCLog::TOR, "Configuring onion proxy for %s\n", resolved.ToStringAddrPort()); Proxy addrOnion = Proxy(resolved, true); SetProxy(NET_ONION, addrOnion); @@ -421,7 +421,7 @@ void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlRe return; } service = LookupNumeric(std::string(service_id+".onion"), Params().GetDefaultPort()); - LogPrintfCategory(BCLog::TOR, "Got service ID %s, advertising service %s\n", service_id, service.ToString()); + LogPrintfCategory(BCLog::TOR, "Got service ID %s, advertising service %s\n", service_id, service.ToStringAddrPort()); if (WriteBinaryFile(GetPrivateKeyFile(), private_key)) { LogPrint(BCLog::TOR, "Cached service private key to %s\n", fs::PathToString(GetPrivateKeyFile())); } else { @@ -453,7 +453,7 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply& } // Request onion service, redirect port. // Note that the 'virtual' port is always the default port to avoid decloaking nodes using other ports. - _conn.Command(strprintf("ADD_ONION %s Port=%i,%s", private_key, Params().GetDefaultPort(), m_target.ToStringIPPort()), + _conn.Command(strprintf("ADD_ONION %s Port=%i,%s", private_key, Params().GetDefaultPort(), m_target.ToStringAddrPort()), std::bind(&TorController::add_onion_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { LogPrintf("tor: Authentication failed\n"); diff --git a/src/txdb.cpp b/src/txdb.cpp index c12b540b9b..7257fb4959 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -70,21 +70,22 @@ struct CoinEntry { } // namespace -CCoinsViewDB::CCoinsViewDB(fs::path ldb_path, size_t nCacheSize, bool fMemory, bool fWipe) : - m_db(std::make_unique<CDBWrapper>(ldb_path, nCacheSize, fMemory, fWipe, true)), - m_ldb_path(ldb_path), - m_is_memory(fMemory) { } +CCoinsViewDB::CCoinsViewDB(DBParams db_params, CoinsViewOptions options) : + m_db_params{std::move(db_params)}, + m_options{std::move(options)}, + m_db{std::make_unique<CDBWrapper>(m_db_params)} { } void CCoinsViewDB::ResizeCache(size_t new_cache_size) { // We can't do this operation with an in-memory DB since we'll lose all the coins upon // reset. - if (!m_is_memory) { + if (!m_db_params.memory_only) { // Have to do a reset first to get the original `m_db` state to release its // filesystem lock. m_db.reset(); - m_db = std::make_unique<CDBWrapper>( - m_ldb_path, new_cache_size, m_is_memory, /*fWipe=*/false, /*obfuscate=*/true); + m_db_params.cache_bytes = new_cache_size; + m_db_params.wipe_data = false; + m_db = std::make_unique<CDBWrapper>(m_db_params); } } @@ -115,8 +116,6 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, boo CDBBatch batch(*m_db); size_t count = 0; size_t changed = 0; - size_t batch_size = (size_t)gArgs.GetIntArg("-dbbatchsize", nDefaultDbBatchSize); - int crash_simulate = gArgs.GetIntArg("-dbcrashratio", 0); assert(!hashBlock.IsNull()); uint256 old_tip = GetBestBlock(); @@ -147,13 +146,13 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, boo } count++; it = erase ? mapCoins.erase(it) : std::next(it); - if (batch.SizeEstimate() > batch_size) { + if (batch.SizeEstimate() > m_options.batch_write_bytes) { LogPrint(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0)); m_db->WriteBatch(batch); batch.Clear(); - if (crash_simulate) { + if (m_options.simulate_crash_ratio) { static FastRandomContext rng; - if (rng.randrange(crash_simulate) == 0) { + if (rng.randrange(m_options.simulate_crash_ratio) == 0) { LogPrintf("Simulating a crash. Goodbye.\n"); _Exit(0); } @@ -176,9 +175,6 @@ size_t CCoinsViewDB::EstimateSize() const return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1)); } -CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(gArgs.GetDataDirNet() / "blocks" / "index", nCacheSize, fMemory, fWipe) { -} - bool CBlockTreeDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) { return Read(std::make_pair(DB_BLOCK_FILES, nFile), info); } diff --git a/src/txdb.h b/src/txdb.h index e3422846c0..8a876349fb 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -45,18 +45,24 @@ static const int64_t max_filter_index_cache = 1024; //! Max memory allocated to coin DB specific cache (MiB) static const int64_t nMaxCoinsDBCache = 8; +//! User-controlled performance and debug options. +struct CoinsViewOptions { + //! Maximum database write batch size in bytes. + size_t batch_write_bytes = nDefaultDbBatchSize; + //! If non-zero, randomly exit when the database is flushed with (1/ratio) + //! probability. + int simulate_crash_ratio = 0; +}; + /** CCoinsView backed by the coin database (chainstate/) */ class CCoinsViewDB final : public CCoinsView { protected: + DBParams m_db_params; + CoinsViewOptions m_options; std::unique_ptr<CDBWrapper> m_db; - fs::path m_ldb_path; - bool m_is_memory; public: - /** - * @param[in] ldb_path Location in the filesystem where leveldb data will be stored. - */ - explicit CCoinsViewDB(fs::path ldb_path, size_t nCacheSize, bool fMemory, bool fWipe); + explicit CCoinsViewDB(DBParams db_params, CoinsViewOptions options); bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; bool HaveCoin(const COutPoint &outpoint) const override; @@ -80,8 +86,7 @@ public: class CBlockTreeDB : public CDBWrapper { public: - explicit CBlockTreeDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); - + using CDBWrapper::CDBWrapper; bool WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo); bool ReadBlockFileInfo(int nFile, CBlockFileInfo &info); bool ReadLastBlockFile(int &nFile); diff --git a/src/util/error.cpp b/src/util/error.cpp index 193265c842..309877d067 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -33,6 +33,8 @@ bilingual_str TransactionErrorString(const TransactionError err) return Untranslated("Specified sighash value does not match value stored in PSBT"); case TransactionError::MAX_FEE_EXCEEDED: return Untranslated("Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)"); + case TransactionError::MAX_BURN_EXCEEDED: + return Untranslated("Unspendable output exceeds maximum configured by user (maxburnamount)"); case TransactionError::EXTERNAL_SIGNER_NOT_FOUND: return Untranslated("External signer not found"); case TransactionError::EXTERNAL_SIGNER_FAILED: diff --git a/src/util/error.h b/src/util/error.h index 649200c98e..a52a8f47de 100644 --- a/src/util/error.h +++ b/src/util/error.h @@ -30,6 +30,7 @@ enum class TransactionError { PSBT_MISMATCH, SIGHASH_MISMATCH, MAX_FEE_EXCEEDED, + MAX_BURN_EXCEEDED, EXTERNAL_SIGNER_NOT_FOUND, EXTERNAL_SIGNER_FAILED, INVALID_PACKAGE, diff --git a/src/util/system.cpp b/src/util/system.cpp index e72c970157..77b659df7e 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -417,8 +417,7 @@ const fs::path& ArgsManager::GetDataDir(bool net_specific) const LOCK(cs_args); fs::path& path = net_specific ? m_cached_network_datadir_path : m_cached_datadir_path; - // Cache the path to avoid calling fs::create_directories on every call of - // this function + // Used cached path if available if (!path.empty()) return path; const fs::path datadir{GetPathArg("-datadir")}; @@ -432,20 +431,34 @@ const fs::path& ArgsManager::GetDataDir(bool net_specific) const path = GetDefaultDataDir(); } - if (!fs::exists(path)) { - fs::create_directories(path / "wallets"); - } - if (net_specific && !BaseParams().DataDir().empty()) { path /= fs::PathFromString(BaseParams().DataDir()); - if (!fs::exists(path)) { - fs::create_directories(path / "wallets"); - } } return path; } +void ArgsManager::EnsureDataDir() const +{ + /** + * "/wallets" subdirectories are created in all **new** + * datadirs, because wallet code will create new wallets in the "wallets" + * subdirectory only if exists already, otherwise it will create them in + * the top-level datadir where they could interfere with other files. + * Wallet init code currently avoids creating "wallets" directories itself + * for backwards compatibility, but this be changed in the future and + * wallet code here could go away (#16220). + */ + auto path{GetDataDir(false)}; + if (!fs::exists(path)) { + fs::create_directories(path / "wallets"); + } + path = GetDataDir(true); + if (!fs::exists(path)) { + fs::create_directories(path / "wallets"); + } +} + void ArgsManager::ClearPathCache() { LOCK(cs_args); @@ -491,6 +504,7 @@ bool ArgsManager::IsArgSet(const std::string& strArg) const bool ArgsManager::InitSettings(std::string& error) { + EnsureDataDir(); if (!GetSettingsPath()) { return true; // Do nothing if settings file disabled. } @@ -965,6 +979,11 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file return true; } +fs::path ArgsManager::GetConfigFilePath() const +{ + return GetConfigFile(GetPathArg("-conf", BITCOIN_CONF_FILENAME)); +} + bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) { { @@ -973,8 +992,8 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) m_config_sections.clear(); } - const fs::path conf_path = GetPathArg("-conf", BITCOIN_CONF_FILENAME); - std::ifstream stream{GetConfigFile(conf_path)}; + const auto conf_path{GetConfigFilePath()}; + std::ifstream stream{conf_path}; // not ok to have a config file specified that cannot be opened if (IsArgSet("-conf") && !stream.good()) { diff --git a/src/util/system.h b/src/util/system.h index c053adf8c3..14f093501a 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -242,6 +242,11 @@ protected: void SelectConfigNetwork(const std::string& network); [[nodiscard]] bool ParseParameters(int argc, const char* const argv[], std::string& error); + + /** + * Return config file path (read-only) + */ + fs::path GetConfigFilePath() const; [[nodiscard]] bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false); /** @@ -475,13 +480,18 @@ protected: */ void LogArgs() const; + /** + * If datadir does not exist, create it along with wallets/ + * subdirectory(s). + */ + void EnsureDataDir() const; + private: /** * Get data directory path * * @param net_specific Append network identifier to the returned path * @return Absolute path on success, otherwise an empty path when a non-directory path would be returned - * @post Returned directory path is created unless it is empty */ const fs::path& GetDataDir(bool net_specific) const; diff --git a/src/validation.cpp b/src/validation.cpp index f0ffb748dd..1357de3c01 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1511,13 +1511,9 @@ CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams) return nSubsidy; } -CoinsViews::CoinsViews( - fs::path ldb_name, - size_t cache_size_bytes, - bool in_memory, - bool should_wipe) : m_dbview( - gArgs.GetDataDirNet() / ldb_name, cache_size_bytes, in_memory, should_wipe), - m_catcherview(&m_dbview) {} +CoinsViews::CoinsViews(DBParams db_params, CoinsViewOptions options) + : m_dbview{std::move(db_params), std::move(options)}, + m_catcherview(&m_dbview) {} void CoinsViews::InitCache() { @@ -1546,7 +1542,14 @@ void Chainstate::InitCoinsDB( } m_coins_views = std::make_unique<CoinsViews>( - leveldb_name, cache_size_bytes, in_memory, should_wipe); + DBParams{ + .path = m_chainman.m_options.datadir / leveldb_name, + .cache_bytes = cache_size_bytes, + .memory_only = in_memory, + .wipe_data = should_wipe, + .obfuscate = true, + .options = m_chainman.m_options.coins_db}, + m_chainman.m_options.coins_view); } void Chainstate::InitCoinsCache(size_t cache_size_bytes) @@ -4057,7 +4060,7 @@ CVerifyDB::~CVerifyDB() uiInterface.ShowProgress("", 100, false); } -bool CVerifyDB::VerifyDB( +VerifyDBResult CVerifyDB::VerifyDB( Chainstate& chainstate, const Consensus::Params& consensus_params, CCoinsView& coinsview, @@ -4066,7 +4069,7 @@ bool CVerifyDB::VerifyDB( AssertLockHeld(cs_main); if (chainstate.m_chain.Tip() == nullptr || chainstate.m_chain.Tip()->pprev == nullptr) { - return true; + return VerifyDBResult::SUCCESS; } // Verify blocks in the best chain @@ -4081,6 +4084,7 @@ bool CVerifyDB::VerifyDB( int nGoodTransactions = 0; BlockValidationState state; int reportDone = 0; + bool skipped_no_block_data{false}; bool skipped_l3_checks{false}; LogPrintf("Verification progress: 0%%\n"); @@ -4100,25 +4104,29 @@ bool CVerifyDB::VerifyDB( if ((chainstate.m_blockman.IsPruneMode() || is_snapshot_cs) && !(pindex->nStatus & BLOCK_HAVE_DATA)) { // If pruning or running under an assumeutxo snapshot, only go // back as far as we have data. - LogPrintf("VerifyDB(): block verification stopping at height %d (pruning, no data)\n", pindex->nHeight); + LogPrintf("VerifyDB(): block verification stopping at height %d (no data). This could be due to pruning or use of an assumeutxo snapshot.\n", pindex->nHeight); + skipped_no_block_data = true; break; } CBlock block; // check level 0: read from disk if (!ReadBlockFromDisk(block, pindex, consensus_params)) { - return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); + LogPrintf("Verification error: ReadBlockFromDisk failed at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); + return VerifyDBResult::CORRUPTED_BLOCK_DB; } // check level 1: verify block validity if (nCheckLevel >= 1 && !CheckBlock(block, state, consensus_params)) { - return error("%s: *** found bad block at %d, hash=%s (%s)\n", __func__, - pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString()); + LogPrintf("Verification error: found bad block at %d, hash=%s (%s)\n", + pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString()); + return VerifyDBResult::CORRUPTED_BLOCK_DB; } // check level 2: verify undo validity if (nCheckLevel >= 2 && pindex) { CBlockUndo undo; if (!pindex->GetUndoPos().IsNull()) { if (!UndoReadFromDisk(undo, pindex)) { - return error("VerifyDB(): *** found bad undo data at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); + LogPrintf("Verification error: found bad undo data at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); + return VerifyDBResult::CORRUPTED_BLOCK_DB; } } } @@ -4130,7 +4138,8 @@ bool CVerifyDB::VerifyDB( assert(coins.GetBestBlock() == pindex->GetBlockHash()); DisconnectResult res = chainstate.DisconnectBlock(block, pindex, coins); if (res == DISCONNECT_FAILED) { - return error("VerifyDB(): *** irrecoverable inconsistency in block data at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); + LogPrintf("Verification error: irrecoverable inconsistency in block data at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); + return VerifyDBResult::CORRUPTED_BLOCK_DB; } if (res == DISCONNECT_UNCLEAN) { nGoodTransactions = 0; @@ -4142,14 +4151,16 @@ bool CVerifyDB::VerifyDB( skipped_l3_checks = true; } } - if (ShutdownRequested()) return true; + if (ShutdownRequested()) return VerifyDBResult::INTERRUPTED; } if (pindexFailure) { - return error("VerifyDB(): *** coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", chainstate.m_chain.Height() - pindexFailure->nHeight + 1, nGoodTransactions); + LogPrintf("Verification error: coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", chainstate.m_chain.Height() - pindexFailure->nHeight + 1, nGoodTransactions); + return VerifyDBResult::CORRUPTED_BLOCK_DB; } if (skipped_l3_checks) { LogPrintf("Skipped verification of level >=3 (insufficient database cache size). Consider increasing -dbcache.\n"); } + // store block count as we move pindex at check level >= 4 int block_count = chainstate.m_chain.Height() - pindex->nHeight; @@ -4165,18 +4176,27 @@ bool CVerifyDB::VerifyDB( uiInterface.ShowProgress(_("Verifying blocks…").translated, percentageDone, false); pindex = chainstate.m_chain.Next(pindex); CBlock block; - if (!ReadBlockFromDisk(block, pindex, consensus_params)) - return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); + if (!ReadBlockFromDisk(block, pindex, consensus_params)) { + LogPrintf("Verification error: ReadBlockFromDisk failed at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); + return VerifyDBResult::CORRUPTED_BLOCK_DB; + } if (!chainstate.ConnectBlock(block, state, pindex, coins)) { - return error("VerifyDB(): *** found unconnectable block at %d, hash=%s (%s)", pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString()); + LogPrintf("Verification error: found unconnectable block at %d, hash=%s (%s)\n", pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString()); + return VerifyDBResult::CORRUPTED_BLOCK_DB; } - if (ShutdownRequested()) return true; + if (ShutdownRequested()) return VerifyDBResult::INTERRUPTED; } } LogPrintf("Verification: No coin database inconsistencies in last %i blocks (%i transactions)\n", block_count, nGoodTransactions); - return true; + if (skipped_l3_checks) { + return VerifyDBResult::SKIPPED_L3_CHECKS; + } + if (skipped_no_block_data) { + return VerifyDBResult::SKIPPED_MISSING_BLOCKS; + } + return VerifyDBResult::SUCCESS; } /** Apply the effects of a block on the utxo cache, ignoring that it may already have been applied. */ diff --git a/src/validation.h b/src/validation.h index 7170467b00..36c6becf4f 100644 --- a/src/validation.h +++ b/src/validation.h @@ -349,12 +349,20 @@ bool HasValidProofOfWork(const std::vector<CBlockHeader>& headers, const Consens /** Return the sum of the work on a given set of headers */ arith_uint256 CalculateHeadersWork(const std::vector<CBlockHeader>& headers); +enum class VerifyDBResult { + SUCCESS, + CORRUPTED_BLOCK_DB, + INTERRUPTED, + SKIPPED_L3_CHECKS, + SKIPPED_MISSING_BLOCKS, +}; + /** RAII wrapper for VerifyDB: Verify consistency of the block and coin databases */ class CVerifyDB { public: CVerifyDB(); ~CVerifyDB(); - bool VerifyDB( + [[nodiscard]] VerifyDBResult VerifyDB( Chainstate& chainstate, const Consensus::Params& consensus_params, CCoinsView& coinsview, @@ -408,7 +416,7 @@ public: //! state to disk, which should not be done until the health of the database is verified. //! //! All arguments forwarded onto CCoinsViewDB. - CoinsViews(fs::path ldb_name, size_t cache_size_bytes, bool in_memory, bool should_wipe); + CoinsViews(DBParams db_params, CoinsViewOptions options); //! Initialize the CCoinsViewCache member. void InitCache() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); diff --git a/src/wallet/dump.cpp b/src/wallet/dump.cpp index 403ec711ff..69208c19dc 100644 --- a/src/wallet/dump.cpp +++ b/src/wallet/dump.cpp @@ -5,6 +5,7 @@ #include <wallet/dump.h> #include <fs.h> +#include <util/system.h> #include <util/translation.h> #include <wallet/wallet.h> @@ -202,7 +203,7 @@ bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs:: // dummy chain interface bool ret = true; - std::shared_ptr<CWallet> wallet(new CWallet(/*chain=*/nullptr, name, gArgs, std::move(database)), WalletToolReleaseWallet); + std::shared_ptr<CWallet> wallet(new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet); { LOCK(wallet->cs_wallet); DBErrors load_wallet_ret = wallet->LoadWallet(); diff --git a/src/wallet/external_signer_scriptpubkeyman.h b/src/wallet/external_signer_scriptpubkeyman.h index 9918979a81..01dc80b1ca 100644 --- a/src/wallet/external_signer_scriptpubkeyman.h +++ b/src/wallet/external_signer_scriptpubkeyman.h @@ -13,11 +13,11 @@ namespace wallet { class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan { public: - ExternalSignerScriptPubKeyMan(WalletStorage& storage, WalletDescriptor& descriptor) - : DescriptorScriptPubKeyMan(storage, descriptor) + ExternalSignerScriptPubKeyMan(WalletStorage& storage, WalletDescriptor& descriptor, int64_t keypool_size) + : DescriptorScriptPubKeyMan(storage, descriptor, keypool_size) {} - ExternalSignerScriptPubKeyMan(WalletStorage& storage) - : DescriptorScriptPubKeyMan(storage) + ExternalSignerScriptPubKeyMan(WalletStorage& storage, int64_t keypool_size) + : DescriptorScriptPubKeyMan(storage, keypool_size) {} /** Provide a descriptor at setup time diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index bd158b5985..37a704bfa4 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -155,7 +155,7 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid) } Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<bilingual_str>& errors, - CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, bool require_mine) + CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, bool require_mine, const std::vector<CTxOut>& outputs) { // We are going to modify coin control later, copy to re-use CCoinControl new_coin_control(coin_control); @@ -222,11 +222,19 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo return result; } - // Fill in recipients(and preserve a single change key if there is one) - // While we're here, calculate the output amount - std::vector<CRecipient> recipients; + // Calculate the old output amount. CAmount output_value = 0; - for (const auto& output : wtx.tx->vout) { + for (const auto& old_output : wtx.tx->vout) { + output_value += old_output.nValue; + } + + old_fee = input_value - output_value; + + // Fill in recipients (and preserve a single change key if there + // is one). If outputs vector is non-empty, replace original + // outputs with its contents, otherwise use original outputs. + std::vector<CRecipient> recipients; + for (const auto& output : outputs.empty() ? wtx.tx->vout : outputs) { if (!OutputIsChange(wallet, output)) { CRecipient recipient = {output.scriptPubKey, output.nValue, false}; recipients.push_back(recipient); @@ -235,11 +243,8 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo ExtractDestination(output.scriptPubKey, change_dest); new_coin_control.destChange = change_dest; } - output_value += output.nValue; } - old_fee = input_value - output_value; - if (coin_control.m_feerate) { // The user provided a feeRate argument. // We calculate this here to avoid compiler warning on the cs_wallet lock diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h index a96871b26f..53cf16e0f1 100644 --- a/src/wallet/feebumper.h +++ b/src/wallet/feebumper.h @@ -51,7 +51,8 @@ Result CreateRateBumpTransaction(CWallet& wallet, CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, - bool require_mine); + bool require_mine, + const std::vector<CTxOut>& outputs); //! Sign the new transaction, //! @return false if the tx couldn't be found or if it was diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 68dd3da9b5..1a76e46c54 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -291,7 +291,8 @@ public: CAmount& new_fee, CMutableTransaction& mtx) override { - return feebumper::CreateRateBumpTransaction(*m_wallet.get(), txid, coin_control, errors, old_fee, new_fee, mtx, /* require_mine= */ true) == feebumper::Result::OK; + std::vector<CTxOut> outputs; // just an empty list of new recipients for now + return feebumper::CreateRateBumpTransaction(*m_wallet.get(), txid, coin_control, errors, old_fee, new_fee, mtx, /* require_mine= */ true, outputs) == feebumper::Result::OK; } bool signBumpTransaction(CMutableTransaction& mtx) override { return feebumper::SignTransaction(*m_wallet.get(), mtx); } bool commitBumpTransaction(const uint256& txid, diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index 93a6bbde20..744537cfbd 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -17,7 +17,6 @@ #include <sync.h> #include <uint256.h> #include <util/bip32.h> -#include <util/system.h> #include <util/time.h> #include <util/translation.h> #include <wallet/rpc/util.h> @@ -1478,7 +1477,7 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c } else { warnings.push_back("Range not given, using default keypool range"); range_start = 0; - range_end = gArgs.GetIntArg("-keypool", DEFAULT_KEYPOOL_SIZE); + range_end = wallet.m_keypool_size; } next_index = range_start; @@ -1651,10 +1650,14 @@ RPCHelpMan importdescriptors() } WalletRescanReserver reserver(*pwallet); - if (!reserver.reserve()) { + if (!reserver.reserve(/*with_passphrase=*/true)) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } + // Ensure that the wallet is not locked for the remainder of this RPC, as + // the passphrase is used to top up the keypool. + LOCK(pwallet->m_relock_mutex); + const UniValue& requests = main_request.params[0]; const int64_t minimum_timestamp = 1; int64_t now = 0; diff --git a/src/wallet/rpc/encrypt.cpp b/src/wallet/rpc/encrypt.cpp index fcf25e01d6..0226d15698 100644 --- a/src/wallet/rpc/encrypt.cpp +++ b/src/wallet/rpc/encrypt.cpp @@ -49,9 +49,7 @@ RPCHelpMan walletpassphrase() // Note that the walletpassphrase is stored in request.params[0] which is not mlock()ed SecureString strWalletPass; strWalletPass.reserve(100); - // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) - // Alternately, find a way to make request.params[0] mlock()'d to begin with. - strWalletPass = request.params[0].get_str().c_str(); + strWalletPass = std::string_view{request.params[0].get_str()}; // Get the timeout nSleepTime = request.params[1].getInt<int64_t>(); @@ -70,7 +68,17 @@ RPCHelpMan walletpassphrase() } if (!pwallet->Unlock(strWalletPass)) { - throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); + // Check if the passphrase has a null character (see #27067 for details) + if (strWalletPass.find('\0') == std::string::npos) { + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); + } else { + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered is incorrect. " + "It contains a null character (ie - a zero byte). " + "If the passphrase was set with a version of this software prior to 25.0, " + "please try again with only the characters up to — but not including — " + "the first null character. If this is successful, please set a new " + "passphrase to avoid this issue in the future."); + } } pwallet->TopUpKeyPool(); @@ -90,7 +98,7 @@ RPCHelpMan walletpassphrase() std::weak_ptr<CWallet> weak_wallet = wallet; pwallet->chain().rpcRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet, relock_time] { if (auto shared_wallet = weak_wallet.lock()) { - LOCK(shared_wallet->cs_wallet); + LOCK2(shared_wallet->m_relock_mutex, shared_wallet->cs_wallet); // Skip if this is not the most recent rpcRunLater callback. if (shared_wallet->nRelockTime != relock_time) return; shared_wallet->Lock(); @@ -122,28 +130,39 @@ RPCHelpMan walletpassphrasechange() std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return UniValue::VNULL; - LOCK(pwallet->cs_wallet); - if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); } - // TODO: get rid of these .c_str() calls by implementing SecureString::operator=(std::string) - // Alternately, find a way to make request.params[0] mlock()'d to begin with. + if (pwallet->IsScanningWithPassphrase()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before changing the passphrase."); + } + + LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet); + SecureString strOldWalletPass; strOldWalletPass.reserve(100); - strOldWalletPass = request.params[0].get_str().c_str(); + strOldWalletPass = std::string_view{request.params[0].get_str()}; SecureString strNewWalletPass; strNewWalletPass.reserve(100); - strNewWalletPass = request.params[1].get_str().c_str(); + strNewWalletPass = std::string_view{request.params[1].get_str()}; if (strOldWalletPass.empty() || strNewWalletPass.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty"); } if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) { - throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); + // Check if the old passphrase had a null character (see #27067 for details) + if (strOldWalletPass.find('\0') == std::string::npos) { + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); + } else { + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The old wallet passphrase entered is incorrect. " + "It contains a null character (ie - a zero byte). " + "If the old passphrase was set with a version of this software prior to 25.0, " + "please try again with only the characters up to — but not including — " + "the first null character."); + } } return UniValue::VNULL; @@ -175,12 +194,16 @@ RPCHelpMan walletlock() std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return UniValue::VNULL; - LOCK(pwallet->cs_wallet); - if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called."); } + if (pwallet->IsScanningWithPassphrase()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before locking the wallet."); + } + + LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet); + pwallet->Lock(); pwallet->nRelockTime = 0; @@ -219,8 +242,6 @@ RPCHelpMan encryptwallet() std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return UniValue::VNULL; - LOCK(pwallet->cs_wallet); - if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: wallet does not contain private keys, nothing to encrypt."); } @@ -229,11 +250,15 @@ RPCHelpMan encryptwallet() throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called."); } - // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) - // Alternately, find a way to make request.params[0] mlock()'d to begin with. + if (pwallet->IsScanningWithPassphrase()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before encrypting the wallet."); + } + + LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet); + SecureString strWalletPass; strWalletPass.reserve(100); - strWalletPass = request.params[0].get_str().c_str(); + strWalletPass = std::string_view{request.params[0].get_str()}; if (strWalletPass.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty"); diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index cab797bbce..88ee6e96b0 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -956,6 +956,26 @@ RPCHelpMan signrawtransactionwithwallet() }; } +// Definition of allowed formats of specifying transaction outputs in +// `bumpfee`, `psbtbumpfee`, `send` and `walletcreatefundedpsbt` RPCs. +static std::vector<RPCArg> OutputsDoc() +{ + return + { + {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "", + { + {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address,\n" + "the value (float or string) is the amount in " + CURRENCY_UNIT + ""}, + }, + }, + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", + { + {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, + }, + }, + }; +} + static RPCHelpMan bumpfee_helper(std::string method_name) { const bool want_psbt = method_name == "psbtbumpfee"; @@ -992,7 +1012,12 @@ static RPCHelpMan bumpfee_helper(std::string method_name) "still be replaceable in practice, for example if it has unconfirmed ancestors which\n" "are replaceable).\n"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" - "\"" + FeeModes("\"\n\"") + "\""}, + "\"" + FeeModes("\"\n\"") + "\""}, + {"outputs", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "New outputs (key-value pairs) which will replace\n" + "the original ones, if provided. Each address can only appear once and there can\n" + "only be one \"data\" object.\n", + OutputsDoc(), + RPCArgOptions{.skip_type_check = true}}, }, RPCArgOptions{.oneline_description="options"}}, }, @@ -1029,6 +1054,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) coin_control.fAllowWatchOnly = pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); // optional parameters coin_control.m_signal_bip125_rbf = true; + std::vector<CTxOut> outputs; if (!request.params[1].isNull()) { UniValue options = request.params[1]; @@ -1039,6 +1065,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) {"fee_rate", UniValueType()}, // will be checked by AmountFromValue() in SetFeeEstimateMode() {"replaceable", UniValueType(UniValue::VBOOL)}, {"estimate_mode", UniValueType(UniValue::VSTR)}, + {"outputs", UniValueType()}, // will be checked by AddOutputs() }, true, true); @@ -1052,6 +1079,16 @@ static RPCHelpMan bumpfee_helper(std::string method_name) coin_control.m_signal_bip125_rbf = options["replaceable"].get_bool(); } SetFeeEstimateMode(*pwallet, coin_control, conf_target, options["estimate_mode"], options["fee_rate"], /*override_min_fee=*/false); + + // Prepare new outputs by creating a temporary tx and calling AddOutputs(). + if (!options["outputs"].isNull()) { + if (options["outputs"].isArray() && options["outputs"].empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument cannot be an empty array"); + } + CMutableTransaction tempTx; + AddOutputs(tempTx, options["outputs"]); + outputs = tempTx.vout; + } } // Make sure the results are valid at least up to the most recent block @@ -1069,7 +1106,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) CMutableTransaction mtx; feebumper::Result res; // Targeting feerate bump. - res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt); + res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt, outputs); if (res != feebumper::Result::OK) { switch(res) { case feebumper::Result::INVALID_ADDRESS_OR_KEY: @@ -1144,18 +1181,7 @@ RPCHelpMan send() {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The outputs (key-value pairs), where none of the keys are duplicated.\n" "That is, each address can only appear once and there can only be one 'data' object.\n" "For convenience, a dictionary, which holds the key-value pairs directly, is also accepted.", - { - {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "", - { - {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + ""}, - }, - }, - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", - { - {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, - }, - }, - }, + OutputsDoc(), RPCArgOptions{.skip_type_check = true}}, {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" @@ -1606,19 +1632,8 @@ RPCHelpMan walletcreatefundedpsbt() "That is, each address can only appear once and there can only be one 'data' object.\n" "For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" "accepted as second parameter.", - { - {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "", - { - {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + ""}, - }, - }, - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", - { - {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, - }, - }, - }, - RPCArgOptions{.skip_type_check = true}}, + OutputsDoc(), + RPCArgOptions{.skip_type_check = true}}, {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", Cat<std::vector<RPCArg>>( diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp index e590aa1f08..3bfe296d90 100644 --- a/src/wallet/rpc/transactions.cpp +++ b/src/wallet/rpc/transactions.cpp @@ -872,15 +872,18 @@ RPCHelpMan rescanblockchain() wallet.BlockUntilSyncedToCurrentChain(); WalletRescanReserver reserver(*pwallet); - if (!reserver.reserve()) { + if (!reserver.reserve(/*with_passphrase=*/true)) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } int start_height = 0; std::optional<int> stop_height; uint256 start_block; + + LOCK(pwallet->m_relock_mutex); { LOCK(pwallet->cs_wallet); + EnsureWalletIsUnlocked(*pwallet); int tip_height = pwallet->GetLastBlockHeight(); if (!request.params[0].isNull()) { diff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp index 31435a69ba..4d82e0a41f 100644 --- a/src/wallet/rpc/util.cpp +++ b/src/wallet/rpc/util.cpp @@ -6,6 +6,7 @@ #include <common/url.h> #include <rpc/util.h> +#include <util/system.h> #include <util/translation.h> #include <wallet/context.h> #include <wallet/wallet.h> diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index 23a88cd51b..16595267b4 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -359,7 +359,7 @@ static RPCHelpMan createwallet() passphrase.reserve(100); std::vector<bilingual_str> warnings; if (!request.params[3].isNull()) { - passphrase = request.params[3].get_str().c_str(); + passphrase = std::string_view{request.params[3].get_str()}; if (passphrase.empty()) { // Empty string means unencrypted warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted.")); @@ -720,9 +720,12 @@ static RPCHelpMan migratewallet() "A new wallet backup will need to be made.\n" "\nThe migration process will create a backup of the wallet before migrating. This backup\n" "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n" - "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet." + - HELP_REQUIRING_PASSPHRASE, - {}, + "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet." + "\nEncrypted wallets must have the passphrase provided as an argument to this call.", + { + {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to migrate. If provided both here and in the RPC endpoint, the two must be identical."}, + {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"}, + }, RPCResult{ RPCResult::Type::OBJ, "", "", { @@ -738,16 +741,26 @@ static RPCHelpMan migratewallet() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; + std::string wallet_name; + if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) { + if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets"); + } + } else { + if (request.params[0].isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Either RPC endpoint wallet or wallet_name parameter must be provided"); + } + wallet_name = request.params[0].get_str(); + } - if (wallet->IsCrypted()) { - throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: migratewallet on encrypted wallets is currently unsupported."); + SecureString wallet_pass; + wallet_pass.reserve(100); + if (!request.params[1].isNull()) { + wallet_pass = std::string_view{request.params[1].get_str()}; } WalletContext& context = EnsureWalletContext(request.context); - - util::Result<MigrationResult> res = MigrateLegacyToDescriptor(std::move(wallet), context); + util::Result<MigrationResult> res = MigrateLegacyToDescriptor(wallet_name, wallet_pass, context); if (!res) { throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original); } diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp index 84f33e50b3..e2b4dbf4c2 100644 --- a/src/wallet/salvage.cpp +++ b/src/wallet/salvage.cpp @@ -135,7 +135,7 @@ bool RecoverDatabaseFile(const ArgsManager& args, const fs::path& file_path, bil } DbTxn* ptxn = env->TxnBegin(); - CWallet dummyWallet(nullptr, "", gArgs, CreateDummyWalletDatabase()); + CWallet dummyWallet(nullptr, "", CreateDummyWalletDatabase()); for (KeyValPair& row : salvagedData) { /* Filter for only private key type KV pairs to be added to the salvaged wallet */ diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 59cf87355b..c109533d7a 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -11,7 +11,6 @@ #include <util/bip32.h> #include <util/strencodings.h> #include <util/string.h> -#include <util/system.h> #include <util/time.h> #include <util/translation.h> #include <wallet/scriptpubkeyman.h> @@ -1294,7 +1293,7 @@ bool LegacyScriptPubKeyMan::TopUpChain(CHDChain& chain, unsigned int kpSize) if (kpSize > 0) { nTargetSize = kpSize; } else { - nTargetSize = std::max(gArgs.GetIntArg("-keypool", DEFAULT_KEYPOOL_SIZE), int64_t{0}); + nTargetSize = m_keypool_size; } int64_t target = std::max((int64_t) nTargetSize, int64_t{1}); @@ -1784,7 +1783,7 @@ std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor() WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0); // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys - auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc)); + auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc, m_keypool_size)); desc_spk_man->AddDescriptorKey(key, key.GetPubKey()); desc_spk_man->TopUp(); auto desc_spks = desc_spk_man->GetScriptPubKeys(); @@ -1829,7 +1828,7 @@ std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor() WalletDescriptor w_desc(std::move(desc), 0, 0, chain_counter, 0); // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys - auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc)); + auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc, m_keypool_size)); desc_spk_man->AddDescriptorKey(master_key.key, master_key.key.GetPubKey()); desc_spk_man->TopUp(); auto desc_spks = desc_spk_man->GetScriptPubKeys(); @@ -1891,7 +1890,7 @@ std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor() } else { // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0); - auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc)); + auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc, m_keypool_size)); for (const auto& keyid : privkeyids) { CKey key; if (!GetKey(keyid, key)) { @@ -2122,7 +2121,7 @@ bool DescriptorScriptPubKeyMan::TopUp(unsigned int size) if (size > 0) { target_size = size; } else { - target_size = std::max(gArgs.GetIntArg("-keypool", DEFAULT_KEYPOOL_SIZE), int64_t{1}); + target_size = m_keypool_size; } // Calculate the new range_end diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 4399ac2087..4d14325241 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -286,6 +286,9 @@ private: int64_t nTimeFirstKey GUARDED_BY(cs_KeyStore) = 0; + //! Number of pre-generated keys/scripts (part of the look-ahead process, used to detect payments) + int64_t m_keypool_size GUARDED_BY(cs_KeyStore){DEFAULT_KEYPOOL_SIZE}; + bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey); bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); @@ -363,7 +366,7 @@ private: bool TopUpChain(CHDChain& chain, unsigned int size); public: - using ScriptPubKeyMan::ScriptPubKeyMan; + LegacyScriptPubKeyMan(WalletStorage& storage, int64_t keypool_size) : ScriptPubKeyMan(storage), m_keypool_size(keypool_size) {} util::Result<CTxDestination> GetNewDestination(const OutputType type) override; isminetype IsMine(const CScript& script) const override; @@ -555,6 +558,9 @@ private: //! keeps track of whether Unlock has run a thorough check before bool m_decryption_thoroughly_checked = false; + //! Number of pre-generated keys/scripts (part of the look-ahead process, used to detect payments) + int64_t m_keypool_size GUARDED_BY(cs_desc_man){DEFAULT_KEYPOOL_SIZE}; + bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man); KeyMap GetKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man); @@ -572,12 +578,14 @@ protected: WalletDescriptor m_wallet_descriptor GUARDED_BY(cs_desc_man); public: - DescriptorScriptPubKeyMan(WalletStorage& storage, WalletDescriptor& descriptor) + DescriptorScriptPubKeyMan(WalletStorage& storage, WalletDescriptor& descriptor, int64_t keypool_size) : ScriptPubKeyMan(storage), + m_keypool_size(keypool_size), m_wallet_descriptor(descriptor) {} - DescriptorScriptPubKeyMan(WalletStorage& storage) - : ScriptPubKeyMan(storage) + DescriptorScriptPubKeyMan(WalletStorage& storage, int64_t keypool_size) + : ScriptPubKeyMan(storage), + m_keypool_size(keypool_size) {} mutable RecursiveMutex cs_desc_man; diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 0ef56ab8ed..1a79b59d12 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -1090,6 +1090,13 @@ util::Result<CreatedTransactionResult> CreateTransaction( TRACE1(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str()); CCoinControl tmp_cc = coin_control; tmp_cc.m_avoid_partial_spends = true; + + // Re-use the change destination from the first creation attempt to avoid skipping BIP44 indexes + const int ungrouped_change_pos = txr_ungrouped.change_pos; + if (ungrouped_change_pos != -1) { + ExtractDestination(txr_ungrouped.tx->vout[ungrouped_change_pos].scriptPubKey, tmp_cc.destChange); + } + auto txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, tmp_cc, sign); // if fee of this alternative one is within the range of the max fee, we use this one const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped.fee + wallet.m_max_aps_fee) : false}; diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 5281d0589a..1c731b95e5 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -294,7 +294,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) coin_selection_params_bnb.m_subtract_fee_outputs = true; { - std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); + std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -316,7 +316,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) } { - std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); + std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -339,7 +339,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) BOOST_CHECK(result10); } { - std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); + std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -404,7 +404,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) FastRandomContext rand{}; const auto temp1{[&rand](std::vector<OutputGroup>& g, const CAmount& v, CAmount c) { return KnapsackSolver(g, v, c, rand); }}; const auto KnapsackSolver{temp1}; - std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); + std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -714,7 +714,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) BOOST_AUTO_TEST_CASE(ApproximateBestSubset) { FastRandomContext rand{}; - std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); + std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -736,7 +736,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset) // Tests that with the ideal conditions, the coin selector will always be able to find a solution that can pay the target value BOOST_AUTO_TEST_CASE(SelectCoins_test) { - std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); + std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -921,9 +921,9 @@ BOOST_AUTO_TEST_CASE(effective_value_test) BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1 } -static util::Result<SelectionResult> select_coins(const CAmount& target, const CoinSelectionParams& cs_params, const CCoinControl& cc, std::function<CoinsResult(CWallet&)> coin_setup, interfaces::Chain* chain, const ArgsManager& args) +static util::Result<SelectionResult> select_coins(const CAmount& target, const CoinSelectionParams& cs_params, const CCoinControl& cc, std::function<CoinsResult(CWallet&)> coin_setup, interfaces::Chain* chain) { - std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(chain, "", args, CreateMockWalletDatabase()); + std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(chain, "", CreateMockWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -984,7 +984,7 @@ BOOST_AUTO_TEST_CASE(check_max_weight) add_coin(available_coins, wallet, CAmount(50 * COIN), CFeeRate(0), 144, false, 0, true); return available_coins; }, - chain, m_args); + chain); BOOST_CHECK(result); BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(50 * COIN))); @@ -1009,7 +1009,7 @@ BOOST_AUTO_TEST_CASE(check_max_weight) } return available_coins; }, - chain, m_args); + chain); BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.0625 * COIN))); BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(0.025 * COIN))); @@ -1030,7 +1030,7 @@ BOOST_AUTO_TEST_CASE(check_max_weight) } return available_coins; }, - chain, m_args); + chain); // No results // 1515 inputs * 68 bytes = 103,020 bytes @@ -1045,7 +1045,7 @@ BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test) // This test creates a coin whose value is higher than the target but whose effective value is lower than the target. // The coin is selected using coin control, with m_allow_other_inputs = false. SelectCoins should fail due to insufficient funds. - std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); + std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -1053,7 +1053,7 @@ BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test) CoinsResult available_coins; { - std::unique_ptr<CWallet> dummyWallet = std::make_unique<CWallet>(m_node.chain.get(), "dummy", m_args, CreateMockWalletDatabase()); + std::unique_ptr<CWallet> dummyWallet = std::make_unique<CWallet>(m_node.chain.get(), "dummy", CreateMockWalletDatabase()); dummyWallet->LoadWallet(); LOCK(dummyWallet->cs_wallet); dummyWallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -1094,7 +1094,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_coinsresult_test, BasicTestingSetup) // Test case to verify CoinsResult object sanity. CoinsResult available_coins; { - std::unique_ptr<CWallet> dummyWallet = std::make_unique<CWallet>(m_node.chain.get(), "dummy", m_args, CreateMockWalletDatabase()); + std::unique_ptr<CWallet> dummyWallet = std::make_unique<CWallet>(m_node.chain.get(), "dummy", CreateMockWalletDatabase()); BOOST_CHECK_EQUAL(dummyWallet->LoadWallet(), DBErrors::LOAD_OK); LOCK(dummyWallet->cs_wallet); dummyWallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); diff --git a/src/wallet/test/ismine_tests.cpp b/src/wallet/test/ismine_tests.cpp index 151b09d2a6..90f369b22a 100644 --- a/src/wallet/test/ismine_tests.cpp +++ b/src/wallet/test/ismine_tests.cpp @@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2PK compressed - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); scriptPubKey = GetScriptForRawPubKey(pubkeys[0]); @@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2PK compressed - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "pk(" + EncodeSecret(keys[0]) + ")"; auto spk_manager = CreateDescriptor(keystore, desc_str, true); @@ -86,7 +86,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2PK uncompressed - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); scriptPubKey = GetScriptForRawPubKey(uncompressedPubkey); @@ -105,7 +105,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2PK uncompressed - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "pk(" + EncodeSecret(uncompressedKey) + ")"; auto spk_manager = CreateDescriptor(keystore, desc_str, true); @@ -117,7 +117,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2PKH compressed - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); scriptPubKey = GetScriptForDestination(PKHash(pubkeys[0])); @@ -136,7 +136,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2PKH compressed - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "pkh(" + EncodeSecret(keys[0]) + ")"; auto spk_manager = CreateDescriptor(keystore, desc_str, true); @@ -148,7 +148,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2PKH uncompressed - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); scriptPubKey = GetScriptForDestination(PKHash(uncompressedPubkey)); @@ -167,7 +167,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2PKH uncompressed - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "pkh(" + EncodeSecret(uncompressedKey) + ")"; auto spk_manager = CreateDescriptor(keystore, desc_str, true); @@ -179,7 +179,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2SH - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); @@ -206,7 +206,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2SH - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "sh(pkh(" + EncodeSecret(keys[0]) + "))"; auto spk_manager = CreateDescriptor(keystore, desc_str, true); @@ -219,7 +219,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // (P2PKH inside) P2SH inside P2SH (invalid) - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); @@ -238,7 +238,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // (P2PKH inside) P2SH inside P2SH (invalid) - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "sh(sh(" + EncodeSecret(keys[0]) + "))"; auto spk_manager = CreateDescriptor(keystore, desc_str, false); @@ -247,7 +247,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // (P2PKH inside) P2SH inside P2WSH (invalid) - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); @@ -266,7 +266,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // (P2PKH inside) P2SH inside P2WSH (invalid) - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "wsh(sh(" + EncodeSecret(keys[0]) + "))"; auto spk_manager = CreateDescriptor(keystore, desc_str, false); @@ -275,7 +275,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2WPKH inside P2WSH (invalid) - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); @@ -292,7 +292,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2WPKH inside P2WSH (invalid) - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "wsh(wpkh(" + EncodeSecret(keys[0]) + "))"; auto spk_manager = CreateDescriptor(keystore, desc_str, false); @@ -301,7 +301,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // (P2PKH inside) P2WSH inside P2WSH (invalid) - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); @@ -320,7 +320,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // (P2PKH inside) P2WSH inside P2WSH (invalid) - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "wsh(wsh(" + EncodeSecret(keys[0]) + "))"; auto spk_manager = CreateDescriptor(keystore, desc_str, false); @@ -329,7 +329,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2WPKH compressed - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); @@ -345,7 +345,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2WPKH compressed - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "wpkh(" + EncodeSecret(keys[0]) + ")"; auto spk_manager = CreateDescriptor(keystore, desc_str, true); @@ -357,7 +357,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2WPKH uncompressed - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey)); @@ -378,7 +378,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2WPKH uncompressed (invalid) - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "wpkh(" + EncodeSecret(uncompressedKey) + ")"; auto spk_manager = CreateDescriptor(keystore, desc_str, false); @@ -387,7 +387,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // scriptPubKey multisig - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); @@ -422,7 +422,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // scriptPubKey multisig - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "multi(2, " + EncodeSecret(uncompressedKey) + ", " + EncodeSecret(keys[1]) + ")"; auto spk_manager = CreateDescriptor(keystore, desc_str, true); @@ -434,7 +434,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2SH multisig - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey)); @@ -457,7 +457,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2SH multisig - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "sh(multi(2, " + EncodeSecret(uncompressedKey) + ", " + EncodeSecret(keys[1]) + "))"; @@ -471,7 +471,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2WSH multisig with compressed keys - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); @@ -500,7 +500,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2WSH multisig with compressed keys - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "wsh(multi(2, " + EncodeSecret(keys[0]) + ", " + EncodeSecret(keys[1]) + "))"; @@ -514,7 +514,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2WSH multisig with uncompressed key - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey)); @@ -543,7 +543,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2WSH multisig with uncompressed key (invalid) - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "wsh(multi(2, " + EncodeSecret(uncompressedKey) + ", " + EncodeSecret(keys[1]) + "))"; @@ -553,7 +553,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2WSH multisig wrapped in P2SH - Legacy { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); @@ -583,7 +583,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2WSH multisig wrapped in P2SH - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "sh(wsh(multi(2, " + EncodeSecret(keys[0]) + ", " + EncodeSecret(keys[1]) + ")))"; @@ -598,7 +598,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Combo - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "combo(" + EncodeSecret(keys[0]) + ")"; @@ -642,7 +642,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Taproot - Descriptor { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); std::string desc_str = "tr(" + EncodeSecret(keys[0]) + ")"; @@ -660,7 +660,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // OP_RETURN { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); @@ -675,7 +675,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // witness unspendable { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); @@ -690,7 +690,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // witness unknown { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); @@ -705,7 +705,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Nonstandard { - CWallet keystore(chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), "", CreateDummyWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); diff --git a/src/wallet/test/scriptpubkeyman_tests.cpp b/src/wallet/test/scriptpubkeyman_tests.cpp index a524b85ccb..90042f5252 100644 --- a/src/wallet/test/scriptpubkeyman_tests.cpp +++ b/src/wallet/test/scriptpubkeyman_tests.cpp @@ -18,7 +18,7 @@ BOOST_FIXTURE_TEST_SUITE(scriptpubkeyman_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(CanProvide) { // Set up wallet and keyman variables. - CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); LegacyScriptPubKeyMan& keyman = *wallet.GetOrCreateLegacyScriptPubKeyMan(); // Make a 1 of 2 multisig script diff --git a/src/wallet/test/spend_tests.cpp b/src/wallet/test/spend_tests.cpp index 364cc5c20b..6e87d1cb49 100644 --- a/src/wallet/test/spend_tests.cpp +++ b/src/wallet/test/spend_tests.cpp @@ -18,7 +18,7 @@ BOOST_FIXTURE_TEST_SUITE(spend_tests, WalletTestingSetup) BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup) { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - auto wallet = CreateSyncedWallet(*m_node.chain, WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain()), m_args, coinbaseKey); + auto wallet = CreateSyncedWallet(*m_node.chain, WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain()), coinbaseKey); // Check that a subtract-from-recipient transaction slightly less than the // coinbase input amount does not create a change output (because it would @@ -118,7 +118,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_duplicated_preset_inputs_test, TestChain100Setup) // Add 4 spendable UTXO, 50 BTC each, to the wallet (total balance 200 BTC) for (int i = 0; i < 4; i++) CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - auto wallet = CreateSyncedWallet(*m_node.chain, WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain()), m_args, coinbaseKey); + auto wallet = CreateSyncedWallet(*m_node.chain, WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain()), coinbaseKey); LOCK(wallet->cs_wallet); auto available_coins = AvailableCoins(*wallet); diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp index 225871fd91..b7bf312edf 100644 --- a/src/wallet/test/util.cpp +++ b/src/wallet/test/util.cpp @@ -14,9 +14,9 @@ #include <memory> namespace wallet { -std::unique_ptr<CWallet> CreateSyncedWallet(interfaces::Chain& chain, CChain& cchain, ArgsManager& args, const CKey& key) +std::unique_ptr<CWallet> CreateSyncedWallet(interfaces::Chain& chain, CChain& cchain, const CKey& key) { - auto wallet = std::make_unique<CWallet>(&chain, "", args, CreateMockWalletDatabase()); + auto wallet = std::make_unique<CWallet>(&chain, "", CreateMockWalletDatabase()); { LOCK2(wallet->cs_wallet, ::cs_main); wallet->SetLastBlockProcessed(cchain.Height(), cchain.Tip()->GetBlockHash()); diff --git a/src/wallet/test/util.h b/src/wallet/test/util.h index 635a5152ec..d726517e21 100644 --- a/src/wallet/test/util.h +++ b/src/wallet/test/util.h @@ -21,7 +21,7 @@ class CWallet; struct DatabaseOptions; class WalletDatabase; -std::unique_ptr<CWallet> CreateSyncedWallet(interfaces::Chain& chain, CChain& cchain, ArgsManager& args, const CKey& key); +std::unique_ptr<CWallet> CreateSyncedWallet(interfaces::Chain& chain, CChain& cchain, const CKey& key); // Creates a copy of the provided database std::unique_ptr<WalletDatabase> DuplicateMockDatabase(WalletDatabase& database, DatabaseOptions& options); diff --git a/src/wallet/test/wallet_crypto_tests.cpp b/src/wallet/test/wallet_crypto_tests.cpp index 6b8542f378..d5e75bb892 100644 --- a/src/wallet/test/wallet_crypto_tests.cpp +++ b/src/wallet/test/wallet_crypto_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <test/util/random.h> #include <test/util/setup_common.h> #include <util/strencodings.h> #include <wallet/crypter.h> diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index c47e56c093..2dd8f9ad33 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -10,7 +10,7 @@ namespace wallet { WalletTestingSetup::WalletTestingSetup(const std::string& chainName) : TestingSetup(chainName), m_wallet_loader{interfaces::MakeWalletLoader(*m_node.chain, *Assert(m_node.args))}, - m_wallet(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()) + m_wallet(m_node.chain.get(), "", CreateMockWalletDatabase()) { m_wallet.LoadWallet(); m_chain_notifications_handler = m_node.chain->handleNotifications({ &m_wallet, [](CWallet*) {} }); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index f056c54734..2e95a14807 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -98,7 +98,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions fails to read an unknown start block. { - CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); LOCK(Assert(m_node.chainman)->GetMutex()); @@ -119,7 +119,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { - CWallet wallet(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); + CWallet wallet(m_node.chain.get(), "", CreateMockWalletDatabase()); { LOCK(wallet.cs_wallet); LOCK(Assert(m_node.chainman)->GetMutex()); @@ -164,7 +164,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions only picks transactions in the new block // file. { - CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); LOCK(Assert(m_node.chainman)->GetMutex()); @@ -192,7 +192,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions scans no blocks. { - CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); LOCK(Assert(m_node.chainman)->GetMutex()); @@ -232,7 +232,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) // before the missing block, and success for a key whose creation time is // after. { - const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); + const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase()); wallet->SetupLegacyScriptPubKeyMan(); WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash())); WalletContext context; @@ -298,7 +298,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) { WalletContext context; context.args = &m_args; - const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); + const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase()); { auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan(); LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore); @@ -321,7 +321,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME // were scanned, and no prior blocks were scanned. { - const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); + const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase()); LOCK(wallet->cs_wallet); wallet->SetupLegacyScriptPubKeyMan(); @@ -355,7 +355,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // debit functions. BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { - CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); LOCK(wallet.cs_wallet); LOCK(Assert(m_node.chainman)->GetMutex()); @@ -527,7 +527,7 @@ public: ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - wallet = CreateSyncedWallet(*m_node.chain, WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain()), m_args, coinbaseKey); + wallet = CreateSyncedWallet(*m_node.chain, WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain()), coinbaseKey); } ~ListCoinsTestingSetup() @@ -665,7 +665,7 @@ BOOST_FIXTURE_TEST_CASE(BasicOutputTypesTest, ListCoinsTest) BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { { - const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); + const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase()); wallet->SetupLegacyScriptPubKeyMan(); wallet->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); @@ -673,7 +673,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, "")); } { - const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); + const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase()); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet->SetMinVersion(FEATURE_LATEST); @@ -961,7 +961,7 @@ public: */ BOOST_FIXTURE_TEST_CASE(wallet_sync_tx_invalid_state_test, TestingSetup) { - CWallet wallet(m_node.chain.get(), "", m_args, std::make_unique<FailDatabase>()); + CWallet wallet(m_node.chain.get(), "", std::make_unique<FailDatabase>()); { LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); diff --git a/src/wallet/test/walletload_tests.cpp b/src/wallet/test/walletload_tests.cpp index f1feb28e7d..9f5a4b14d3 100644 --- a/src/wallet/test/walletload_tests.cpp +++ b/src/wallet/test/walletload_tests.cpp @@ -46,7 +46,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_load_unknown_descriptor, TestingSetup) { // Now try to load the wallet and verify the error. - const std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", m_args, std::move(database))); + const std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(database))); BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::UNKNOWN_DESCRIPTOR); } } @@ -84,7 +84,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_load_verif_crypted_key_checksum, TestingSetup) { // Context setup. // Create and encrypt legacy wallet - std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", m_args, CreateMockWalletDatabase())); + std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockWalletDatabase())); LOCK(wallet->cs_wallet); auto legacy_spkm = wallet->GetOrCreateLegacyScriptPubKeyMan(); BOOST_CHECK(legacy_spkm->SetupGeneration(true)); @@ -112,7 +112,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_load_verif_crypted_key_checksum, TestingSetup) // the records every time that 'CWallet::Unlock' gets called, which is not good. // Load the wallet and check that is encrypted - std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", m_args, get_db(dbs))); + std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", get_db(dbs))); BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::LOAD_OK); BOOST_CHECK(wallet->IsCrypted()); BOOST_CHECK(HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY)); @@ -138,7 +138,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_load_verif_crypted_key_checksum, TestingSetup) } // Load the wallet and check that is encrypted - std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", m_args, std::move(db))); + std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(db))); BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::LOAD_OK); BOOST_CHECK(wallet->IsCrypted()); BOOST_CHECK(HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY)); @@ -166,7 +166,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_load_verif_crypted_key_checksum, TestingSetup) BOOST_CHECK(batch->Write(key, value, /*fOverwrite=*/true)); } - std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", m_args, std::move(db))); + std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(db))); BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::CORRUPT); } @@ -182,7 +182,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_load_verif_crypted_key_checksum, TestingSetup) BOOST_CHECK(db->MakeBatch(false)->Write(key, value, /*fOverwrite=*/true)); } - std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", m_args, std::move(db))); + std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(db))); BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::CORRUPT); } } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b709bd9650..daddd6446d 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -35,6 +35,7 @@ #include <util/moneystr.h> #include <util/rbf.h> #include <util/string.h> +#include <util/system.h> #include <util/translation.h> #include <wallet/coincontrol.h> #include <wallet/context.h> @@ -551,7 +552,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, bool fWasLocked = IsLocked(); { - LOCK(cs_wallet); + LOCK2(m_relock_mutex, cs_wallet); Lock(); CCrypter crypter; @@ -786,7 +787,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) return false; { - LOCK(cs_wallet); + LOCK2(m_relock_mutex, cs_wallet); mapMasterKeys[++nMasterKeyMaxID] = kMasterKey; WalletBatch* encrypted_batch = new WalletBatch(GetDatabase()); if (!encrypted_batch->TxnBegin()) { @@ -1109,7 +1110,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const #if HAVE_SYSTEM // notify an external script when a wallet transaction comes in or is updated - std::string strCmd = m_args.GetArg("-walletnotify", ""); + std::string strCmd = m_notify_tx_changed_script; if (!strCmd.empty()) { @@ -2914,7 +2915,11 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri const auto start{SteadyClock::now()}; // TODO: Can't use std::make_shared because we need a custom deleter but // should be possible to use std::allocate_shared. - std::shared_ptr<CWallet> walletInstance(new CWallet(chain, name, args, std::move(database)), ReleaseWallet); + std::shared_ptr<CWallet> walletInstance(new CWallet(chain, name, std::move(database)), ReleaseWallet); + walletInstance->m_keypool_size = std::max(args.GetIntArg("-keypool", DEFAULT_KEYPOOL_SIZE), int64_t{1}); + walletInstance->m_notify_tx_changed_script = args.GetArg("-walletnotify", ""); + + // Load wallet bool rescan_required = false; DBErrors nLoadWalletRet = walletInstance->LoadWallet(); if (nLoadWalletRet != DBErrors::LOAD_OK) { @@ -3407,7 +3412,7 @@ bool CWallet::Lock() return false; { - LOCK(cs_wallet); + LOCK2(m_relock_mutex, cs_wallet); if (!vMasterKey.empty()) { memory_cleanse(vMasterKey.data(), vMasterKey.size() * sizeof(decltype(vMasterKey)::value_type)); vMasterKey.clear(); @@ -3538,7 +3543,7 @@ void CWallet::SetupLegacyScriptPubKeyMan() return; } - auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new LegacyScriptPubKeyMan(*this)); + auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new LegacyScriptPubKeyMan(*this, m_keypool_size)); for (const auto& type : LEGACY_OUTPUT_TYPES) { m_internal_spk_managers[type] = spk_manager.get(); m_external_spk_managers[type] = spk_manager.get(); @@ -3567,10 +3572,10 @@ void CWallet::ConnectScriptPubKeyManNotifiers() void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc) { if (IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) { - auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, desc)); + auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, desc, m_keypool_size)); m_spk_managers[id] = std::move(spk_manager); } else { - auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc)); + auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc, m_keypool_size)); m_spk_managers[id] = std::move(spk_manager); } } @@ -3581,7 +3586,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key) for (bool internal : {false, true}) { for (OutputType t : OUTPUT_TYPES) { - auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this)); + auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, m_keypool_size)); if (IsCrypted()) { if (IsLocked()) { throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors"); @@ -3637,7 +3642,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans() continue; } OutputType t = *desc->GetOutputType(); - auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this)); + auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, m_keypool_size)); spk_manager->SetupDescriptor(std::move(desc)); uint256 id = spk_manager->GetID(); m_spk_managers[id] = std::move(spk_manager); @@ -3753,7 +3758,7 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat WalletLogPrintf("Update existing descriptor: %s\n", desc.descriptor->ToString()); spk_man->UpdateWalletDescriptor(desc); } else { - auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc)); + auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc, m_keypool_size)); spk_man = new_spk_man.get(); // Save the descriptor to memory @@ -3879,7 +3884,7 @@ std::optional<MigrationData> CWallet::GetDescriptorsForLegacy(bilingual_str& err std::optional<MigrationData> res = legacy_spkm->MigrateToDescriptor(); if (res == std::nullopt) { - error = _("Error: Unable to produce descriptors for this legacy wallet. Make sure the wallet is unlocked first"); + error = _("Error: Unable to produce descriptors for this legacy wallet. Make sure to provide the wallet's passphrase if it is encrypted."); return std::nullopt; } return res; @@ -4169,32 +4174,19 @@ bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error, return true; } -util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>&& wallet, WalletContext& context) +util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& wallet_name, const SecureString& passphrase, WalletContext& context) { - // Before anything else, check if there is something to migrate. - if (!wallet->GetLegacyScriptPubKeyMan()) { - return util::Error{_("Error: This wallet is already a descriptor wallet")}; - } - MigrationResult res; bilingual_str error; std::vector<bilingual_str> warnings; - // Make a backup of the DB - std::string wallet_name = wallet->GetName(); - fs::path this_wallet_dir = fs::absolute(fs::PathFromString(wallet->GetDatabase().Filename())).parent_path(); - fs::path backup_filename = fs::PathFromString(strprintf("%s-%d.legacy.bak", wallet_name, GetTime())); - fs::path backup_path = this_wallet_dir / backup_filename; - if (!wallet->BackupWallet(fs::PathToString(backup_path))) { - return util::Error{_("Error: Unable to make a backup of your wallet")}; - } - res.backup_path = backup_path; - - // Unload the wallet so that nothing else tries to use it while we're changing it - if (!RemoveWallet(context, wallet, /*load_on_start=*/std::nullopt, warnings)) { - return util::Error{_("Unable to unload the wallet before migrating")}; + // If the wallet is still loaded, unload it so that nothing else tries to use it while we're changing it + if (auto wallet = GetWallet(context, wallet_name)) { + if (!RemoveWallet(context, wallet, /*load_on_start=*/std::nullopt, warnings)) { + return util::Error{_("Unable to unload the wallet before migrating")}; + } + UnloadWallet(std::move(wallet)); } - UnloadWallet(std::move(wallet)); // Load the wallet but only in the context of this function. // No signals should be connected nor should anything else be aware of this wallet @@ -4208,15 +4200,43 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet> return util::Error{Untranslated("Wallet file verification failed.") + Untranslated(" ") + error}; } + // Make the local wallet std::shared_ptr<CWallet> local_wallet = CWallet::Create(empty_context, wallet_name, std::move(database), options.create_flags, error, warnings); if (!local_wallet) { return util::Error{Untranslated("Wallet loading failed.") + Untranslated(" ") + error}; } + // Before anything else, check if there is something to migrate. + if (!local_wallet->GetLegacyScriptPubKeyMan()) { + return util::Error{_("Error: This wallet is already a descriptor wallet")}; + } + + // Make a backup of the DB + fs::path this_wallet_dir = fs::absolute(fs::PathFromString(local_wallet->GetDatabase().Filename())).parent_path(); + fs::path backup_filename = fs::PathFromString(strprintf("%s-%d.legacy.bak", wallet_name, GetTime())); + fs::path backup_path = this_wallet_dir / backup_filename; + if (!local_wallet->BackupWallet(fs::PathToString(backup_path))) { + return util::Error{_("Error: Unable to make a backup of your wallet")}; + } + res.backup_path = backup_path; + bool success = false; { LOCK(local_wallet->cs_wallet); + // Unlock the wallet if needed + if (local_wallet->IsLocked() && !local_wallet->Unlock(passphrase)) { + if (passphrase.find('\0') == std::string::npos) { + return util::Error{Untranslated("Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect.")}; + } else { + return util::Error{Untranslated("Error: Wallet decryption failed, the wallet passphrase entered was incorrect. " + "The passphrase contains a null character (ie - a zero byte). " + "If this passphrase was set with a version of this software prior to 25.0, " + "please try again with only the characters up to — but not including — " + "the first null character.")}; + } + } + // First change to using SQLite if (!local_wallet->MigrateToSQLite(error)) return util::Error{error}; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f104a15f98..32cb3e3f59 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -10,6 +10,7 @@ #include <fs.h> #include <interfaces/chain.h> #include <interfaces/handler.h> +#include <logging.h> #include <outputtype.h> #include <policy/feerate.h> #include <psbt.h> @@ -19,7 +20,6 @@ #include <util/result.h> #include <util/strencodings.h> #include <util/string.h> -#include <util/system.h> #include <util/time.h> #include <util/ui_change_type.h> #include <validationinterface.h> @@ -243,6 +243,7 @@ private: std::atomic<bool> fAbortRescan{false}; std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver std::atomic<bool> m_attaching_chain{false}; + std::atomic<bool> m_scanning_with_passphrase{false}; std::atomic<int64_t> m_scanning_start{0}; std::atomic<double> m_scanning_progress{0}; friend class WalletRescanReserver; @@ -307,9 +308,6 @@ private: //! Unset the blank wallet flag and saves it to disk void UnsetBlankWalletFlag(WalletBatch& batch) override; - /** Provider of aplication-wide arguments. */ - const ArgsManager& m_args; - /** Interface for accessing chain state. */ interfaces::Chain* m_chain; @@ -373,9 +371,8 @@ public: unsigned int nMasterKeyMaxID = 0; /** Construct wallet with specified name and database implementation. */ - CWallet(interfaces::Chain* chain, const std::string& name, const ArgsManager& args, std::unique_ptr<WalletDatabase> database) - : m_args(args), - m_chain(chain), + CWallet(interfaces::Chain* chain, const std::string& name, std::unique_ptr<WalletDatabase> database) + : m_chain(chain), m_name(name), m_database(std::move(database)) { @@ -467,6 +464,7 @@ public: void AbortRescan() { fAbortRescan = true; } bool IsAbortingRescan() const { return fAbortRescan; } bool IsScanning() const { return fScanningWallet; } + bool IsScanningWithPassphrase() const { return m_scanning_with_passphrase; } int64_t ScanningDuration() const { return fScanningWallet ? GetTimeMillis() - m_scanning_start : 0; } double ScanningProgress() const { return fScanningWallet ? (double) m_scanning_progress : 0; } @@ -486,6 +484,9 @@ public: // Used to prevent concurrent calls to walletpassphrase RPC. Mutex m_unlock_mutex; + // Used to prevent deleting the passphrase from memory when it is still in use. + RecursiveMutex m_relock_mutex; + bool Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys = false); bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); @@ -642,6 +643,12 @@ public: /** Absolute maximum transaction fee (in satoshis) used by default for the wallet */ CAmount m_default_max_tx_fee{DEFAULT_TRANSACTION_MAXFEE}; + /** Number of pre-generated keys/scripts by each spkm (part of the look-ahead process, used to detect payments) */ + int64_t m_keypool_size{DEFAULT_KEYPOOL_SIZE}; + + /** Notify external script when a wallet transaction comes in or is updated (handled by -walletnotify) */ + std::string m_notify_tx_changed_script; + size_t KeypoolCountExternalKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool TopUpKeyPool(unsigned int kpSize = 0); @@ -960,12 +967,13 @@ private: public: explicit WalletRescanReserver(CWallet& w) : m_wallet(w) {} - bool reserve() + bool reserve(bool with_passphrase = false) { assert(!m_could_reserve); if (m_wallet.fScanningWallet.exchange(true)) { return false; } + m_wallet.m_scanning_with_passphrase.exchange(with_passphrase); m_wallet.m_scanning_start = GetTimeMillis(); m_wallet.m_scanning_progress = 0; m_could_reserve = true; @@ -985,6 +993,7 @@ public: { if (m_could_reserve) { m_wallet.fScanningWallet = false; + m_wallet.m_scanning_with_passphrase = false; } } }; @@ -1007,7 +1016,7 @@ struct MigrationResult { }; //! Do all steps to migrate a legacy wallet to a descriptor wallet -util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>&& wallet, WalletContext& context); +util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& wallet_name, const SecureString& passphrase, WalletContext& context); } // namespace wallet #endif // BITCOIN_WALLET_WALLET_H diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index 96537b9873..f389676204 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -47,7 +47,7 @@ static void WalletCreate(CWallet* wallet_instance, uint64_t wallet_creation_flag wallet_instance->TopUpKeyPool(); } -static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::path& path, const ArgsManager& args, DatabaseOptions options) +static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::path& path, DatabaseOptions options) { DatabaseStatus status; bilingual_str error; @@ -58,7 +58,7 @@ static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::pa } // dummy chain interface - std::shared_ptr<CWallet> wallet_instance{new CWallet(/*chain=*/nullptr, name, args, std::move(database)), WalletToolReleaseWallet}; + std::shared_ptr<CWallet> wallet_instance{new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet}; DBErrors load_wallet_ret; try { load_wallet_ret = wallet_instance->LoadWallet(); @@ -159,7 +159,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command) options.require_format = DatabaseFormat::SQLITE; } - const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, args, options); + const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options); if (wallet_instance) { WalletShowInfo(wallet_instance.get()); wallet_instance->Close(); @@ -168,7 +168,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command) DatabaseOptions options; ReadDatabaseArgs(args, options); options.require_existing = true; - const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, args, options); + const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options); if (!wallet_instance) return false; WalletShowInfo(wallet_instance.get()); wallet_instance->Close(); @@ -194,7 +194,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command) DatabaseOptions options; ReadDatabaseArgs(args, options); options.require_existing = true; - const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, args, options); + const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options); if (!wallet_instance) return false; bilingual_str error; bool ret = DumpWallet(args, *wallet_instance, error); diff --git a/test/README.md b/test/README.md index fdbb91832a..0eddb72e1f 100644 --- a/test/README.md +++ b/test/README.md @@ -109,34 +109,57 @@ how many jobs to run, append `--jobs=n` The individual tests and the test_runner harness have many command-line options. Run `test/functional/test_runner.py -h` to see them all. -#### Speed up test runs with a ramdisk +#### Speed up test runs with a RAM disk -If you have available RAM on your system you can create a ramdisk to use as the `cache` and `tmp` directories for the functional tests in order to speed them up. -Speed-up amount varies on each system (and according to your ram speed and other variables), but a 2-3x speed-up is not uncommon. +If you have available RAM on your system you can create a RAM disk to use as the `cache` and `tmp` directories for the functional tests in order to speed them up. +Speed-up amount varies on each system (and according to your RAM speed and other variables), but a 2-3x speed-up is not uncommon. -To create a 4GB ramdisk on Linux at `/mnt/tmp/`: +**Linux** + +To create a 4 GiB RAM disk at `/mnt/tmp/`: ```bash sudo mkdir -p /mnt/tmp sudo mount -t tmpfs -o size=4g tmpfs /mnt/tmp/ ``` -Configure the size of the ramdisk using the `size=` option. -The size of the ramdisk needed is relative to the number of concurrent jobs the test suite runs. -For example running the test suite with `--jobs=100` might need a 4GB ramdisk, but running with `--jobs=32` will only need a 2.5GB ramdisk. +Configure the size of the RAM disk using the `size=` option. +The size of the RAM disk needed is relative to the number of concurrent jobs the test suite runs. +For example running the test suite with `--jobs=100` might need a 4 GiB RAM disk, but running with `--jobs=32` will only need a 2.5 GiB RAM disk. -To use, run the test suite specifying the ramdisk as the `cachedir` and `tmpdir`: +To use, run the test suite specifying the RAM disk as the `cachedir` and `tmpdir`: ```bash test/functional/test_runner.py --cachedir=/mnt/tmp/cache --tmpdir=/mnt/tmp ``` -Once finished with the tests and the disk, and to free the ram, simply unmount the disk: +Once finished with the tests and the disk, and to free the RAM, simply unmount the disk: ```bash sudo umount /mnt/tmp ``` +**macOS** + +To create a 4 GiB RAM disk named "ramdisk" at `/Volumes/ramdisk/`: + +```bash +diskutil erasevolume HFS+ ramdisk $(hdiutil attach -nomount ram://8388608) +``` + +Configure the RAM disk size, expressed as the number of blocks, at the end of the command +(`4096 MiB * 2048 blocks/MiB = 8388608 blocks` for 4 GiB). To run the tests using the RAM disk: + +```bash +test/functional/test_runner.py --cachedir=/Volumes/ramdisk/cache --tmpdir=/Volumes/ramdisk/tmp +``` + +To unmount: + +```bash +umount /Volumes/ramdisk +``` + #### Troubleshooting and debugging test failures ##### Resource contention diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py index eff4d9b149..4f8541a5d7 100755 --- a/test/functional/feature_coinstatsindex.py +++ b/test/functional/feature_coinstatsindex.py @@ -156,9 +156,10 @@ class CoinStatsIndexTest(BitcoinTestFramework): # Generate and send another tx with an OP_RETURN output (which is unspendable) tx2 = self.wallet.create_self_transfer(utxo_to_spend=tx1_out_21)['tx'] - tx2.vout = [CTxOut(int(Decimal('20.99') * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] + tx2_val = '20.99' + tx2.vout = [CTxOut(int(Decimal(tx2_val) * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] tx2_hex = tx2.serialize().hex() - self.nodes[0].sendrawtransaction(tx2_hex) + self.nodes[0].sendrawtransaction(tx2_hex, 0, tx2_val) # Include both txs in a block self.generate(self.nodes[0], 1) diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index a37d614535..f9730b48c5 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -251,11 +251,43 @@ class ConfArgsTest(BitcoinTestFramework): ]): self.nodes[0].setmocktime(start + 65) + def test_connect_with_seednode(self): + self.log.info('Test -connect with -seednode') + seednode_ignored = ['-seednode is ignored when -connect is used\n'] + dnsseed_ignored = ['-dnsseed is ignored when -connect is used and -proxy is specified\n'] + addcon_thread_started = ['addcon thread start\n'] + self.stop_node(0) + + # When -connect is supplied, expanding addrman via getaddr calls to ADDR_FETCH(-seednode) + # nodes is irrelevant and -seednode is ignored. + with self.nodes[0].assert_debug_log(expected_msgs=seednode_ignored): + self.start_node(0, extra_args=['-connect=fakeaddress1', '-seednode=fakeaddress2']) + + # With -proxy, an ADDR_FETCH connection is made to a peer that the dns seed resolves to. + # ADDR_FETCH connections are not used when -connect is used. + with self.nodes[0].assert_debug_log(expected_msgs=dnsseed_ignored): + self.restart_node(0, extra_args=['-connect=fakeaddress1', '-dnsseed=1', '-proxy=1.2.3.4']) + + # If the user did not disable -dnsseed, but it was soft-disabled because they provided -connect, + # they shouldn't see a warning about -dnsseed being ignored. + with self.nodes[0].assert_debug_log(expected_msgs=addcon_thread_started, + unexpected_msgs=dnsseed_ignored): + self.restart_node(0, extra_args=['-connect=fakeaddress1', '-proxy=1.2.3.4']) + + # We have to supply expected_msgs as it's a required argument + # The expected_msg must be something we are confident will be logged after the unexpected_msg + # These cases test for -connect being supplied but only to disable it + for connect_arg in ['-connect=0', '-noconnect']: + with self.nodes[0].assert_debug_log(expected_msgs=addcon_thread_started, + unexpected_msgs=seednode_ignored): + self.restart_node(0, extra_args=[connect_arg, '-seednode=fakeaddress2']) + def run_test(self): self.test_log_buffer() self.test_args_log() self.test_seed_peers() self.test_networkactive() + self.test_connect_with_seednode() self.test_config_file_parser() self.test_invalid_command_line_options() diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index e2bc566f53..1f2e0936ed 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -85,7 +85,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): self.nodes[node_index].waitforblock(expected_tip) utxo_hash = self.nodes[node_index].gettxoutsetinfo()['hash_serialized_2'] return utxo_hash - except: + except Exception: # An exception here should mean the node is about to crash. # If bitcoind exits, then try again. wait_for_node_exit() # should raise an exception if bitcoind doesn't exit. diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 664ed779db..519877ac5b 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -223,8 +223,8 @@ class PruneTest(BitcoinTestFramework): def reorg_back(self): # Verify that a block on the old main chain fork has been pruned away assert_raises_rpc_error(-1, "Block not available (pruned data)", self.nodes[2].getblock, self.forkhash) - with self.nodes[2].assert_debug_log(expected_msgs=['block verification stopping at height', '(pruning, no data)']): - self.nodes[2].verifychain(checklevel=4, nblocks=0) + with self.nodes[2].assert_debug_log(expected_msgs=['block verification stopping at height', '(no data)']): + assert not self.nodes[2].verifychain(checklevel=4, nblocks=0) self.log.info(f"Will need to redownload block {self.forkheight}") # Verify that we have enough history to reorg back to the fork point diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index 144e01c367..8ac06f570d 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -750,7 +750,7 @@ def spenders_taproot_active(): # Reusing the scripts above, test that various features affect the sighash. add_spender(spenders, "sighash/annex", tap=tap, leaf="pk_codesep", key=secs[1], hashtype=hashtype, standard=False, **SINGLE_SIG, annex=bytes([ANNEX_TAG]), failure={"sighash": override(default_sighash, annex=None)}, **ERR_SIG_SCHNORR) add_spender(spenders, "sighash/script", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, script_taproot=tap.leaves["codesep_pk"].script)}, **ERR_SIG_SCHNORR) - add_spender(spenders, "sighash/leafver", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, leafversion=random.choice([x & 0xFE for x in range(0x100) if x & 0xFE != 0xC0]))}, **ERR_SIG_SCHNORR) + add_spender(spenders, "sighash/leafver", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, leafversion=random.choice([x & 0xFE for x in range(0x100) if x & 0xFE != LEAF_VERSION_TAPSCRIPT]))}, **ERR_SIG_SCHNORR) add_spender(spenders, "sighash/scriptpath", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, leaf=None)}, **ERR_SIG_SCHNORR) add_spender(spenders, "sighash/keypath", tap=tap, key=secs[0], **common, failure={"sighash": override(default_sighash, leaf="pk_codesep")}, **ERR_SIG_SCHNORR) @@ -1555,12 +1555,16 @@ class TaprootTest(BitcoinTestFramework): script_lists = [ None, - [("0", CScript([pubs[50], OP_CHECKSIG]), 0xc0)], - [("0", CScript([pubs[51], OP_CHECKSIG]), 0xc0)], - [("0", CScript([pubs[52], OP_CHECKSIG]), 0xc0), ("1", CScript([b"BIP341"]), VALID_LEAF_VERS[pubs[99][0] % 41])], - [("0", CScript([pubs[53], OP_CHECKSIG]), 0xc0), ("1", CScript([b"Taproot"]), VALID_LEAF_VERS[pubs[99][1] % 41])], - [("0", CScript([pubs[54], OP_CHECKSIG]), 0xc0), [("1", CScript([pubs[55], OP_CHECKSIG]), 0xc0), ("2", CScript([pubs[56], OP_CHECKSIG]), 0xc0)]], - [("0", CScript([pubs[57], OP_CHECKSIG]), 0xc0), [("1", CScript([pubs[58], OP_CHECKSIG]), 0xc0), ("2", CScript([pubs[59], OP_CHECKSIG]), 0xc0)]], + [("0", CScript([pubs[50], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT)], + [("0", CScript([pubs[51], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT)], + [("0", CScript([pubs[52], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT), ("1", CScript([b"BIP341"]), VALID_LEAF_VERS[pubs[99][0] % 41])], + [("0", CScript([pubs[53], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT), ("1", CScript([b"Taproot"]), VALID_LEAF_VERS[pubs[99][1] % 41])], + [("0", CScript([pubs[54], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT), + [("1", CScript([pubs[55], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT), ("2", CScript([pubs[56], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT)] + ], + [("0", CScript([pubs[57], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT), + [("1", CScript([pubs[58], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT), ("2", CScript([pubs[59], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT)] + ], ] taps = [taproot_construct(inner_keys[i], script_lists[i]) for i in range(len(inner_keys))] diff --git a/test/functional/p2p_disconnect_ban.py b/test/functional/p2p_disconnect_ban.py index b2f0659eda..394009f30f 100755 --- a/test/functional/p2p_disconnect_ban.py +++ b/test/functional/p2p_disconnect_ban.py @@ -116,7 +116,7 @@ class DisconnectBanTest(BitcoinTestFramework): self.log.info("disconnectnode: successfully disconnect node by address") address1 = self.nodes[0].getpeerinfo()[0]['addr'] self.nodes[0].disconnectnode(address=address1) - self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 1, timeout=10) + self.wait_until(lambda: len(self.nodes[1].getpeerinfo()) == 1, timeout=10) assert not [node for node in self.nodes[0].getpeerinfo() if node['addr'] == address1] self.log.info("disconnectnode: successfully reconnect node") @@ -127,7 +127,7 @@ class DisconnectBanTest(BitcoinTestFramework): self.log.info("disconnectnode: successfully disconnect node by node id") id1 = self.nodes[0].getpeerinfo()[0]['id'] self.nodes[0].disconnectnode(nodeid=id1) - self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 1, timeout=10) + self.wait_until(lambda: len(self.nodes[1].getpeerinfo()) == 1, timeout=10) assert not [node for node in self.nodes[0].getpeerinfo() if node['id'] == id1] if __name__ == '__main__': diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py index 5a0003d3ef..a56afbcf7b 100755 --- a/test/functional/p2p_node_network_limited.py +++ b/test/functional/p2p_node_network_limited.py @@ -85,7 +85,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): self.connect_nodes(0, 2) try: self.sync_blocks([self.nodes[0], self.nodes[2]], timeout=5) - except: + except Exception: pass # node2 must remain at height 0 assert_equal(self.nodes[2].getblockheader(self.nodes[2].getbestblockhash())['height'], 0) diff --git a/test/functional/rpc_decodescript.py b/test/functional/rpc_decodescript.py index 8df3b68785..673836bd04 100755 --- a/test/functional/rpc_decodescript.py +++ b/test/functional/rpc_decodescript.py @@ -272,6 +272,9 @@ class DecodeScriptTest(BitcoinTestFramework): # Miniscript-incompatible offered HTLC res = self.nodes[0].decodescript("82012088a914ffffffffffffffffffffffffffffffffffffffff882102ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffacb2") assert res["segwit"]["desc"] == "wsh(raw(82012088a914ffffffffffffffffffffffffffffffffffffffff882102ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffacb2))#ra6w2xa7" + # Miniscript-compatible multisig bigger than 520 byte P2SH limit. + res = self.nodes[0].decodescript("5b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc401021031c41fdbcebe17bec8d49816e00ca1b5ac34766b91c9f2ac37d39c63e5e008afb2103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5fae736402c00fb269522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb53ae68") + assert_equal(res["segwit"]["desc"], "wsh(or_d(multi(11,020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b67817261,02675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af99,02896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d48,029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c,02a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc4010,031c41fdbcebe17bec8d49816e00ca1b5ac34766b91c9f2ac37d39c63e5e008afb,03079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b,03111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2,0318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa0840174,03230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de1,035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a6,03bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c,03cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d17546,03d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d424828,03ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a),and_v(v:older(4032),multi(2,03aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79,0291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807,0386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb))))#7jwwklk4") def run_test(self): self.log.info("Test decoding of standard input scripts [scriptSig]") diff --git a/test/functional/rpc_preciousblock.py b/test/functional/rpc_preciousblock.py index 91298937fd..3062a86565 100755 --- a/test/functional/rpc_preciousblock.py +++ b/test/functional/rpc_preciousblock.py @@ -16,7 +16,7 @@ def unidirectional_node_sync_via_rpc(node_src, node_dest): try: assert len(node_dest.getblock(blockhash, False)) > 0 break - except: + except Exception: blocks_to_copy.append(blockhash) blockhash = node_src.getblockheader(blockhash, True)['previousblockhash'] blocks_to_copy.reverse() diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index cdec4b2a85..2395935620 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -18,9 +18,17 @@ from itertools import product from test_framework.messages import ( MAX_BIP125_RBF_SEQUENCE, + COIN, CTransaction, + CTxOut, tx_from_hex, ) +from test_framework.script import ( + CScript, + OP_FALSE, + OP_INVALIDOPCODE, + OP_RETURN, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -331,6 +339,57 @@ class RawTransactionsTest(BitcoinTestFramework): rawtx = self.nodes[2].createrawtransaction(inputs, outputs) assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx) + self.log.info("Test sendrawtransaction exceeding, falling short of, and equaling maxburnamount") + max_burn_exceeded = "Unspendable output exceeds maximum configured by user (maxburnamount)" + + + # Test that spendable transaction with default maxburnamount (0) gets sent + tx = self.wallet.create_self_transfer()['tx'] + tx_hex = tx.serialize().hex() + self.nodes[2].sendrawtransaction(hexstring=tx_hex) + + # Test that datacarrier transaction with default maxburnamount (0) does not get sent + tx = self.wallet.create_self_transfer()['tx'] + tx_val = 0.001 + tx.vout = [CTxOut(int(Decimal(tx_val) * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] + tx_hex = tx.serialize().hex() + assert_raises_rpc_error(-25, max_burn_exceeded, self.nodes[2].sendrawtransaction, tx_hex) + + # Test that oversized script gets rejected by sendrawtransaction + tx = self.wallet.create_self_transfer()['tx'] + tx_val = 0.001 + tx.vout = [CTxOut(int(Decimal(tx_val) * COIN), CScript([OP_FALSE] * 10001))] + tx_hex = tx.serialize().hex() + assert_raises_rpc_error(-25, max_burn_exceeded, self.nodes[2].sendrawtransaction, tx_hex) + + # Test that script containing invalid opcode gets rejected by sendrawtransaction + tx = self.wallet.create_self_transfer()['tx'] + tx_val = 0.01 + tx.vout = [CTxOut(int(Decimal(tx_val) * COIN), CScript([OP_INVALIDOPCODE]))] + tx_hex = tx.serialize().hex() + assert_raises_rpc_error(-25, max_burn_exceeded, self.nodes[2].sendrawtransaction, tx_hex) + + # Test a transaction where our burn exceeds maxburnamount + tx = self.wallet.create_self_transfer()['tx'] + tx_val = 0.001 + tx.vout = [CTxOut(int(Decimal(tx_val) * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] + tx_hex = tx.serialize().hex() + assert_raises_rpc_error(-25, max_burn_exceeded, self.nodes[2].sendrawtransaction, tx_hex, 0, 0.0009) + + # Test a transaction where our burn falls short of maxburnamount + tx = self.wallet.create_self_transfer()['tx'] + tx_val = 0.001 + tx.vout = [CTxOut(int(Decimal(tx_val) * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] + tx_hex = tx.serialize().hex() + self.nodes[2].sendrawtransaction(hexstring=tx_hex, maxfeerate='0', maxburnamount='0.0011') + + # Test a transaction where our burn equals maxburnamount + tx = self.wallet.create_self_transfer()['tx'] + tx_val = 0.001 + tx.vout = [CTxOut(int(Decimal(tx_val) * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] + tx_hex = tx.serialize().hex() + self.nodes[2].sendrawtransaction(hexstring=tx_hex, maxfeerate='0', maxburnamount='0.001') + def sendrawtransaction_testmempoolaccept_tests(self): self.log.info("Test sendrawtransaction/testmempoolaccept with maxfeerate") fee_exceeds_max = "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)" diff --git a/test/functional/test_framework/authproxy.py b/test/functional/test_framework/authproxy.py index dd20b28550..61f92aeac3 100644 --- a/test/functional/test_framework/authproxy.py +++ b/test/functional/test_framework/authproxy.py @@ -78,7 +78,10 @@ class AuthServiceProxy(): passwd = None if self.__url.password is None else self.__url.password.encode('utf8') authpair = user + b':' + passwd self.__auth_header = b'Basic ' + base64.b64encode(authpair) - self.timeout = timeout + # clamp the socket timeout, since larger values can cause an + # "Invalid argument" exception in Python's HTTP(S) client + # library on some operating systems (e.g. OpenBSD, FreeBSD) + self.timeout = min(timeout, 2147483) self._set_conn(connection) def __getattr__(self, name): diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py index 59157f4755..c5768177bd 100755 --- a/test/functional/test_framework/p2p.py +++ b/test/functional/test_framework/p2p.py @@ -385,7 +385,7 @@ class P2PInterface(P2PConnection): self.message_count[msgtype] += 1 self.last_message[msgtype] = message getattr(self, 'on_' + msgtype)(message) - except: + except Exception: print("ERROR delivering %s (%s)" % (repr(message), sys.exc_info()[0])) raise diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 513b795478..9620951a16 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -279,10 +279,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): if seed is None: seed = random.randrange(sys.maxsize) else: - self.log.debug("User supplied random seed {}".format(seed)) + self.log.info("User supplied random seed {}".format(seed)) random.seed(seed) - self.log.debug("PRNG seed is: {}".format(seed)) + self.log.info("PRNG seed is: {}".format(seed)) self.log.debug('Setting up network thread') self.network_thread = NetworkThread() @@ -557,7 +557,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): node.start(extra_args[i], *args, **kwargs) for node in self.nodes: node.wait_for_rpc_connection() - except: + except Exception: # If one node failed to start, stop the others self.stop_nodes() raise diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index b515538a1a..882f82e0f2 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -829,7 +829,7 @@ class RPCOverloadWrapper(): int(address ,16) is_hex = True desc = descsum_create('raw(' + address + ')') - except: + except Exception: desc = descsum_create('addr(' + address + ')') reqs = [{ 'desc': desc, diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 0fe28b3855..26ebce039b 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -216,6 +216,8 @@ BASE_SCRIPTS = [ 'rpc_blockchain.py', 'rpc_deprecated.py', 'wallet_disable.py', + 'wallet_change_address.py --legacy-wallet', + 'wallet_change_address.py --descriptors', 'p2p_addr_relay.py', 'p2p_getaddr_caching.py', 'p2p_getdata.py', diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index a2ae997ecb..ad79e0288c 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -81,7 +81,7 @@ class BumpFeeTest(BitcoinTestFramework): self.log.info("Running tests") dest_address = peer_node.getnewaddress() - for mode in ["default", "fee_rate"]: + for mode in ["default", "fee_rate", "new_outputs"]: test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address) self.test_invalid_parameters(rbf_node, peer_node, dest_address) test_segwit_bumpfee_succeeds(self, rbf_node, dest_address) @@ -157,6 +157,14 @@ class BumpFeeTest(BitcoinTestFramework): assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', rbf_node.bumpfee, rbfid, {"estimate_mode": mode}) + self.log.info("Test invalid outputs values") + assert_raises_rpc_error(-8, "Invalid parameter, output argument cannot be an empty array", + rbf_node.bumpfee, rbfid, {"outputs": []}) + assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: " + dest_address, + rbf_node.bumpfee, rbfid, {"outputs": [{dest_address: 0.1}, {dest_address: 0.2}]}) + assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data", + rbf_node.bumpfee, rbfid, {"outputs": [{"data": "deadbeef"}, {"data": "deadbeef"}]}) + self.clear_mempool() @@ -169,6 +177,10 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address): if mode == "fee_rate": bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"fee_rate": str(NORMAL)}) bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": NORMAL}) + elif mode == "new_outputs": + new_address = peer_node.getnewaddress() + bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"outputs": {new_address: 0.0003}}) + bumped_tx = rbf_node.bumpfee(rbfid, {"outputs": {new_address: 0.0003}}) else: bumped_psbt = rbf_node.psbtbumpfee(rbfid) bumped_tx = rbf_node.bumpfee(rbfid) @@ -192,6 +204,10 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address): bumpedwtx = rbf_node.gettransaction(bumped_tx["txid"]) assert_equal(oldwtx["replaced_by_txid"], bumped_tx["txid"]) assert_equal(bumpedwtx["replaces_txid"], rbfid) + # if this is a new_outputs test, check that outputs were indeed replaced + if mode == "new_outputs": + assert len(bumpedwtx["details"]) == 1 + assert bumpedwtx["details"][0]["address"] == new_address self.clear_mempool() @@ -628,12 +644,14 @@ def test_change_script_match(self, rbf_node, dest_address): self.clear_mempool() -def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")): +def spend_one_input(node, dest_address, change_size=Decimal("0.00049000"), data=None): tx_input = dict( sequence=MAX_BIP125_RBF_SEQUENCE, **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000"))) destinations = {dest_address: Decimal("0.00050000")} if change_size > 0: destinations[node.getrawchangeaddress()] = change_size + if data: + destinations['data'] = data rawtx = node.createrawtransaction([tx_input], destinations) signedtx = node.signrawtransactionwithwallet(rawtx) txid = node.sendrawtransaction(signedtx["hex"]) diff --git a/test/functional/wallet_change_address.py b/test/functional/wallet_change_address.py new file mode 100755 index 0000000000..f8bfe9eebf --- /dev/null +++ b/test/functional/wallet_change_address.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test wallet change address selection""" + +import re + +from test_framework.blocktools import COINBASE_MATURITY +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) + + +class WalletChangeAddressTest(BitcoinTestFramework): + def add_options(self, parser): + self.add_wallet_options(parser) + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 3 + # discardfee is used to make change outputs less likely in the change_pos test + self.extra_args = [ + [], + ["-discardfee=1"], + ["-avoidpartialspends", "-discardfee=1"] + ] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def assert_change_index(self, node, tx, index): + change_index = None + for vout in tx["vout"]: + info = node.getaddressinfo(vout["scriptPubKey"]["address"]) + if (info["ismine"] and info["ischange"]): + change_index = int(re.findall(r'\d+', info["hdkeypath"])[-1]) + break + assert_equal(change_index, index) + + def assert_change_pos(self, wallet, tx, pos): + change_pos = None + for index, output in enumerate(tx["vout"]): + info = wallet.getaddressinfo(output["scriptPubKey"]["address"]) + if (info["ismine"] and info["ischange"]): + change_pos = index + break + assert_equal(change_pos, pos) + + def run_test(self): + self.log.info("Setting up") + # Mine some coins + self.generate(self.nodes[0], COINBASE_MATURITY + 1) + + # Get some addresses from the two nodes + addr1 = [self.nodes[1].getnewaddress() for _ in range(3)] + addr2 = [self.nodes[2].getnewaddress() for _ in range(3)] + addrs = addr1 + addr2 + + # Send 1 + 0.5 coin to each address + [self.nodes[0].sendtoaddress(addr, 1.0) for addr in addrs] + [self.nodes[0].sendtoaddress(addr, 0.5) for addr in addrs] + self.generate(self.nodes[0], 1) + + for i in range(20): + for n in [1, 2]: + self.log.debug(f"Send transaction from node {n}: expected change index {i}") + txid = self.nodes[n].sendtoaddress(self.nodes[0].getnewaddress(), 0.2) + tx = self.nodes[n].getrawtransaction(txid, True) + # find the change output and ensure that expected change index was used + self.assert_change_index(self.nodes[n], tx, i) + + # Start next test with fresh wallets and new coins + self.nodes[1].createwallet("w1") + self.nodes[2].createwallet("w2") + w1 = self.nodes[1].get_wallet_rpc("w1") + w2 = self.nodes[2].get_wallet_rpc("w2") + addr1 = w1.getnewaddress() + addr2 = w2.getnewaddress() + self.nodes[0].sendtoaddress(addr1, 3.0) + self.nodes[0].sendtoaddress(addr1, 0.1) + self.nodes[0].sendtoaddress(addr2, 3.0) + self.nodes[0].sendtoaddress(addr2, 0.1) + self.generate(self.nodes[0], 1) + + sendTo1 = self.nodes[0].getnewaddress() + sendTo2 = self.nodes[0].getnewaddress() + sendTo3 = self.nodes[0].getnewaddress() + + # The avoid partial spends wallet will always create a change output + node = self.nodes[2] + res = w2.send({sendTo1: "1.0", sendTo2: "1.0", sendTo3: "0.9999"}, options={"change_position": 0}) + tx = node.getrawtransaction(res["txid"], True) + self.assert_change_pos(w2, tx, 0) + + # The default wallet will internally create a tx without change first, + # then create a second candidate using APS that requires a change output. + # Ensure that the user-configured change position is kept + node = self.nodes[1] + res = w1.send({sendTo1: "1.0", sendTo2: "1.0", sendTo3: "0.9999"}, options={"change_position": 0}) + tx = node.getrawtransaction(res["txid"], True) + # If the wallet ignores the user's change_position there is still a 25% + # that the random change position passes the test + self.assert_change_pos(w1, tx, 0) + +if __name__ == '__main__': + WalletChangeAddressTest().main() diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py index 885c52cf2e..88b9ebbddd 100755 --- a/test/functional/wallet_encryption.py +++ b/test/functional/wallet_encryption.py @@ -90,6 +90,17 @@ class WalletEncryptionTest(BitcoinTestFramework): self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE + 1000) actual_time = self.nodes[0].getwalletinfo()['unlocked_until'] assert_equal(actual_time, expected_time) + self.nodes[0].walletlock() + + # Test passphrase with null characters + passphrase_with_nulls = "Phrase\0With\0Nulls" + self.nodes[0].walletpassphrasechange(passphrase2, passphrase_with_nulls) + # walletpassphrasechange should not stop at null characters + assert_raises_rpc_error(-14, "wallet passphrase entered was incorrect", self.nodes[0].walletpassphrase, passphrase_with_nulls.partition("\0")[0], 10) + self.nodes[0].walletpassphrase(passphrase_with_nulls, 10) + sig = self.nodes[0].signmessage(address, msg) + assert self.nodes[0].verifymessage(address, sig, msg) + self.nodes[0].walletlock() if __name__ == '__main__': diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py index ca0209b61d..e66eb2c289 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -448,14 +448,14 @@ class ImportDescriptorsTest(BitcoinTestFramework): wallet=wmulti_priv) assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1001) # Range end (1000) is inclusive, so 1001 addresses generated - addr = wmulti_priv.getnewaddress('', 'bech32') + addr = wmulti_priv.getnewaddress('', 'bech32') # uses receive 0 assert_equal(addr, 'bcrt1qdt0qy5p7dzhxzmegnn4ulzhard33s2809arjqgjndx87rv5vd0fq2czhy8') # Derived at m/84'/0'/0'/0 - change_addr = wmulti_priv.getrawchangeaddress('bech32') - assert_equal(change_addr, 'bcrt1qt9uhe3a9hnq7vajl7a094z4s3crm9ttf8zw3f5v9gr2nyd7e3lnsy44n8e') + change_addr = wmulti_priv.getrawchangeaddress('bech32') # uses change 0 + assert_equal(change_addr, 'bcrt1qt9uhe3a9hnq7vajl7a094z4s3crm9ttf8zw3f5v9gr2nyd7e3lnsy44n8e') # Derived at m/84'/1'/0'/0 assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1000) txid = w0.sendtoaddress(addr, 10) self.generate(self.nodes[0], 6) - send_txid = wmulti_priv.sendtoaddress(w0.getnewaddress(), 8) + send_txid = wmulti_priv.sendtoaddress(w0.getnewaddress(), 8) # uses change 1 decoded = wmulti_priv.gettransaction(txid=send_txid, verbose=True)['decoded'] assert_equal(len(decoded['vin'][0]['txinwitness']), 4) self.sync_all() @@ -481,10 +481,10 @@ class ImportDescriptorsTest(BitcoinTestFramework): wallet=wmulti_pub) assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 1000) # The first one was already consumed by previous import and is detected as used - addr = wmulti_pub.getnewaddress('', 'bech32') + addr = wmulti_pub.getnewaddress('', 'bech32') # uses receive 1 assert_equal(addr, 'bcrt1qp8s25ckjl7gr6x2q3dx3tn2pytwp05upkjztk6ey857tt50r5aeqn6mvr9') # Derived at m/84'/0'/0'/1 - change_addr = wmulti_pub.getrawchangeaddress('bech32') - assert_equal(change_addr, 'bcrt1qzxl0qz2t88kljdnkzg4n4gapr6kte26390gttrg79x66nt4p04fssj53nl') + change_addr = wmulti_pub.getrawchangeaddress('bech32') # uses change 2 + assert_equal(change_addr, 'bcrt1qp6j3jw8yetefte7kw6v5pc89rkgakzy98p6gf7ayslaveaxqyjusnw580c') # Derived at m/84'/1'/0'/2 assert send_txid in self.nodes[0].getrawmempool(True) assert send_txid in (x['txid'] for x in wmulti_pub.listunspent(0)) assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999) @@ -667,5 +667,33 @@ class ImportDescriptorsTest(BitcoinTestFramework): success=True, warnings=["Unknown output type, cannot set descriptor to active."]) + self.log.info("Test importing a descriptor to an encrypted wallet") + + descriptor = {"desc": descsum_create("pkh(" + xpriv + "/1h/*h)"), + "timestamp": "now", + "active": True, + "range": [0,4000], + "next_index": 4000} + + self.nodes[0].createwallet("temp_wallet", blank=True, descriptors=True) + temp_wallet = self.nodes[0].get_wallet_rpc("temp_wallet") + temp_wallet.importdescriptors([descriptor]) + self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 1, temp_wallet.getnewaddress()) + self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 1, temp_wallet.getnewaddress()) + + self.nodes[0].createwallet("encrypted_wallet", blank=True, descriptors=True, passphrase="passphrase") + encrypted_wallet = self.nodes[0].get_wallet_rpc("encrypted_wallet") + + descriptor["timestamp"] = 0 + descriptor["next_index"] = 0 + + batch = [] + batch.append(encrypted_wallet.walletpassphrase.get_request("passphrase", 3)) + batch.append(encrypted_wallet.importdescriptors.get_request([descriptor])) + + encrypted_wallet.batch(batch) + + assert_equal(temp_wallet.getbalance(), encrypted_wallet.getbalance()) + if __name__ == '__main__': ImportDescriptorsTest().main() diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index 72c5fe7b84..7c2959bb89 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -400,11 +400,75 @@ class WalletMigrationTest(BitcoinTestFramework): def test_encrypted(self): self.log.info("Test migration of an encrypted wallet") wallet = self.create_legacy_wallet("encrypted") + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) wallet.encryptwallet("pass") + addr = wallet.getnewaddress() + txid = default.sendtoaddress(addr, 1) + self.generate(self.nodes[0], 1) + bals = wallet.getbalances() + + assert_raises_rpc_error(-4, "Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect", wallet.migratewallet) + assert_raises_rpc_error(-4, "Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect", wallet.migratewallet, None, "badpass") + assert_raises_rpc_error(-4, "The passphrase contains a null character", wallet.migratewallet, None, "pass\0with\0null") + + wallet.migratewallet(passphrase="pass") + + info = wallet.getwalletinfo() + assert_equal(info["descriptors"], True) + assert_equal(info["format"], "sqlite") + assert_equal(info["unlocked_until"], 0) + wallet.gettransaction(txid) + + assert_equal(bals, wallet.getbalances()) + + def test_unloaded(self): + self.log.info("Test migration of a wallet that isn't loaded") + wallet = self.create_legacy_wallet("notloaded") + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + addr = wallet.getnewaddress() + txid = default.sendtoaddress(addr, 1) + self.generate(self.nodes[0], 1) + bals = wallet.getbalances() + + wallet.unloadwallet() + + assert_raises_rpc_error(-8, "RPC endpoint wallet and wallet_name parameter specify different wallets", wallet.migratewallet, "someotherwallet") + assert_raises_rpc_error(-8, "Either RPC endpoint wallet or wallet_name parameter must be provided", self.nodes[0].migratewallet) + self.nodes[0].migratewallet("notloaded") - assert_raises_rpc_error(-15, "Error: migratewallet on encrypted wallets is currently unsupported.", wallet.migratewallet) - # TODO: Fix migratewallet so that we can actually migrate encrypted wallets + info = wallet.getwalletinfo() + assert_equal(info["descriptors"], True) + assert_equal(info["format"], "sqlite") + wallet.gettransaction(txid) + + assert_equal(bals, wallet.getbalances()) + + def test_unloaded_by_path(self): + self.log.info("Test migration of a wallet that isn't loaded, specified by path") + wallet = self.create_legacy_wallet("notloaded2") + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + addr = wallet.getnewaddress() + txid = default.sendtoaddress(addr, 1) + self.generate(self.nodes[0], 1) + bals = wallet.getbalances() + + wallet.unloadwallet() + + wallet_file_path = os.path.join(self.nodes[0].datadir, "regtest", "wallets", "notloaded2") + self.nodes[0].migratewallet(wallet_file_path) + + # Because we gave the name by full path, the loaded wallet's name is that path too. + wallet = self.nodes[0].get_wallet_rpc(wallet_file_path) + + info = wallet.getwalletinfo() + assert_equal(info["descriptors"], True) + assert_equal(info["format"], "sqlite") + wallet.gettransaction(txid) + + assert_equal(bals, wallet.getbalances()) def run_test(self): self.generate(self.nodes[0], 101) @@ -416,6 +480,8 @@ class WalletMigrationTest(BitcoinTestFramework): self.test_no_privkeys() self.test_pk_coinbases() self.test_encrypted() + self.test_unloaded() + self.test_unloaded_by_path() if __name__ == '__main__': WalletMigrationTest().main() diff --git a/test/functional/wallet_transactiontime_rescan.py b/test/functional/wallet_transactiontime_rescan.py index de9616b4a1..904013cdef 100755 --- a/test/functional/wallet_transactiontime_rescan.py +++ b/test/functional/wallet_transactiontime_rescan.py @@ -14,6 +14,9 @@ from test_framework.util import ( assert_raises_rpc_error, set_node_times, ) +from test_framework.wallet_util import ( + get_generate_key, +) class TransactionTimeRescanTest(BitcoinTestFramework): @@ -23,6 +26,10 @@ class TransactionTimeRescanTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 3 + self.extra_args = [["-keypool=400"], + ["-keypool=400"], + [] + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -167,6 +174,38 @@ class TransactionTimeRescanTest(BitcoinTestFramework): assert_raises_rpc_error(-8, "Invalid stop_height", restorewo_wallet.rescanblockchain, 1, -1) assert_raises_rpc_error(-8, "stop_height must be greater than start_height", restorewo_wallet.rescanblockchain, 20, 10) + self.log.info("Test `rescanblockchain` fails when wallet is encrypted and locked") + usernode.createwallet(wallet_name="enc_wallet", passphrase="passphrase") + enc_wallet = usernode.get_wallet_rpc("enc_wallet") + assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", enc_wallet.rescanblockchain) + + if not self.options.descriptors: + self.log.info("Test rescanning an encrypted wallet") + hd_seed = get_generate_key().privkey + + usernode.createwallet(wallet_name="temp_wallet", blank=True, descriptors=False) + temp_wallet = usernode.get_wallet_rpc("temp_wallet") + temp_wallet.sethdseed(seed=hd_seed) + + for i in range(399): + temp_wallet.getnewaddress() + + self.generatetoaddress(usernode, COINBASE_MATURITY + 1, temp_wallet.getnewaddress()) + self.generatetoaddress(usernode, COINBASE_MATURITY + 1, temp_wallet.getnewaddress()) + + minernode.createwallet("encrypted_wallet", blank=True, passphrase="passphrase", descriptors=False) + encrypted_wallet = minernode.get_wallet_rpc("encrypted_wallet") + + encrypted_wallet.walletpassphrase("passphrase", 1) + encrypted_wallet.sethdseed(seed=hd_seed) + + batch = [] + batch.append(encrypted_wallet.walletpassphrase.get_request("passphrase", 3)) + batch.append(encrypted_wallet.rescanblockchain.get_request()) + + encrypted_wallet.batch(batch) + + assert_equal(encrypted_wallet.getbalance(), temp_wallet.getbalance()) if __name__ == '__main__': TransactionTimeRescanTest().main() diff --git a/test/lint/lint-locale-dependence.py b/test/lint/lint-locale-dependence.py index b37ba05a93..faea643882 100755 --- a/test/lint/lint-locale-dependence.py +++ b/test/lint/lint-locale-dependence.py @@ -34,8 +34,6 @@ # # See https://doc.qt.io/qt-5/qcoreapplication.html#locale-settings and # https://stackoverflow.com/a/34878283 for more details. -# -# TODO: Reduce KNOWN_VIOLATIONS by replacing uses of locale dependent snprintf with strprintf. import re import sys diff --git a/test/lint/lint-python.py b/test/lint/lint-python.py index 4d16facfea..4ec7608708 100755 --- a/test/lint/lint-python.py +++ b/test/lint/lint-python.py @@ -47,6 +47,7 @@ ENABLED = ( 'E711,' # comparison to None should be 'if cond is None:' 'E714,' # test for object identity should be "is not" 'E721,' # do not compare types, use "isinstance()" + 'E722,' # do not use bare 'except' 'E742,' # do not define classes named "l", "O", or "I" 'E743,' # do not define functions named "l", "O", or "I" 'E901,' # SyntaxError: invalid syntax diff --git a/test/util/test_runner.py b/test/util/test_runner.py index ea3626fa65..e5cdd0bc3a 100755 --- a/test/util/test_runner.py +++ b/test/util/test_runner.py @@ -54,7 +54,7 @@ def bctester(testDir, input_basename, buildenv): try: bctest(testDir, testObj, buildenv) logging.info("PASSED: " + testObj["description"]) - except: + except Exception: logging.info("FAILED: " + testObj["description"]) failed_testcases.append(testObj["description"]) @@ -96,7 +96,7 @@ def bctest(testDir, testObj, buildenv): try: with open(os.path.join(testDir, outputFn), encoding="utf8") as f: outputData = f.read() - except: + except Exception: logging.error("Output file " + outputFn + " cannot be opened") raise if not outputData: |