diff options
64 files changed, 940 insertions, 147 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 691582239e..b3d58461e0 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -35,8 +35,6 @@ global_task_template: &GLOBAL_TASK_TEMPLATE folder: "/tmp/cirrus-ci-build/depends/built" depends_sdk_cache: folder: "/tmp/cirrus-ci-build/depends/sdk-sources" - depends_releases_cache: - folder: "/tmp/cirrus-ci-build/releases" ci_script: - ./ci/test_run_all.sh @@ -103,6 +101,8 @@ task: # For faster CI feedback, immediately schedule a task that compiles most modules << : *CREDITS_TEMPLATE << : *GLOBAL_TASK_TEMPLATE + depends_releases_cache: + folder: "/tmp/cirrus-ci-build/releases" container: image: ubuntu:bionic env: @@ -180,3 +180,13 @@ task: CI_USE_APT_INSTALL: "no" PACKAGE_MANAGER_INSTALL: "echo" # Nothing to do FILE_ENV: "./ci/test/00_setup_env_mac_host.sh" + +task: + name: 'ARM64 Android APK [focal]' + depends_sources_cache: + folder: "/tmp/cirrus-ci-build/depends/sources" + << : *GLOBAL_TASK_TEMPLATE + container: + image: ubuntu:focal + env: + FILE_ENV: "./ci/test/00_setup_env_android.sh" diff --git a/Makefile.am b/Makefile.am index 66be768277..be62c3e6a9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -78,6 +78,7 @@ COVERAGE_INFO = $(COV_TOOL_WRAPPER) baseline.info \ dist-hook: -$(GIT) archive --format=tar HEAD -- src/clientversion.cpp | $(AMTAR) -C $(top_distdir) -xf - +if TARGET_WINDOWS $(BITCOIN_WIN_INSTALLER): all-recursive $(MKDIR_P) $(top_builddir)/release STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIND_BIN) $(top_builddir)/release @@ -90,6 +91,10 @@ $(BITCOIN_WIN_INSTALLER): all-recursive echo error: could not build $@ @echo built $@ +deploy: $(BITCOIN_WIN_INSTALLER) +endif + +if TARGET_DARWIN $(OSX_APP)/Contents/PkgInfo: $(MKDIR_P) $(@D) @echo "APPL????" > $@ @@ -133,7 +138,7 @@ $(OSX_BACKGROUND_IMAGE): $(OSX_BACKGROUND_IMAGE).png $(OSX_BACKGROUND_IMAGE)@2x. tiffutil -cathidpicheck $^ -out $@ deploydir: $(OSX_DMG) -else +else !BUILD_DARWIN APP_DIST_DIR=$(top_builddir)/dist APP_DIST_EXTRAS=$(APP_DIST_DIR)/.background/$(OSX_BACKGROUND_IMAGE) $(APP_DIST_DIR)/.DS_Store $(APP_DIST_DIR)/Applications @@ -160,15 +165,11 @@ $(APP_DIST_DIR)/$(OSX_APP)/Contents/MacOS/Bitcoin-Qt: $(OSX_APP_BUILT) $(OSX_PAC INSTALLNAMETOOL=$(INSTALLNAMETOOL) OTOOL=$(OTOOL) STRIP=$(STRIP) $(PYTHON) $(OSX_DEPLOY_SCRIPT) $(OSX_APP) $(OSX_VOLNAME) -translations-dir=$(QT_TRANSLATION_DIR) deploydir: $(APP_DIST_EXTRAS) -endif +endif !BUILD_DARWIN -if TARGET_DARWIN appbundle: $(OSX_APP_BUILT) deploy: $(OSX_DMG) endif -if TARGET_WINDOWS -deploy: $(BITCOIN_WIN_INSTALLER) -endif $(BITCOIN_QT_BIN): FORCE $(MAKE) -C src qt/$(@F) diff --git a/build_msvc/bitcoin_config.h b/build_msvc/bitcoin_config.h index dd01cb29eb..aba5607eb6 100644 --- a/build_msvc/bitcoin_config.h +++ b/build_msvc/bitcoin_config.h @@ -181,9 +181,6 @@ /* Define to 1 if you have the <miniupnpc/miniupnpc.h> header file. */ #define HAVE_MINIUPNPC_MINIUPNPC_H 1 -/* Define to 1 if you have the <miniupnpc/miniwget.h> header file. */ -#define HAVE_MINIUPNPC_MINIWGET_H 1 - /* Define to 1 if you have the <miniupnpc/upnpcommands.h> header file. */ #define HAVE_MINIUPNPC_UPNPCOMMANDS_H 1 diff --git a/ci/test/00_setup_env.sh b/ci/test/00_setup_env.sh index 87cf8538f6..e6aec723bc 100755 --- a/ci/test/00_setup_env.sh +++ b/ci/test/00_setup_env.sh @@ -11,6 +11,9 @@ export LC_ALL=C.UTF-8 # This is where the depends build is done. BASE_ROOT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../../ >/dev/null 2>&1 && pwd ) export BASE_ROOT_DIR +# The depends dir. +# This folder exists on the ci host and ci guest. Changes are propagated back and forth. +export DEPENDS_DIR=${DEPENDS_DIR:-$BASE_ROOT_DIR/depends} echo "Setting specific values in env" if [ -n "${FILE_ENV}" ]; then @@ -56,9 +59,6 @@ export CCACHE_COMPRESS=${CCACHE_COMPRESS:-1} # The cache dir. # This folder exists on the ci host and ci guest. Changes are propagated back and forth. export CCACHE_DIR=${CCACHE_DIR:-$BASE_SCRATCH_DIR/.ccache} -# The depends dir. -# This folder exists on the ci host and ci guest. Changes are propagated back and forth. -export DEPENDS_DIR=${DEPENDS_DIR:-$BASE_ROOT_DIR/depends} # Folder where the build result is put (bin and lib). export BASE_OUTDIR=${BASE_OUTDIR:-$BASE_SCRATCH_DIR/out/$HOST} # Folder where the build is done (dist and out-of-tree build). diff --git a/ci/test/00_setup_env_android.sh b/ci/test/00_setup_env_android.sh new file mode 100644 index 0000000000..f78a84eeac --- /dev/null +++ b/ci/test/00_setup_env_android.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2019-2020 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +export LC_ALL=C.UTF-8 + +export HOST=aarch64-linux-android +export PACKAGES="clang llvm unzip openjdk-8-jdk gradle" +export CONTAINER_NAME=ci_android +export DOCKER_NAME_TAG="ubuntu:focal" + +export RUN_UNIT_TESTS=false +export RUN_FUNCTIONAL_TESTS=false + +export ANDROID_API_LEVEL=28 +export ANDROID_BUILD_TOOLS_VERSION=28.0.3 +export ANDROID_NDK_VERSION=21.1.6352462 +export ANDROID_TOOLS_URL=https://dl.google.com/android/repository/commandlinetools-linux-6609375_latest.zip +export ANDROID_HOME="${DEPENDS_DIR}/SDKs/android" +export ANDROID_NDK_HOME="${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}" +export DEP_OPTS="ANDROID_SDK=${ANDROID_HOME} ANDROID_NDK=${ANDROID_NDK_HOME} ANDROID_API_LEVEL=${ANDROID_API_LEVEL} ANDROID_TOOLCHAIN_BIN=${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/" + +export BITCOIN_CONFIG="--disable-ccache" diff --git a/ci/test/05_before_script.sh b/ci/test/05_before_script.sh index f69afd8a26..8dd489d7f8 100755 --- a/ci/test/05_before_script.sh +++ b/ci/test/05_before_script.sh @@ -22,6 +22,15 @@ if [ -n "$XCODE_VERSION" ] && [ ! -f "$OSX_SDK_PATH" ]; then DOCKER_EXEC curl --location --fail "${SDK_URL}/${OSX_SDK_BASENAME}" -o "$OSX_SDK_PATH" fi +if [ -n "$ANDROID_TOOLS_URL" ]; then + ANDROID_TOOLS_PATH=$DEPENDS_DIR/sdk-sources/android-tools.zip + + DOCKER_EXEC curl --location --fail "${ANDROID_TOOLS_URL}" -o "$ANDROID_TOOLS_PATH" + DOCKER_EXEC mkdir -p "${ANDROID_HOME}/cmdline-tools" + DOCKER_EXEC unzip -o "$ANDROID_TOOLS_PATH" -d "${ANDROID_HOME}/cmdline-tools" + DOCKER_EXEC "yes | ${ANDROID_HOME}/cmdline-tools/tools/bin/sdkmanager --install \"build-tools;${ANDROID_BUILD_TOOLS_VERSION}\" \"platform-tools\" \"platforms;android-${ANDROID_API_LEVEL}\" \"ndk;${ANDROID_NDK_VERSION}\"" +fi + if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then # Use BDB compiled using install_db4.sh script to work around linking issue when using BDB # from depends. See https://github.com/bitcoin/bitcoin/pull/18288#discussion_r433189350 for diff --git a/ci/test/06_script_a.sh b/ci/test/06_script_a.sh index 7986f7665a..a42cd6cee1 100755 --- a/ci/test/06_script_a.sh +++ b/ci/test/06_script_a.sh @@ -6,6 +6,14 @@ export LC_ALL=C.UTF-8 +if [ -n "$ANDROID_TOOLS_URL" ]; then + DOCKER_EXEC make distclean || true + DOCKER_EXEC ./autogen.sh + DOCKER_EXEC ./configure $BITCOIN_CONFIG --prefix=$DEPENDS_DIR/aarch64-linux-android || ( (DOCKER_EXEC cat config.log) && false) + DOCKER_EXEC "cd src/qt && make $MAKEJOBS && ANDROID_HOME=${ANDROID_HOME} ANDROID_NDK_HOME=${ANDROID_NDK_HOME} make apk" + exit 0 +fi + BITCOIN_CONFIG_ALL="--enable-suppress-external-warnings --disable-dependency-tracking --prefix=$DEPENDS_DIR/$HOST --bindir=$BASE_OUTDIR/bin --libdir=$BASE_OUTDIR/lib" if [ -z "$NO_WERROR" ]; then BITCOIN_CONFIG_ALL="${BITCOIN_CONFIG_ALL} --enable-werror" diff --git a/configure.ac b/configure.ac index 0b176e6039..9ac2643374 100644 --- a/configure.ac +++ b/configure.ac @@ -734,6 +734,21 @@ case $host in *android*) dnl make sure android stays above linux for hosts like *linux-android* TARGET_OS=android + case $host in + *x86_64*) + ANDROID_ARCH=x86_64 + ;; + *aarch64*) + ANDROID_ARCH=arm64-v8a + ;; + *armv7a*) + ANDROID_ARCH=armeabi-v7a + ;; + *i686*) + ANDROID_ARCH=i686 + ;; + *) AC_MSG_ERROR("Could not determine Android arch") ;; + esac ;; *linux*) TARGET_OS=linux @@ -866,11 +881,16 @@ if test x$use_hardening != xno; then dnl Use CHECK_LINK_FLAG & --fatal-warnings to ensure we won't use the flag in this case. AX_CHECK_LINK_FLAG([-fcf-protection=full],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fcf-protection=full"],, [[$LDFLAG_WERROR]]) - dnl stack-clash-protection does not work properly when building for Windows. - dnl We use the test case from https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90458 - dnl to determine if it can be enabled. - AX_CHECK_COMPILE_FLAG([-fstack-clash-protection],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fstack-clash-protection"],[],["-O0"], - [AC_LANG_SOURCE([[class D {public: unsigned char buf[32768];}; int main() {D d; return 0;}]])]) + case $host in + *mingw*) + dnl stack-clash-protection doesn't currently work, and likely should just be skipped for Windows. + dnl See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90458 for more details. + ;; + *) + AX_CHECK_COMPILE_FLAG([-fstack-clash-protection],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fstack-clash-protection"]) + ;; + esac + dnl When enable_debug is yes, all optimizations are disabled. dnl However, FORTIFY_SOURCE requires that there is some level of optimization, otherwise it does nothing and just creates a compiler warning. @@ -1346,7 +1366,7 @@ fi dnl Check for libminiupnpc (optional) if test x$use_upnp != xno; then AC_CHECK_HEADERS( - [miniupnpc/miniwget.h miniupnpc/miniupnpc.h miniupnpc/upnpcommands.h miniupnpc/upnperrors.h], + [miniupnpc/miniupnpc.h miniupnpc/upnpcommands.h miniupnpc/upnperrors.h], [AC_CHECK_LIB([miniupnpc], [upnpDiscover], [MINIUPNPC_LIBS=-lminiupnpc], [have_miniupnpc=no])], [have_miniupnpc=no] ) @@ -1844,6 +1864,7 @@ AC_SUBST(HAVE_BUILTIN_PREFETCH) AC_SUBST(HAVE_MM_PREFETCH) AC_SUBST(HAVE_STRONG_GETAUXVAL) AC_SUBST(HAVE_WEAK_GETAUXVAL) +AC_SUBST(ANDROID_ARCH) AC_CONFIG_FILES([Makefile src/Makefile doc/man/Makefile share/setup.nsi share/qt/Info.plist test/config.ini]) AC_CONFIG_FILES([contrib/devtools/split-debug.sh],[chmod +x contrib/devtools/split-debug.sh]) AM_COND_IF([HAVE_DOXYGEN], [AC_CONFIG_FILES([doc/Doxyfile])]) diff --git a/contrib/init/bitcoind.service b/contrib/init/bitcoind.service index 5999928aa4..93de353bb4 100644 --- a/contrib/init/bitcoind.service +++ b/contrib/init/bitcoind.service @@ -18,7 +18,7 @@ After=network-online.target Wants=network-online.target [Service] -ExecStart=/usr/bin/bitcoind -daemon \ +ExecStart=/usr/bin/bitcoind -daemonwait \ -pid=/run/bitcoind/bitcoind.pid \ -conf=/etc/bitcoin/bitcoin.conf \ -datadir=/var/lib/bitcoind @@ -33,6 +33,7 @@ ExecStartPre=/bin/chgrp bitcoin /etc/bitcoin Type=forking PIDFile=/run/bitcoind/bitcoind.pid Restart=on-failure +TimeoutStartSec=infinity TimeoutStopSec=600 # Directory creation and permissions diff --git a/depends/packages/miniupnpc.mk b/depends/packages/miniupnpc.mk index 49a584e462..99f5b0a8db 100644 --- a/depends/packages/miniupnpc.mk +++ b/depends/packages/miniupnpc.mk @@ -1,9 +1,9 @@ package=miniupnpc -$(package)_version=2.0.20180203 +$(package)_version=2.2.2 $(package)_download_path=https://miniupnp.tuxfamily.org/files/ $(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=90dda8c7563ca6cd4a83e23b3c66dbbea89603a1675bfdb852897c2c9cc220b7 -$(package)_patches=dont_use_wingen.patch +$(package)_sha256_hash=888fb0976ba61518276fe1eda988589c700a3f2a69d71089260d75562afd3687 +$(package)_patches=dont_leak_info.patch define $(package)_set_vars $(package)_build_opts=CC="$($(package)_cc)" @@ -13,9 +13,7 @@ $(package)_build_env+=CFLAGS="$($(package)_cflags) $($(package)_cppflags)" AR="$ endef define $(package)_preprocess_cmds - mkdir dll && \ - sed -e 's|MINIUPNPC_VERSION_STRING \"version\"|MINIUPNPC_VERSION_STRING \"$($(package)_version)\"|' -e 's|OS/version|$(host)|' miniupnpcstrings.h.in > miniupnpcstrings.h && \ - patch -p1 < $($(package)_patch_dir)/dont_use_wingen.patch + patch -p1 < $($(package)_patch_dir)/dont_leak_info.patch endef define $(package)_build_cmds diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 0ac6f496c4..ee6796e2ad 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -9,7 +9,8 @@ $(package)_qt_libs=corelib network widgets gui plugins testlib $(package)_patches=fix_qt_pkgconfig.patch mac-qmake.conf fix_no_printer.patch no-xlib.patch $(package)_patches+= fix_android_qmake_conf.patch fix_android_jni_static.patch dont_hardcode_pwd.patch $(package)_patches+= drop_lrelease_dependency.patch no_sdk_version_check.patch -$(package)_patches+= fix_qpainter_non_determinism.patch fix_lib_paths.patch +$(package)_patches+= fix_qpainter_non_determinism.patch fix_lib_paths.patch fix_android_pch.patch +$(package)_patches+= fix_bigsur_drawing.patch $(package)_qttranslations_file_name=qttranslations-$($(package)_suffix) $(package)_qttranslations_sha256_hash=e1de58ed108b7e0a138815ea60fd46a2c4e1fc31396a707e5630e92de79c53de @@ -111,6 +112,7 @@ $(package)_config_opts += -no-feature-xml $(package)_config_opts_darwin = -no-dbus $(package)_config_opts_darwin += -no-opengl $(package)_config_opts_darwin += -pch +$(package)_config_opts_darwin += -no-feature-corewlan $(package)_config_opts_darwin += -device-option QMAKE_MACOSX_DEPLOYMENT_TARGET=$(OSX_MIN_VERSION) ifneq ($(build_os),darwin) @@ -164,6 +166,7 @@ $(package)_config_opts_android += -no-fontconfig $(package)_config_opts_android += -L $(host_prefix)/lib $(package)_config_opts_android += -I $(host_prefix)/include $(package)_config_opts_android += -pch +$(package)_config_opts_android += -no-feature-vulkan $(package)_config_opts_aarch64_android += -android-arch arm64-v8a $(package)_config_opts_armv7a_android += -android-arch armeabi-v7a @@ -223,10 +226,12 @@ define $(package)_preprocess_cmds patch -p1 -i $($(package)_patch_dir)/fix_no_printer.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_android_qmake_conf.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_android_jni_static.patch && \ + patch -p1 -i $($(package)_patch_dir)/fix_android_pch.patch && \ patch -p1 -i $($(package)_patch_dir)/no-xlib.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_qpainter_non_determinism.patch &&\ patch -p1 -i $($(package)_patch_dir)/no_sdk_version_check.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_lib_paths.patch && \ + patch -p1 -i $($(package)_patch_dir)/fix_bigsur_drawing.patch && \ sed -i.old "s|updateqm.commands = \$$$$\$$$$LRELEASE|updateqm.commands = $($(package)_extract_dir)/qttools/bin/lrelease|" qttranslations/translations/translations.pro && \ mkdir -p qtbase/mkspecs/macx-clang-linux &&\ cp -f qtbase/mkspecs/macx-clang/qplatformdefs.h qtbase/mkspecs/macx-clang-linux/ &&\ diff --git a/depends/patches/miniupnpc/dont_leak_info.patch b/depends/patches/miniupnpc/dont_leak_info.patch new file mode 100644 index 0000000000..512f9c50ea --- /dev/null +++ b/depends/patches/miniupnpc/dont_leak_info.patch @@ -0,0 +1,32 @@ +commit 8815452257437ba36607d0e2381c01142d1c7bb0 +Author: fanquake <fanquake@gmail.com> +Date: Thu Nov 19 10:51:19 2020 +0800 + + Don't leak OS and miniupnpc version info in User-Agent + +diff --git a//minisoap.c b/minisoap.c +index 7860667..775580b 100644 +--- a/minisoap.c ++++ b/minisoap.c +@@ -90,7 +90,7 @@ int soapPostSubmit(SOCKET fd, + headerssize = snprintf(headerbuf, sizeof(headerbuf), + "POST %s HTTP/%s\r\n" + "Host: %s%s\r\n" +- "User-Agent: " OS_STRING ", " UPNP_VERSION_STRING ", MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" ++ "User-Agent: " UPNP_VERSION_STRING "\r\n" + "Content-Length: %d\r\n" + "Content-Type: text/xml\r\n" + "SOAPAction: \"%s\"\r\n" +diff --git a/miniwget.c b/miniwget.c +index d5b7970..05aeb9c 100644 +--- a/miniwget.c ++++ b/miniwget.c +@@ -444,7 +444,7 @@ miniwget3(const char * host, + "GET %s HTTP/%s\r\n" + "Host: %s:%d\r\n" + "Connection: Close\r\n" +- "User-Agent: " OS_STRING ", " UPNP_VERSION_STRING ", MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" ++ "User-Agent: " UPNP_VERSION_STRING "\r\n" + + "\r\n", + path, httpversion, host, port); diff --git a/depends/patches/miniupnpc/dont_use_wingen.patch b/depends/patches/miniupnpc/dont_use_wingen.patch deleted file mode 100644 index a1cc9b50d1..0000000000 --- a/depends/patches/miniupnpc/dont_use_wingen.patch +++ /dev/null @@ -1,26 +0,0 @@ -commit e8077044df239bcf0d9e9980b0e1afb9f1f5c446 -Author: fanquake <fanquake@gmail.com> -Date: Tue Aug 18 20:50:19 2020 +0800 - - Don't use wingenminiupnpcstrings when generating miniupnpcstrings.h - - The wingenminiupnpcstrings tool is used on Windows to generate version - information. This information is irrelevant for us, and trying to use - wingenminiupnpcstrings would cause builds to fail, so just don't use it. - - We should be able to drop this once we are using 2.1 or later. See - upstream commit: 9663c55c61408fdcc39a82987d2243f816b22932. - -diff --git a/Makefile.mingw b/Makefile.mingw -index 574720e..fcc17bb 100644 ---- a/Makefile.mingw -+++ b/Makefile.mingw -@@ -74,7 +74,7 @@ wingenminiupnpcstrings: wingenminiupnpcstrings.o - - wingenminiupnpcstrings.o: wingenminiupnpcstrings.c - --miniupnpcstrings.h: miniupnpcstrings.h.in wingenminiupnpcstrings -+miniupnpcstrings.h: miniupnpcstrings.h.in - wingenminiupnpcstrings $< $@ - - minixml.o: minixml.c minixml.h diff --git a/depends/patches/qt/fix_android_pch.patch b/depends/patches/qt/fix_android_pch.patch new file mode 100644 index 0000000000..bed6e4bb63 --- /dev/null +++ b/depends/patches/qt/fix_android_pch.patch @@ -0,0 +1,10 @@ +--- old/qtbase/mkspecs/common/android-base-head.conf ++++ new/qtbase/mkspecs/common/android-base-head.conf +@@ -73,6 +73,6 @@ CROSS_COMPILE = $$NDK_TOOLCHAIN_PATH/bin/$$NDK_TOOLS_PREFIX- + QMAKE_PCH_OUTPUT_EXT = .gch + + QMAKE_CFLAGS_PRECOMPILE = -x c-header -c ${QMAKE_PCH_INPUT} -o ${QMAKE_PCH_OUTPUT} +-QMAKE_CFLAGS_USE_PRECOMPILE = -include ${QMAKE_PCH_OUTPUT_BASE} ++QMAKE_CFLAGS_USE_PRECOMPILE = -include-pch ${QMAKE_PCH_OUTPUT} + QMAKE_CXXFLAGS_PRECOMPILE = -x c++-header -c ${QMAKE_PCH_INPUT} -o ${QMAKE_PCH_OUTPUT} + QMAKE_CXXFLAGS_USE_PRECOMPILE = $$QMAKE_CFLAGS_USE_PRECOMPILE diff --git a/depends/patches/qt/fix_bigsur_drawing.patch b/depends/patches/qt/fix_bigsur_drawing.patch new file mode 100644 index 0000000000..98c0c6be30 --- /dev/null +++ b/depends/patches/qt/fix_bigsur_drawing.patch @@ -0,0 +1,31 @@ +Fix GUI stuck on Big Sur + +See: + - https://github.com/bitcoin-core/gui/issues/249 + - https://github.com/bitcoin/bitcoin/pull/21495 + - https://bugreports.qt.io/browse/QTBUG-87014 + +We should be able to drop this once we are using one of the following versions: + - Qt 5.12.11 or later, see upstream commit: c5d904639dbd690a36306e2b455610029704d821 + - Qt 5.15.3 or later, see upstream commit: 2cae34354bd41ae286258c7a6b3653b746e786ae + +--- a/qtbase/src/plugins/platforms/cocoa/qnsview_drawing.mm ++++ b/qtbase/src/plugins/platforms/cocoa/qnsview_drawing.mm +@@ -95,8 +95,15 @@ + // by AppKit at a point where we've already set up other parts of the platform plugin + // based on the presence of layers or not. Once we've rewritten these parts to support + // dynamically picking up layer enablement we can let AppKit do its thing. +- return QMacVersion::buildSDK() >= QOperatingSystemVersion::MacOSMojave +- && QMacVersion::currentRuntime() >= QOperatingSystemVersion::MacOSMojave; ++ ++ if (QMacVersion::currentRuntime() >= QOperatingSystemVersion::MacOSBigSur) ++ return true; // Big Sur always enables layer-backing, regardless of SDK ++ ++ if (QMacVersion::currentRuntime() >= QOperatingSystemVersion::MacOSMojave ++ && QMacVersion::buildSDK() >= QOperatingSystemVersion::MacOSMojave) ++ return true; // Mojave and Catalina enable layers based on the app's SDK ++ ++ return false; // Prior versions needed explicitly enabled layer backing + } + + - (BOOL)layerExplicitlyRequested diff --git a/doc/README.md b/doc/README.md index 19d8204d83..f32600d009 100644 --- a/doc/README.md +++ b/doc/README.md @@ -44,6 +44,7 @@ The following are developer notes on how to build Bitcoin Core on your native pl - [FreeBSD Build Notes](build-freebsd.md) - [OpenBSD Build Notes](build-openbsd.md) - [NetBSD Build Notes](build-netbsd.md) +- [Android Build Notes](build-android.md) - [Gitian Building Guide (External Link)](https://github.com/bitcoin-core/docs/blob/master/gitian-building.md) Development diff --git a/doc/build-android.md b/doc/build-android.md new file mode 100644 index 0000000000..7a8a9e6a65 --- /dev/null +++ b/doc/build-android.md @@ -0,0 +1,12 @@ +ANDROID BUILD NOTES +====================== + +This guide describes how to build and package the `bitcoin-qt` GUI for Android on Linux and macOS. + +## Preparation + +You will need to get the Android NDK and build dependencies for Android as described in [depends/README.md](../depends/README.md). + +## Building and packaging + +After the depends are built configure with one of the resulting prefixes and run `make && make apk` in `src/qt`.
\ No newline at end of file diff --git a/doc/dependencies.md b/doc/dependencies.md index 1397fe9d0c..22161856ce 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -17,7 +17,7 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | libnatpmp | git commit [4536032...](https://github.com/miniupnp/libnatpmp/tree/4536032ae32268a45c073a4d5e91bbab4534773a) | | No | | | | libpng | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | | librsvg | | | | | | -| MiniUPnPc | [2.0.20180203](https://miniupnp.tuxfamily.org/files) | | No | | | +| MiniUPnPc | [2.2.2](https://miniupnp.tuxfamily.org/files) | | No | | | | PCRE | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | | Python (tests) | | [3.6](https://www.python.org/downloads) | | | | | qrencode | [3.4.4](https://fukuchi.org/works/qrencode) | | No | | | diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 59cfdb9839..399a8430ef 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -378,6 +378,20 @@ bitcoin_qt_clean: FORCE bitcoin_qt : qt/bitcoin-qt$(EXEEXT) +APK_LIB_DIR = qt/android/libs/$(ANDROID_ARCH) +QT_BASE_PATH = $(shell find ../depends/sources/ -maxdepth 1 -type f -regex ".*qtbase.*\.tar.xz") +QT_BASE_TLD = $(shell tar tf $(QT_BASE_PATH) --exclude='*/*') + +bitcoin_qt_apk: FORCE + mkdir -p $(APK_LIB_DIR) + cp $(dir $(CC))../sysroot/usr/lib/$(host_alias)/libc++_shared.so $(APK_LIB_DIR) + tar xf $(QT_BASE_PATH) -C qt/android/src/ $(QT_BASE_TLD)src/android/jar/src --strip-components=5 + tar xf $(QT_BASE_PATH) -C qt/android/src/ $(QT_BASE_TLD)src/android/java/src --strip-components=5 + tar xf $(QT_BASE_PATH) -C qt/android/res/ $(QT_BASE_TLD)src/android/java/res --strip-components=5 + cp qt/bitcoin-qt $(APK_LIB_DIR)/libbitcoin-qt.so + cd qt/android && gradle wrapper --gradle-version=6.6.1 + cd qt/android && ./gradlew build + ui_%.h: %.ui @test -f $(UIC) @$(MKDIR_P) $(@D) diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 7ed0eafb14..e00f17a83f 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -297,6 +297,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/transaction.cpp \ test/fuzz/tx_in.cpp \ test/fuzz/tx_out.cpp \ + test/fuzz/tx_pool.cpp \ test/fuzz/txrequest.cpp \ test/fuzz/validation_load_mempool.cpp \ test/fuzz/versionbits.cpp diff --git a/src/coins.h b/src/coins.h index feb441fd6a..5a6f73652b 100644 --- a/src/coins.h +++ b/src/coins.h @@ -75,6 +75,9 @@ public: ::Unserialize(s, Using<TxOutCompression>(out)); } + /** Either this coin never existed (see e.g. coinEmpty in coins.cpp), or it + * did exist and has been spent. + */ bool IsSpent() const { return out.IsNull(); } diff --git a/src/init.cpp b/src/init.cpp index 7d5420e3be..f4b7699233 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1308,7 +1308,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA LogPrintf("Config file: %s\n", config_file_path.string()); } else if (args.IsArgSet("-conf")) { // Warn if no conf file exists at path provided by user - InitWarning(strprintf(_("The specified config file %s does not exist\n"), config_file_path.string())); + InitWarning(strprintf(_("The specified config file %s does not exist"), config_file_path.string())); } else { // Not categorizing as "Warning" because it's the default behavior LogPrintf("Config file: %s (not found, skipping)\n", config_file_path.string()); diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp index 88bdba5953..268580c6e6 100644 --- a/src/node/coinstats.cpp +++ b/src/node/coinstats.cpp @@ -15,6 +15,7 @@ #include <map> +// Database-independent metric indicating the UTXO set size static uint64_t GetBogoSize(const CScript& scriptPubKey) { return 32 /* txid */ + diff --git a/src/qt/Makefile b/src/qt/Makefile index b9dcf0c599..3bd6199059 100644 --- a/src/qt/Makefile +++ b/src/qt/Makefile @@ -7,3 +7,5 @@ check: FORCE $(MAKE) -C .. test_bitcoin_qt_check bitcoin-qt bitcoin-qt.exe: FORCE $(MAKE) -C .. bitcoin_qt +apk: FORCE + $(MAKE) -C .. bitcoin_qt_apk diff --git a/src/qt/android/AndroidManifest.xml b/src/qt/android/AndroidManifest.xml new file mode 100644 index 0000000000..abb88fe89d --- /dev/null +++ b/src/qt/android/AndroidManifest.xml @@ -0,0 +1,38 @@ +<?xml version='1.0' encoding='utf-8'?> +<manifest package="org.bitcoincore.qt" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto"> + <uses-sdk android:targetSdkVersion="24"/> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + + <uses-feature android:glEsVersion="0x00020000" android:required="true" /> + + <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/> + + <application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="Bitcoin Core"> + <activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" + android:name="org.bitcoincore.qt.BitcoinQtActivity" + android:label="Bitcoin Core" + android:icon="@drawable/bitcoin" + android:screenOrientation="unspecified" + android:launchMode="singleTop"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + + <meta-data android:name="android.app.arguments" android:value="-testnet"/> + <meta-data android:name="android.app.lib_name" android:value="bitcoin-qt"/> + <meta-data android:name="android.app.repository" android:value="default"/> + <meta-data android:name="android.app.bundle_local_qt_libs" android:value="1"/> + <meta-data android:name="android.app.use_local_qt_libs" android:value="1"/> + <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/> + <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> + <meta-data android:name="android.app.background_running" android:value="true"/> + <meta-data android:name="android.app.auto_screen_scale_factor" android:value="true"/> + <meta-data android:name="android.app.extract_android_style" android:value="default"/> + </activity> + + </application> +</manifest> diff --git a/src/qt/android/build.gradle b/src/qt/android/build.gradle new file mode 100644 index 0000000000..4c36e79db8 --- /dev/null +++ b/src/qt/android/build.gradle @@ -0,0 +1,52 @@ +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.1.0' + } +} + +repositories { + google() + jcenter() +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +} + +android { + compileSdkVersion androidCompileSdkVersion.toInteger() + + buildToolsVersion androidBuildToolsVersion + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] + aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] + res.srcDirs = [qt5AndroidDir + '/res', 'res'] + resources.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + assets.srcDirs = ['assets'] + jniLibs.srcDirs = ['libs'] + } + } + + lintOptions { + abortOnError false + } + + dexOptions { + javaMaxHeapSize '4g' + } + + defaultConfig { + minSdkVersion 24 + } +} diff --git a/src/qt/android/gradle.properties b/src/qt/android/gradle.properties new file mode 100644 index 0000000000..838870f62d --- /dev/null +++ b/src/qt/android/gradle.properties @@ -0,0 +1,4 @@ +androidBuildToolsVersion=28.0.3 +androidCompileSdkVersion=28 +qt5AndroidDir=new File(".").absolutePath +org.gradle.jvmargs=-Xmx4608M diff --git a/src/qt/android/res/drawable-hdpi/bitcoin.png b/src/qt/android/res/drawable-hdpi/bitcoin.png Binary files differnew file mode 100644 index 0000000000..31a556a35f --- /dev/null +++ b/src/qt/android/res/drawable-hdpi/bitcoin.png diff --git a/src/qt/android/res/drawable-ldpi/bitcoin.png b/src/qt/android/res/drawable-ldpi/bitcoin.png Binary files differnew file mode 100644 index 0000000000..76d80d4196 --- /dev/null +++ b/src/qt/android/res/drawable-ldpi/bitcoin.png diff --git a/src/qt/android/res/drawable-mdpi/bitcoin.png b/src/qt/android/res/drawable-mdpi/bitcoin.png Binary files differnew file mode 100644 index 0000000000..c2aeab851a --- /dev/null +++ b/src/qt/android/res/drawable-mdpi/bitcoin.png diff --git a/src/qt/android/res/drawable-xhdpi/bitcoin.png b/src/qt/android/res/drawable-xhdpi/bitcoin.png Binary files differnew file mode 100644 index 0000000000..2bd5e3defc --- /dev/null +++ b/src/qt/android/res/drawable-xhdpi/bitcoin.png diff --git a/src/qt/android/res/drawable-xxhdpi/bitcoin.png b/src/qt/android/res/drawable-xxhdpi/bitcoin.png Binary files differnew file mode 100644 index 0000000000..d236cf2132 --- /dev/null +++ b/src/qt/android/res/drawable-xxhdpi/bitcoin.png diff --git a/src/qt/android/res/drawable-xxxhdpi/bitcoin.png b/src/qt/android/res/drawable-xxxhdpi/bitcoin.png Binary files differnew file mode 100644 index 0000000000..bb1dbc3554 --- /dev/null +++ b/src/qt/android/res/drawable-xxxhdpi/bitcoin.png diff --git a/src/qt/android/src/org/bitcoincore/qt/BitcoinQtActivity.java b/src/qt/android/src/org/bitcoincore/qt/BitcoinQtActivity.java new file mode 100644 index 0000000000..cf3b4f6668 --- /dev/null +++ b/src/qt/android/src/org/bitcoincore/qt/BitcoinQtActivity.java @@ -0,0 +1,29 @@ +package org.bitcoincore.qt; + +import android.os.Bundle; +import android.system.ErrnoException; +import android.system.Os; + +import org.qtproject.qt5.android.bindings.QtActivity; + +import java.io.File; + +public class BitcoinQtActivity extends QtActivity +{ + @Override + public void onCreate(Bundle savedInstanceState) + { + final File bitcoinDir = new File(getFilesDir().getAbsolutePath() + "/.bitcoin"); + if (!bitcoinDir.exists()) { + bitcoinDir.mkdir(); + } + + try { + Os.setenv("QT_QPA_PLATFORM", "android", true); + } catch (ErrnoException e) { + e.printStackTrace(); + } + + super.onCreate(savedInstanceState); + } +} diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 791a29f7a0..fc6d0febc2 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -466,6 +466,12 @@ int GuiMain(int argc, char* argv[]) QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#if defined(QT_QPA_PLATFORM_ANDROID) + QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); + QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); + QApplication::setAttribute(Qt::AA_DontUseNativeDialogs); +#endif + BitcoinApplication app; QFontDatabase::addApplicationFont(":/fonts/monospace"); diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp index 1467801522..113bd30a0c 100644 --- a/src/qt/createwalletdialog.cpp +++ b/src/qt/createwalletdialog.cpp @@ -53,6 +53,12 @@ CreateWalletDialog::CreateWalletDialog(QWidget* parent) : } }); + connect(ui->blank_wallet_checkbox, &QCheckBox::toggled, [this](bool checked) { + if (!checked) { + ui->disable_privkeys_checkbox->setChecked(false); + } + }); + #ifndef USE_SQLITE ui->descriptor_checkbox->setToolTip(tr("Compiled without sqlite support (required for descriptor wallets)")); ui->descriptor_checkbox->setEnabled(false); diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 32890ed0fe..b4afdbcc22 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -759,14 +759,14 @@ QString formatNiceTimeOffset(qint64 secs) QString formatBytes(uint64_t bytes) { - if(bytes < 1024) + if (bytes < 1'000) return QObject::tr("%1 B").arg(bytes); - if(bytes < 1024 * 1024) - return QObject::tr("%1 KB").arg(bytes / 1024); - if(bytes < 1024 * 1024 * 1024) - return QObject::tr("%1 MB").arg(bytes / 1024 / 1024); + if (bytes < 1'000'000) + return QObject::tr("%1 kB").arg(bytes / 1'000); + if (bytes < 1'000'000'000) + return QObject::tr("%1 MB").arg(bytes / 1'000'000); - return QObject::tr("%1 GB").arg(bytes / 1024 / 1024 / 1024); + return QObject::tr("%1 GB").arg(bytes / 1'000'000'000); } qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal minPointSize, qreal font_size) { diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index 96f6202874..58f6c14e7a 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -235,9 +235,9 @@ void PaymentServer::handleURIOrFile(const QString& s) if (!IsValidDestinationString(recipient.address.toStdString())) { if (uri.hasQueryItem("r")) { // payment request Q_EMIT message(tr("URI handling"), - tr("Cannot process payment request because BIP70 is not supported.")+ - tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+ - tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."), + tr("Cannot process payment request because BIP70 is not supported.\n" + "Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.\n" + "If you are receiving this error you should request the merchant provide a BIP21 compatible URI."), CClientUIInterface::ICON_WARNING); } Q_EMIT message(tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address), @@ -258,9 +258,9 @@ void PaymentServer::handleURIOrFile(const QString& s) if (QFile::exists(s)) // payment request file { Q_EMIT message(tr("Payment request file handling"), - tr("Cannot process payment request because BIP70 is not supported.")+ - tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+ - tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."), + tr("Cannot process payment request because BIP70 is not supported.\n" + "Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.\n" + "If you are receiving this error you should request the merchant provide a BIP21 compatible URI."), CClientUIInterface::ICON_WARNING); } } diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index b258219894..34d055e5a5 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -608,7 +608,6 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ // set up peer table ui->peerWidget->setModel(model->getPeerTableModel()); ui->peerWidget->verticalHeader()->hide(); - ui->peerWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->peerWidget->setSelectionBehavior(QAbstractItemView::SelectRows); ui->peerWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); ui->peerWidget->setContextMenuPolicy(Qt::CustomContextMenu); @@ -651,7 +650,6 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ // set up ban table ui->banlistWidget->setModel(model->getBanTableModel()); ui->banlistWidget->verticalHeader()->hide(); - ui->banlistWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->banlistWidget->setSelectionBehavior(QAbstractItemView::SelectRows); ui->banlistWidget->setSelectionMode(QAbstractItemView::SingleSelection); ui->banlistWidget->setContextMenuPolicy(Qt::CustomContextMenu); diff --git a/src/qt/trafficgraphwidget.cpp b/src/qt/trafficgraphwidget.cpp index dabdf9d887..7e12410c80 100644 --- a/src/qt/trafficgraphwidget.cpp +++ b/src/qt/trafficgraphwidget.cpp @@ -79,7 +79,7 @@ void TrafficGraphWidget::paintEvent(QPaintEvent *) int base = floor(log10(fMax)); float val = pow(10.0f, base); - const QString units = tr("KB/s"); + const QString units = tr("kB/s"); const float yMarginText = 2.0; // draw lines @@ -128,10 +128,10 @@ void TrafficGraphWidget::updateRates() quint64 bytesIn = clientModel->node().getTotalBytesRecv(), bytesOut = clientModel->node().getTotalBytesSent(); - float inRate = (bytesIn - nLastBytesIn) / 1024.0f * 1000 / timer->interval(); - float outRate = (bytesOut - nLastBytesOut) / 1024.0f * 1000 / timer->interval(); - vSamplesIn.push_front(inRate); - vSamplesOut.push_front(outRate); + float in_rate_kilobytes_per_sec = static_cast<float>(bytesIn - nLastBytesIn) / timer->interval(); + float out_rate_kilobytes_per_sec = static_cast<float>(bytesOut - nLastBytesOut) / timer->interval(); + vSamplesIn.push_front(in_rate_kilobytes_per_sec); + vSamplesOut.push_front(out_rate_kilobytes_per_sec); nLastBytesIn = bytesIn; nLastBytesOut = bytesOut; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index b90dc9bf42..3ba2fa46ae 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1050,15 +1050,15 @@ static RPCHelpMan gettxoutsetinfo() RPCResult{ RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::NUM, "height", "The current block height (index)"}, - {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"}, + {RPCResult::Type::NUM, "height", "The block height (index) of the returned statistics"}, + {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at which these statistics are calculated"}, {RPCResult::Type::NUM, "transactions", "The number of transactions with unspent outputs"}, {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"}, {RPCResult::Type::NUM, "bogosize", "A meaningless metric for UTXO set size"}, {RPCResult::Type::STR_HEX, "hash_serialized_2", /* optional */ true, "The serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"}, {RPCResult::Type::STR_HEX, "muhash", /* optional */ true, "The serialized hash (only present if 'muhash' hash_type is chosen)"}, {RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk"}, - {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount"}, + {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of coins in the UTXO set"}, }}, RPCExamples{ HelpExampleCli("gettxoutsetinfo", "") diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index b55f1c72b1..0baf30aef6 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -104,7 +104,7 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman) [&] { const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider); if (opt_service) { - addr_man.SetServices(*opt_service, ServiceFlags{fuzzed_data_provider.ConsumeIntegral<uint64_t>()}); + addr_man.SetServices(*opt_service, ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS)); } }, [&] { diff --git a/src/test/fuzz/cuckoocache.cpp b/src/test/fuzz/cuckoocache.cpp index dc20dc3f62..a522c837ef 100644 --- a/src/test/fuzz/cuckoocache.cpp +++ b/src/test/fuzz/cuckoocache.cpp @@ -30,7 +30,7 @@ FUZZ_TARGET(cuckoocache) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); fuzzed_data_provider_ptr = &fuzzed_data_provider; - CuckooCache::cache<bool, RandomHasher> cuckoo_cache{}; + CuckooCache::cache<int, RandomHasher> cuckoo_cache{}; if (fuzzed_data_provider.ConsumeBool()) { const size_t megabytes = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 16); cuckoo_cache.setup_bytes(megabytes << 20); diff --git a/src/test/fuzz/parse_numbers.cpp b/src/test/fuzz/parse_numbers.cpp index 1ad5fb6a05..2c546e9b4a 100644 --- a/src/test/fuzz/parse_numbers.cpp +++ b/src/test/fuzz/parse_numbers.cpp @@ -21,6 +21,9 @@ FUZZ_TARGET(parse_numbers) uint8_t u8; (void)ParseUInt8(random_string, &u8); + uint16_t u16; + (void)ParseUInt16(random_string, &u16); + int32_t i32; (void)ParseInt32(random_string, &i32); (void)atoi(random_string); diff --git a/src/test/fuzz/pow.cpp b/src/test/fuzz/pow.cpp index 53726ca893..47b4323e81 100644 --- a/src/test/fuzz/pow.cpp +++ b/src/test/fuzz/pow.cpp @@ -34,7 +34,7 @@ FUZZ_TARGET_INIT(pow, initialize_pow) } CBlockIndex current_block{*block_header}; { - CBlockIndex* previous_block = !blocks.empty() ? &blocks[fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, blocks.size() - 1)] : nullptr; + CBlockIndex* previous_block = blocks.empty() ? nullptr : &PickValue(fuzzed_data_provider, blocks); const int current_height = (previous_block != nullptr && previous_block->nHeight != std::numeric_limits<int>::max()) ? previous_block->nHeight + 1 : 0; if (fuzzed_data_provider.ConsumeBool()) { current_block.pprev = previous_block; @@ -66,9 +66,9 @@ FUZZ_TARGET_INIT(pow, initialize_pow) } } { - const CBlockIndex* to = &blocks[fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, blocks.size() - 1)]; - const CBlockIndex* from = &blocks[fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, blocks.size() - 1)]; - const CBlockIndex* tip = &blocks[fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, blocks.size() - 1)]; + const CBlockIndex* to = &PickValue(fuzzed_data_provider, blocks); + const CBlockIndex* from = &PickValue(fuzzed_data_provider, blocks); + const CBlockIndex* tip = &PickValue(fuzzed_data_provider, blocks); try { (void)GetBlockProofEquivalentTime(*to, *from, *tip, consensus_params); } catch (const uint_error&) { diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index 04fc6da9b1..96e1cfa08f 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -68,8 +68,8 @@ void fuzz_target(FuzzBufferType buffer, const std::string& LIMIT_TO_MESSAGE_TYPE { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - ConnmanTestMsg& connman = *(ConnmanTestMsg*)g_setup->m_node.connman.get(); - TestChainState& chainstate = *(TestChainState*)&g_setup->m_node.chainman->ActiveChainstate(); + ConnmanTestMsg& connman = *static_cast<ConnmanTestMsg*>(g_setup->m_node.connman.get()); + TestChainState& chainstate = *static_cast<TestChainState*>(&g_setup->m_node.chainman->ActiveChainstate()); SetMockTime(1610000000); // any time to successfully reset ibd chainstate.ResetIbd(); diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp index ee402dba38..203c0ef8e1 100644 --- a/src/test/fuzz/process_messages.cpp +++ b/src/test/fuzz/process_messages.cpp @@ -35,8 +35,8 @@ FUZZ_TARGET_INIT(process_messages, initialize_process_messages) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - ConnmanTestMsg& connman = *(ConnmanTestMsg*)g_setup->m_node.connman.get(); - TestChainState& chainstate = *(TestChainState*)&g_setup->m_node.chainman->ActiveChainstate(); + ConnmanTestMsg& connman = *static_cast<ConnmanTestMsg*>(g_setup->m_node.connman.get()); + TestChainState& chainstate = *static_cast<TestChainState*>(&g_setup->m_node.chainman->ActiveChainstate()); SetMockTime(1610000000); // any time to successfully reset ibd chainstate.ResetIbd(); @@ -65,7 +65,7 @@ FUZZ_TARGET_INIT(process_messages, initialize_process_messages) net_msg.m_type = random_message_type; net_msg.data = ConsumeRandomLengthByteVector(fuzzed_data_provider); - CNode& random_node = *peers.at(fuzzed_data_provider.ConsumeIntegralInRange<int>(0, peers.size() - 1)); + CNode& random_node = *PickValue(fuzzed_data_provider, peers); (void)connman.ReceiveMsgFrom(random_node, net_msg); random_node.fPauseSend = false; diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp new file mode 100644 index 0000000000..f84d6702a7 --- /dev/null +++ b/src/test/fuzz/tx_pool.cpp @@ -0,0 +1,285 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <consensus/validation.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <test/util/mining.h> +#include <test/util/script.h> +#include <test/util/setup_common.h> +#include <util/rbf.h> +#include <validation.h> +#include <validationinterface.h> + +namespace { + +const TestingSetup* g_setup; +std::vector<COutPoint> g_outpoints_coinbase_init_mature; +std::vector<COutPoint> g_outpoints_coinbase_init_immature; + +struct MockedTxPool : public CTxMemPool { + void RollingFeeUpdate() + { + lastRollingFeeUpdate = GetTime(); + blockSinceLastRollingFeeBump = true; + } +}; + +void initialize_tx_pool() +{ + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); + g_setup = testing_setup.get(); + + for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) { + CTxIn in = MineBlock(g_setup->m_node, P2WSH_OP_TRUE); + // Remember the txids to avoid expensive disk acess later on + auto& outpoints = i < COINBASE_MATURITY ? + g_outpoints_coinbase_init_mature : + g_outpoints_coinbase_init_immature; + outpoints.push_back(in.prevout); + } + SyncWithValidationInterfaceQueue(); +} + +struct TransactionsDelta final : public CValidationInterface { + std::set<CTransactionRef>& m_removed; + std::set<CTransactionRef>& m_added; + + explicit TransactionsDelta(std::set<CTransactionRef>& r, std::set<CTransactionRef>& a) + : m_removed{r}, m_added{a} {} + + void TransactionAddedToMempool(const CTransactionRef& tx, uint64_t /* mempool_sequence */) override + { + Assert(m_added.insert(tx).second); + } + + void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override + { + Assert(m_removed.insert(tx).second); + } +}; + +void SetMempoolConstraints(ArgsManager& args, FuzzedDataProvider& fuzzed_data_provider) +{ + args.ForceSetArg("-limitancestorcount", + ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50))); + args.ForceSetArg("-limitancestorsize", + ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202))); + args.ForceSetArg("-limitdescendantcount", + ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50))); + args.ForceSetArg("-limitdescendantsize", + ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202))); + args.ForceSetArg("-maxmempool", + ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200))); + args.ForceSetArg("-mempoolexpiry", + ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999))); +} + +FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + const auto& node = g_setup->m_node; + auto& chainstate = node.chainman->ActiveChainstate(); + + SetMockTime(ConsumeTime(fuzzed_data_provider)); + SetMempoolConstraints(*node.args, fuzzed_data_provider); + + // All RBF-spendable outpoints + std::set<COutPoint> outpoints_rbf; + // All outpoints counting toward the total supply (subset of outpoints_rbf) + std::set<COutPoint> outpoints_supply; + for (const auto& outpoint : g_outpoints_coinbase_init_mature) { + Assert(outpoints_supply.insert(outpoint).second); + } + outpoints_rbf = outpoints_supply; + + // The sum of the values of all spendable outpoints + constexpr CAmount SUPPLY_TOTAL{COINBASE_MATURITY * 50 * COIN}; + + CTxMemPool tx_pool_{/* estimator */ nullptr, /* check_ratio */ 1}; + MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_); + + // Helper to query an amount + const CCoinsViewMemPool amount_view{WITH_LOCK(::cs_main, return &chainstate.CoinsTip()), tx_pool}; + const auto GetAmount = [&](const COutPoint& outpoint) { + Coin c; + Assert(amount_view.GetCoin(outpoint, c)); + return c.out.nValue; + }; + + while (fuzzed_data_provider.ConsumeBool()) { + { + // Total supply is the mempool fee + all outpoints + CAmount supply_now{WITH_LOCK(tx_pool.cs, return tx_pool.GetTotalFee())}; + for (const auto& op : outpoints_supply) { + supply_now += GetAmount(op); + } + Assert(supply_now == SUPPLY_TOTAL); + } + Assert(!outpoints_supply.empty()); + + // Create transaction to add to the mempool + const CTransactionRef tx = [&] { + CMutableTransaction tx_mut; + tx_mut.nVersion = CTransaction::CURRENT_VERSION; + tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>(); + const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size()); + const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size() * 2); + + CAmount amount_in{0}; + for (int i = 0; i < num_in; ++i) { + // Pop random outpoint + auto pop = outpoints_rbf.begin(); + std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints_rbf.size() - 1)); + const auto outpoint = *pop; + outpoints_rbf.erase(pop); + amount_in += GetAmount(outpoint); + + // Create input + const auto sequence = ConsumeSequence(fuzzed_data_provider); + const auto script_sig = CScript{}; + const auto script_wit_stack = std::vector<std::vector<uint8_t>>{WITNESS_STACK_ELEM_OP_TRUE}; + CTxIn in; + in.prevout = outpoint; + in.nSequence = sequence; + in.scriptSig = script_sig; + in.scriptWitness.stack = script_wit_stack; + + tx_mut.vin.push_back(in); + } + const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1000, amount_in); + const auto amount_out = (amount_in - amount_fee) / num_out; + for (int i = 0; i < num_out; ++i) { + tx_mut.vout.emplace_back(amount_out, P2WSH_OP_TRUE); + } + const auto tx = MakeTransactionRef(tx_mut); + // Restore previously removed outpoints + for (const auto& in : tx->vin) { + Assert(outpoints_rbf.insert(in.prevout).second); + } + return tx; + }(); + + if (fuzzed_data_provider.ConsumeBool()) { + SetMockTime(ConsumeTime(fuzzed_data_provider)); + } + if (fuzzed_data_provider.ConsumeBool()) { + SetMempoolConstraints(*node.args, fuzzed_data_provider); + } + if (fuzzed_data_provider.ConsumeBool()) { + tx_pool.RollingFeeUpdate(); + } + if (fuzzed_data_provider.ConsumeBool()) { + const auto& txid = fuzzed_data_provider.ConsumeBool() ? + tx->GetHash() : + PickValue(fuzzed_data_provider, outpoints_rbf).hash; + const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN); + tx_pool.PrioritiseTransaction(txid, delta); + } + + // Remember all removed and added transactions + std::set<CTransactionRef> removed; + std::set<CTransactionRef> added; + auto txr = std::make_shared<TransactionsDelta>(removed, added); + RegisterSharedValidationInterface(txr); + const bool bypass_limits = fuzzed_data_provider.ConsumeBool(); + ::fRequireStandard = fuzzed_data_provider.ConsumeBool(); + const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx_pool, tx, bypass_limits)); + const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID; + SyncWithValidationInterfaceQueue(); + UnregisterSharedValidationInterface(txr); + + Assert(accepted != added.empty()); + Assert(accepted == res.m_state.IsValid()); + Assert(accepted != res.m_state.IsInvalid()); + if (accepted) { + Assert(added.size() == 1); // For now, no package acceptance + Assert(tx == *added.begin()); + } else { + // Do not consider rejected transaction removed + removed.erase(tx); + } + + // Helper to insert spent and created outpoints of a tx into collections + using Sets = std::vector<std::reference_wrapper<std::set<COutPoint>>>; + const auto insert_tx = [](Sets created_by_tx, Sets consumed_by_tx, const auto& tx) { + for (size_t i{0}; i < tx.vout.size(); ++i) { + for (auto& set : created_by_tx) { + Assert(set.get().emplace(tx.GetHash(), i).second); + } + } + for (const auto& in : tx.vin) { + for (auto& set : consumed_by_tx) { + Assert(set.get().insert(in.prevout).second); + } + } + }; + // Add created outpoints, remove spent outpoints + { + // Outpoints that no longer exist at all + std::set<COutPoint> consumed_erased; + // Outpoints that no longer count toward the total supply + std::set<COutPoint> consumed_supply; + for (const auto& removed_tx : removed) { + insert_tx(/* created_by_tx */ {consumed_erased}, /* consumed_by_tx */ {outpoints_supply}, /* tx */ *removed_tx); + } + for (const auto& added_tx : added) { + insert_tx(/* created_by_tx */ {outpoints_supply, outpoints_rbf}, /* consumed_by_tx */ {consumed_supply}, /* tx */ *added_tx); + } + for (const auto& p : consumed_erased) { + Assert(outpoints_supply.erase(p) == 1); + Assert(outpoints_rbf.erase(p) == 1); + } + for (const auto& p : consumed_supply) { + Assert(outpoints_supply.erase(p) == 1); + } + } + } + WITH_LOCK(::cs_main, tx_pool.check(chainstate)); + const auto info_all = tx_pool.infoAll(); + if (!info_all.empty()) { + const auto& tx_to_remove = *PickValue(fuzzed_data_provider, info_all).tx; + WITH_LOCK(tx_pool.cs, tx_pool.removeRecursive(tx_to_remove, /* dummy */ MemPoolRemovalReason::BLOCK)); + std::vector<uint256> all_txids; + tx_pool.queryHashes(all_txids); + assert(all_txids.size() < info_all.size()); + WITH_LOCK(::cs_main, tx_pool.check(chainstate)); + } + SyncWithValidationInterfaceQueue(); +} + +FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + const auto& node = g_setup->m_node; + + std::vector<uint256> txids; + for (const auto& outpoint : g_outpoints_coinbase_init_mature) { + txids.push_back(outpoint.hash); + } + for (int i{0}; i <= 3; ++i) { + // Add some immature and non-existent outpoints + txids.push_back(g_outpoints_coinbase_init_immature.at(i).hash); + txids.push_back(ConsumeUInt256(fuzzed_data_provider)); + } + + CTxMemPool tx_pool{/* estimator */ nullptr, /* check_ratio */ 1}; + + while (fuzzed_data_provider.ConsumeBool()) { + const auto mut_tx = ConsumeTransaction(fuzzed_data_provider, txids); + + const auto tx = MakeTransactionRef(mut_tx); + const bool bypass_limits = fuzzed_data_provider.ConsumeBool(); + ::fRequireStandard = fuzzed_data_provider.ConsumeBool(); + const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(node.chainman->ActiveChainstate(), tx_pool, tx, bypass_limits)); + const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID; + if (accepted) { + txids.push_back(tx->GetHash()); + } + + SyncWithValidationInterfaceQueue(); + } +} +} // namespace diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp index 0a541e4186..93418ab1ff 100644 --- a/src/test/fuzz/util.cpp +++ b/src/test/fuzz/util.cpp @@ -3,8 +3,11 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <test/fuzz/util.h> +#include <test/util/script.h> +#include <util/rbf.h> #include <version.h> + void FillNode(FuzzedDataProvider& fuzzed_data_provider, CNode& node, bool init_version) noexcept { const ServiceFlags remote_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS); @@ -23,3 +26,78 @@ void FillNode(FuzzedDataProvider& fuzzed_data_provider, CNode& node, bool init_v node.m_tx_relay->fRelayTxes = filter_txs; } } + +CMutableTransaction ConsumeTransaction(FuzzedDataProvider& fuzzed_data_provider, const std::optional<std::vector<uint256>>& prevout_txids, const int max_num_in, const int max_num_out) noexcept +{ + CMutableTransaction tx_mut; + const auto p2wsh_op_true = fuzzed_data_provider.ConsumeBool(); + tx_mut.nVersion = fuzzed_data_provider.ConsumeBool() ? + CTransaction::CURRENT_VERSION : + fuzzed_data_provider.ConsumeIntegral<int32_t>(); + tx_mut.nLockTime = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); + const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, max_num_in); + const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, max_num_out); + for (int i = 0; i < num_in; ++i) { + const auto& txid_prev = prevout_txids ? + PickValue(fuzzed_data_provider, *prevout_txids) : + ConsumeUInt256(fuzzed_data_provider); + const auto index_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, max_num_out); + const auto sequence = ConsumeSequence(fuzzed_data_provider); + const auto script_sig = p2wsh_op_true ? CScript{} : ConsumeScript(fuzzed_data_provider); + CScriptWitness script_wit; + if (p2wsh_op_true) { + script_wit.stack = std::vector<std::vector<uint8_t>>{WITNESS_STACK_ELEM_OP_TRUE}; + } else { + script_wit = ConsumeScriptWitness(fuzzed_data_provider); + } + CTxIn in; + in.prevout = COutPoint{txid_prev, index_out}; + in.nSequence = sequence; + in.scriptSig = script_sig; + in.scriptWitness = script_wit; + + tx_mut.vin.push_back(in); + } + for (int i = 0; i < num_out; ++i) { + const auto amount = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-10, 50 * COIN + 10); + const auto script_pk = p2wsh_op_true ? + P2WSH_OP_TRUE : + ConsumeScript(fuzzed_data_provider, /* max_length */ 128, /* maybe_p2wsh */ true); + tx_mut.vout.emplace_back(amount, script_pk); + } + return tx_mut; +} + +CScriptWitness ConsumeScriptWitness(FuzzedDataProvider& fuzzed_data_provider, const size_t max_stack_elem_size) noexcept +{ + CScriptWitness ret; + const auto n_elements = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, max_stack_elem_size); + for (size_t i = 0; i < n_elements; ++i) { + ret.stack.push_back(ConsumeRandomLengthByteVector(fuzzed_data_provider)); + } + return ret; +} + +CScript ConsumeScript(FuzzedDataProvider& fuzzed_data_provider, const size_t max_length, const bool maybe_p2wsh) noexcept +{ + const std::vector<uint8_t> b = ConsumeRandomLengthByteVector(fuzzed_data_provider); + CScript r_script{b.begin(), b.end()}; + if (maybe_p2wsh && fuzzed_data_provider.ConsumeBool()) { + uint256 script_hash; + CSHA256().Write(&r_script[0], r_script.size()).Finalize(script_hash.begin()); + r_script.clear(); + r_script << OP_0 << ToByteVector(script_hash); + } + return r_script; +} + +uint32_t ConsumeSequence(FuzzedDataProvider& fuzzed_data_provider) noexcept +{ + return fuzzed_data_provider.ConsumeBool() ? + fuzzed_data_provider.PickValueInArray({ + CTxIn::SEQUENCE_FINAL, + CTxIn::SEQUENCE_FINAL - 1, + MAX_BIP125_RBF_SEQUENCE, + }) : + fuzzed_data_provider.ConsumeIntegral<uint32_t>(); +} diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index cdddad82b3..d5c54911c5 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -48,6 +48,16 @@ void CallOneOf(FuzzedDataProvider& fuzzed_data_provider, Callables... callables) return ((i++ == call_index ? callables() : void()), ...); } +template <typename Collection> +auto& PickValue(FuzzedDataProvider& fuzzed_data_provider, Collection& col) +{ + const auto sz = col.size(); + assert(sz >= 1); + auto it = col.begin(); + std::advance(it, fuzzed_data_provider.ConsumeIntegralInRange<decltype(sz)>(0, sz - 1)); + return *it; +} + [[nodiscard]] inline std::vector<uint8_t> ConsumeRandomLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, const size_t max_length = 4096) noexcept { const std::string s = fuzzed_data_provider.ConsumeRandomLengthString(max_length); @@ -125,11 +135,13 @@ template <typename WeakEnumType, size_t size> return fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(time_min, time_max); } -[[nodiscard]] inline CScript ConsumeScript(FuzzedDataProvider& fuzzed_data_provider) noexcept -{ - const std::vector<uint8_t> b = ConsumeRandomLengthByteVector(fuzzed_data_provider); - return {b.begin(), b.end()}; -} +[[nodiscard]] CMutableTransaction ConsumeTransaction(FuzzedDataProvider& fuzzed_data_provider, const std::optional<std::vector<uint256>>& prevout_txids, const int max_num_in = 10, const int max_num_out = 10) noexcept; + +[[nodiscard]] CScriptWitness ConsumeScriptWitness(FuzzedDataProvider& fuzzed_data_provider, const size_t max_stack_elem_size = 32) noexcept; + +[[nodiscard]] CScript ConsumeScript(FuzzedDataProvider& fuzzed_data_provider, const size_t max_length = 4096, const bool maybe_p2wsh = false) noexcept; + +[[nodiscard]] uint32_t ConsumeSequence(FuzzedDataProvider& fuzzed_data_provider) noexcept; [[nodiscard]] inline CScriptNum ConsumeScriptNum(FuzzedDataProvider& fuzzed_data_provider) noexcept { diff --git a/src/test/fuzz/versionbits.cpp b/src/test/fuzz/versionbits.cpp index a898e2782d..88c1a1a9cb 100644 --- a/src/test/fuzz/versionbits.cpp +++ b/src/test/fuzz/versionbits.cpp @@ -25,18 +25,18 @@ private: const Consensus::Params dummy_params{}; public: - const int64_t m_begin = 0; - const int64_t m_end = 0; - const int m_period = 0; - const int m_threshold = 0; - const int m_bit = 0; + const int64_t m_begin; + const int64_t m_end; + const int m_period; + const int m_threshold; + const int m_bit; TestConditionChecker(int64_t begin, int64_t end, int period, int threshold, int bit) : m_begin{begin}, m_end{end}, m_period{period}, m_threshold{threshold}, m_bit{bit} { assert(m_period > 0); assert(0 <= m_threshold && m_threshold <= m_period); - assert(0 <= m_bit && m_bit <= 32 && m_bit < VERSIONBITS_NUM_BITS); + assert(0 <= m_bit && m_bit < 32 && m_bit < VERSIONBITS_NUM_BITS); } bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return Condition(pindex->nVersion); } @@ -49,9 +49,10 @@ public: int GetStateSinceHeightFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateSinceHeightFor(pindexPrev, dummy_params, m_cache); } BIP9Stats GetStateStatisticsFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateStatisticsFor(pindexPrev, dummy_params); } - bool Condition(int64_t version) const + bool Condition(int32_t version) const { - return ((version >> m_bit) & 1) != 0 && (version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS; + uint32_t mask = ((uint32_t)1) << m_bit; + return (((version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (version & mask) != 0); } bool Condition(const CBlockIndex* pindex) const { return Condition(pindex->nVersion); } @@ -94,18 +95,20 @@ public: } }; +std::unique_ptr<const CChainParams> g_params; + void initialize() { - SelectParams(CBaseChainParams::MAIN); + // this is actually comparatively slow, so only do it once + g_params = CreateChainParams(ArgsManager{}, CBaseChainParams::MAIN); + assert(g_params != nullptr); } -} // namespace -constexpr uint32_t MAX_TIME = 4102444800; // 2100-01-01 +constexpr uint32_t MAX_START_TIME = 4102444800; // 2100-01-01 FUZZ_TARGET_INIT(versionbits, initialize) { - const CChainParams& params = Params(); - + const CChainParams& params = *g_params; const int64_t interval = params.GetConsensus().nPowTargetSpacing; assert(interval > 1); // need to be able to halve it assert(interval < std::numeric_limits<int32_t>::max()); @@ -122,9 +125,9 @@ FUZZ_TARGET_INIT(versionbits, initialize) // too many blocks at 10min each might cause uint32_t time to overflow if // block_start_time is at the end of the range above - assert(std::numeric_limits<uint32_t>::max() - MAX_TIME > interval * max_blocks); + assert(std::numeric_limits<uint32_t>::max() - MAX_START_TIME > interval * max_blocks); - const int64_t block_start_time = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(params.GenesisBlock().nTime, MAX_TIME); + const int64_t block_start_time = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(params.GenesisBlock().nTime, MAX_START_TIME); // what values for version will we use to signal / not signal? const int32_t ver_signal = fuzzed_data_provider.ConsumeIntegral<int32_t>(); @@ -173,8 +176,10 @@ FUZZ_TARGET_INIT(versionbits, initialize) if (checker.Condition(ver_nosignal)) return; if (ver_nosignal < 0) return; - // TOP_BITS should ensure version will be positive + // TOP_BITS should ensure version will be positive and meet min + // version requirement assert(ver_signal > 0); + assert(ver_signal >= VERSIONBITS_LAST_OLD_BLOCK_VERSION); // Now that we have chosen time and versions, setup to mine blocks Blocks blocks(block_start_time, interval, ver_signal, ver_nosignal); @@ -203,7 +208,7 @@ FUZZ_TARGET_INIT(versionbits, initialize) } // don't risk exceeding max_blocks or times may wrap around - if (blocks.size() + period*2 > max_blocks) break; + if (blocks.size() + 2 * period > max_blocks) break; } // NOTE: fuzzed_data_provider may be fully consumed at this point and should not be used further @@ -316,7 +321,7 @@ FUZZ_TARGET_INIT(versionbits, initialize) assert(false); } - if (blocks.size() >= max_periods * period) { + if (blocks.size() >= period * max_periods) { // we chose the timeout (and block times) so that by the time we have this many blocks it's all over assert(state == ThresholdState::ACTIVE || state == ThresholdState::FAILED); } @@ -343,3 +348,4 @@ FUZZ_TARGET_INIT(versionbits, initialize) } } } +} // namespace diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 98a322eae5..5ac09b05db 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -77,6 +77,9 @@ BOOST_AUTO_TEST_CASE(util_check) const int two = *Assert(p_two); Assert(two == 2); Assert(true); + // Check that Assume can be used as unary expression + const bool result{Assume(two == 2)}; + Assert(result); } BOOST_AUTO_TEST_CASE(util_criticalsection) @@ -1450,7 +1453,6 @@ BOOST_AUTO_TEST_CASE(test_ParseInt32) BOOST_CHECK(!ParseInt32("1a", &n)); BOOST_CHECK(!ParseInt32("aap", &n)); BOOST_CHECK(!ParseInt32("0x1", &n)); // no hex - BOOST_CHECK(!ParseInt32("0x1", &n)); // no hex BOOST_CHECK(!ParseInt32(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseInt32("-2147483649", nullptr)); @@ -1513,7 +1515,6 @@ BOOST_AUTO_TEST_CASE(test_ParseUInt8) BOOST_CHECK(!ParseUInt8("1a", &n)); BOOST_CHECK(!ParseUInt8("aap", &n)); BOOST_CHECK(!ParseUInt8("0x1", &n)); // no hex - BOOST_CHECK(!ParseUInt8("0x1", &n)); // no hex BOOST_CHECK(!ParseUInt8(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseUInt8("-255", &n)); @@ -1523,6 +1524,41 @@ BOOST_AUTO_TEST_CASE(test_ParseUInt8) BOOST_CHECK(!ParseUInt8("256", nullptr)); } +BOOST_AUTO_TEST_CASE(test_ParseUInt16) +{ + uint16_t n; + // Valid values + BOOST_CHECK(ParseUInt16("1234", nullptr)); + BOOST_CHECK(ParseUInt16("0", &n) && n == 0); + BOOST_CHECK(ParseUInt16("1234", &n) && n == 1234); + BOOST_CHECK(ParseUInt16("01234", &n) && n == 1234); // no octal + BOOST_CHECK(ParseUInt16("65535", &n) && n == static_cast<uint16_t>(65535)); + BOOST_CHECK(ParseUInt16("+65535", &n) && n == 65535); + BOOST_CHECK(ParseUInt16("00000000000000000012", &n) && n == 12); + BOOST_CHECK(ParseUInt16("00000000000000000000", &n) && n == 0); + // Invalid values + BOOST_CHECK(!ParseUInt16("-00000000000000000000", &n)); + BOOST_CHECK(!ParseUInt16("", &n)); + BOOST_CHECK(!ParseUInt16(" 1", &n)); // no padding inside + BOOST_CHECK(!ParseUInt16(" -1", &n)); + BOOST_CHECK(!ParseUInt16("++1", &n)); + BOOST_CHECK(!ParseUInt16("+-1", &n)); + BOOST_CHECK(!ParseUInt16("-+1", &n)); + BOOST_CHECK(!ParseUInt16("--1", &n)); + BOOST_CHECK(!ParseUInt16("-1", &n)); + BOOST_CHECK(!ParseUInt16("1 ", &n)); + BOOST_CHECK(!ParseUInt16("1a", &n)); + BOOST_CHECK(!ParseUInt16("aap", &n)); + BOOST_CHECK(!ParseUInt16("0x1", &n)); // no hex + BOOST_CHECK(!ParseUInt16(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); + // Overflow and underflow + BOOST_CHECK(!ParseUInt16("-65535", &n)); + BOOST_CHECK(!ParseUInt16("65536", &n)); + BOOST_CHECK(!ParseUInt16("-123", &n)); + BOOST_CHECK(!ParseUInt16("-123", nullptr)); + BOOST_CHECK(!ParseUInt16("65536", nullptr)); +} + BOOST_AUTO_TEST_CASE(test_ParseUInt32) { uint32_t n; @@ -1551,7 +1587,6 @@ BOOST_AUTO_TEST_CASE(test_ParseUInt32) BOOST_CHECK(!ParseUInt32("1a", &n)); BOOST_CHECK(!ParseUInt32("aap", &n)); BOOST_CHECK(!ParseUInt32("0x1", &n)); // no hex - BOOST_CHECK(!ParseUInt32("0x1", &n)); // no hex BOOST_CHECK(!ParseUInt32(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseUInt32("-2147483648", &n)); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index faccd1ade0..67549fc13d 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -895,7 +895,7 @@ std::optional<CTxMemPool::txiter> CTxMemPool::GetIter(const uint256& txid) const { auto it = mapTx.find(txid); if (it != mapTx.end()) return it; - return {}; + return std::nullopt; } CTxMemPool::setEntries CTxMemPool::GetIterSet(const std::set<uint256>& hashes) const diff --git a/src/txmempool.h b/src/txmempool.h index 9d4ea760e7..c3a9bd851d 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -476,7 +476,7 @@ enum class MemPoolRemovalReason { */ class CTxMemPool { -private: +protected: const int m_check_ratio; //!< Value n means that 1 times in n we check. std::atomic<unsigned int> nTransactionsUpdated{0}; //!< Used by getblocktemplate to trigger CreateNewBlock() invocation CBlockPolicyEstimator* minerPolicyEstimator; diff --git a/src/util/check.h b/src/util/check.h index bc62da3440..e60088a2c6 100644 --- a/src/util/check.h +++ b/src/util/check.h @@ -69,7 +69,7 @@ T get_pure_r_value(T&& val) #ifdef ABORT_ON_FAILED_ASSUME #define Assume(val) Assert(val) #else -#define Assume(val) ((void)(val)) +#define Assume(val) ([&]() -> decltype(get_pure_r_value(val)) { auto&& check = (val); return std::forward<decltype(get_pure_r_value(val))>(check); }()) #endif #endif // BITCOIN_UTIL_CHECK_H diff --git a/src/validation.cpp b/src/validation.cpp index 74df13f462..d1b9efe7ba 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2974,6 +2974,10 @@ bool CChainState::PreciousBlock(BlockValidationState& state, const CChainParams& bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex) { + // Genesis block can't be invalidated + assert(pindex); + if (pindex->nHeight == 0) return false; + CBlockIndex* to_mark_failed = pindex; bool pindex_was_in_chain = false; int disconnected = 0; @@ -5182,7 +5186,7 @@ std::optional<uint256> ChainstateManager::SnapshotBlockhash() const { // If a snapshot chainstate exists, it will always be our active. return m_active_chainstate->m_from_snapshot_blockhash; } - return {}; + return std::nullopt; } std::vector<CChainState*> ChainstateManager::GetAll() diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index 4543f6fb4c..6a59bc2b38 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -76,7 +76,7 @@ bool VerifyWallets(interfaces::Chain& chain) bilingual_str error_string; if (!MakeWalletDatabase(wallet_file, options, status, error_string)) { if (status == DatabaseStatus::FAILED_NOT_FOUND) { - chain.initWarning(Untranslated(strprintf("Skipping -wallet path that doesn't exist. %s\n", error_string.original))); + chain.initWarning(Untranslated(strprintf("Skipping -wallet path that doesn't exist. %s", error_string.original))); } else { chain.initError(error_string); return false; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d04279ab5e..b00fa851fd 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -238,7 +238,7 @@ std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& { auto result = WITH_LOCK(g_loading_wallet_mutex, return g_loading_wallet_set.insert(name)); if (!result.second) { - error = Untranslated("Wallet already being loading."); + error = Untranslated("Wallet already loading."); status = DatabaseStatus::FAILED_LOAD; return nullptr; } diff --git a/test/functional/feature_anchors.py b/test/functional/feature_anchors.py new file mode 100755 index 0000000000..a60a723b3e --- /dev/null +++ b/test/functional/feature_anchors.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 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 Anchors functionality""" + +import os + +from test_framework.p2p import P2PInterface +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +def check_node_connections(*, node, num_in, num_out): + info = node.getnetworkinfo() + assert_equal(info["connections_in"], num_in) + assert_equal(info["connections_out"], num_out) + + +class AnchorsTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def setup_network(self): + self.setup_nodes() + + def run_test(self): + self.log.info("Add 2 block-relay-only connections to node 0") + for i in range(2): + self.log.debug(f"block-relay-only: {i}") + self.nodes[0].add_outbound_p2p_connection( + P2PInterface(), p2p_idx=i, connection_type="block-relay-only" + ) + + self.log.info("Add 5 inbound connections to node 0") + for i in range(5): + self.log.debug(f"inbound: {i}") + self.nodes[0].add_p2p_connection(P2PInterface()) + + self.log.info("Check node 0 connections") + check_node_connections(node=self.nodes[0], num_in=5, num_out=2) + + # 127.0.0.1 + ip = "7f000001" + + # Since the ip is always 127.0.0.1 for this case, + # we store only the port to identify the peers + block_relay_nodes_port = [] + inbound_nodes_port = [] + for p in self.nodes[0].getpeerinfo(): + addr_split = p["addr"].split(":") + if p["connection_type"] == "block-relay-only": + block_relay_nodes_port.append(hex(int(addr_split[1]))[2:]) + else: + inbound_nodes_port.append(hex(int(addr_split[1]))[2:]) + + self.log.info("Stop node 0") + self.stop_node(0) + + node0_anchors_path = os.path.join( + self.nodes[0].datadir, "regtest", "anchors.dat" + ) + + # It should contain only the block-relay-only addresses + self.log.info("Check the addresses in anchors.dat") + + with open(node0_anchors_path, "rb") as file_handler: + anchors = file_handler.read().hex() + + for port in block_relay_nodes_port: + ip_port = ip + port + assert ip_port in anchors + for port in inbound_nodes_port: + ip_port = ip + port + assert ip_port not in anchors + + self.log.info("Start node 0") + self.start_node(0) + + self.log.info("When node starts, check if anchors.dat doesn't exist anymore") + assert not os.path.exists(node0_anchors_path) + + +if __name__ == "__main__": + AnchorsTest().main() diff --git a/test/functional/feature_utxo_set_hash.py b/test/functional/feature_utxo_set_hash.py index 6e6046d84d..ce00faffee 100755 --- a/test/functional/feature_utxo_set_hash.py +++ b/test/functional/feature_utxo_set_hash.py @@ -6,7 +6,6 @@ import struct -from test_framework.blocktools import create_transaction from test_framework.messages import ( CBlock, COutPoint, @@ -15,38 +14,30 @@ from test_framework.messages import ( from test_framework.muhash import MuHash3072 from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal +from test_framework.wallet import MiniWallet class UTXOSetHashTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - - def test_deterministic_hash_results(self): - self.log.info("Test deterministic UTXO set hash results") - - # These depend on the setup_clean_chain option, the chain loaded from the cache - assert_equal(self.nodes[0].gettxoutsetinfo()['hash_serialized_2'], "b32ec1dda5a53cd025b95387aad344a801825fe46a60ff952ce26528f01d3be8") - assert_equal(self.nodes[0].gettxoutsetinfo("muhash")['muhash'], "dd5ad2a105c2d29495f577245c357409002329b9f4d6182c0af3dc2f462555c8") - def test_muhash_implementation(self): self.log.info("Test MuHash implementation consistency") node = self.nodes[0] + wallet = MiniWallet(node) + mocktime = node.getblockheader(node.getblockhash(0))['time'] + 1 + node.setmocktime(mocktime) # Generate 100 blocks and remove the first since we plan to spend its # coinbase - block_hashes = node.generate(100) + block_hashes = wallet.generate(1) + node.generate(99) blocks = list(map(lambda block: FromHex(CBlock(), node.getblock(block, False)), block_hashes)) - spending = blocks.pop(0) + blocks.pop(0) # Create a spending transaction and mine a block which includes it - tx = create_transaction(node, spending.vtx[0].rehash(), node.getnewaddress(), amount=49) - txid = node.sendrawtransaction(hexstring=tx.serialize_with_witness().hex(), maxfeerate=0) - - tx_block = node.generateblock(output=node.getnewaddress(), transactions=[txid]) + txid = wallet.send_self_transfer(from_node=node)['txid'] + tx_block = node.generateblock(output=wallet.get_address(), transactions=[txid]) blocks.append(FromHex(CBlock(), node.getblock(tx_block['hash'], False))) # Serialize the outputs that should be in the UTXO set and add them to @@ -77,8 +68,11 @@ class UTXOSetHashTest(BitcoinTestFramework): assert_equal(finalized[::-1].hex(), node_muhash) + self.log.info("Test deterministic UTXO set hash results") + assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "5b1b44097406226c0eb8e1362cd17a1f346522cf9390a8175a57a5262cb1963f") + assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "4b8803075d7151d06fad3e88b68ba726886794873fbfa841d12aefb2cc2b881b") + def run_test(self): - self.test_deterministic_hash_results() self.test_muhash_implementation() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index a18a9ec109..5a9736a7a3 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -1045,13 +1045,11 @@ class msg_version: self.nStartingHeight = struct.unpack("<i", f.read(4))[0] - if self.nVersion >= 70001: - # Relay field is optional for version 70001 onwards - try: - self.relay = struct.unpack("<b", f.read(1))[0] - except: - self.relay = 0 - else: + # Relay field is optional for version 70001 onwards + # But, unconditionally check it to match behaviour in bitcoind + try: + self.relay = struct.unpack("<b", f.read(1))[0] + except struct.error: self.relay = 0 def serialize(self): diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index 38fbf3c1a6..a906a21dd0 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -49,6 +49,9 @@ class MiniWallet: self._utxos.append({'txid': cb_tx['txid'], 'vout': 0, 'value': cb_tx['vout'][0]['value']}) return blocks + def get_address(self): + return self._address + def get_utxo(self, *, txid=''): """ Returns a utxo and marks it as spent (pops it from the internal list) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 79ad2cf161..28d3518715 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -279,6 +279,7 @@ BASE_SCRIPTS = [ 'p2p_ping.py', 'rpc_scantxoutset.py', 'feature_logging.py', + 'feature_anchors.py', 'p2p_node_network_limited.py', 'p2p_permissions.py', 'feature_blocksdir.py', diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index bf24b9c7b3..71d1b96a95 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -34,7 +34,7 @@ def test_load_unload(node, name): node.loadwallet(name) node.unloadwallet(name) except JSONRPCException as e: - if e.error['code'] == -4 and 'Wallet already being loading' in e.error['message']: + if e.error['code'] == -4 and 'Wallet already loading' in e.error['message']: got_loading_error = True return |