diff options
181 files changed, 1643 insertions, 1078 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/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/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 6b6bb18523..f0dcee7a9b 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: |